Ruby - array flatten - ruby

I have an n-depth array where n is a variable greater than or equal to 2:
[[1,1],[[1,1],[1,1]]]
I want to flatten this array to have exactly 2 depth, like this:
[[1,1],[1,1],[1,1]]
Can anyone think of a good way to achieve that?

This should do it.
def flatten_after_first(arr)
arr.flat_map { |a| a.first.is_a?(Array) ? a.map(&:flatten) : [a] }
end
flatten_after_first [[1,1],[[1,1],[1,1]]]
#=> [[1, 1], [1, 1], [1, 1]]
flatten_after_first [[1,1], [[2,2], [2,2]], [[[3,3], [3,3]], [[3,3], [3,3]]]]
#=> [[1, 1], [2, 2], [2, 2], [3, 3, 3, 3], [3, 3, 3, 3]]

May be this will help
def flat(array)
array.each do |item|
if item.is_a?(Array) && item.flatten.count != item.count
flat(item)
else
$arr << item
end
end
end
###
$arr = []
collection = [[1, 1], [[1, 1], [1, 1], [[1, 2], [1, 2, 3]]]]
flat(collection)
puts $arr.inspect
=> [[1, 1], [1, 1], [1, 1], [1, 2], [1, 2, 3]]
$arr = []
collection = [[1,1],[[[1,1],[1,1]],[1,1]]]
flat(collection)
$arr
=> [[1, 1], [1, 1], [1, 1], [1, 1]]

Try this:
def depth_first_flatten array
result = []
array.each do |element|
if element.first.is_a? Array
result += deph(element)
else
result << element
end
end
result
end
# array = [[1,2],[[3,4],[5,6]]]
# depth_first_flatten(array)
#
# OUTPUT: [[1, 2], [3, 4], [5, 6]]

Related

Ruby, remove super-arrays

If I have an array of arrays, A, and want to get rid of all arrays in A who also have a sub-array in A, how would I do that. In this context, array_1 is a sub-array of array_2 if array_1 - array_2 = []. In the case that multiple arrays are simply rearranged versions of the same elements, bonus points if you can get rid of all but one of them, but you can handle this however you want if it's easier.
In python, I could easily use comprehension, with A being a set of frozen sets :
A = {a for a in A if all(b-a for b in A-{a})}
Is there a simple way to write this in ruby? I don't care if the order of A or it's arrays are preserved at all. Also, in my program, none of the arrays have duplicate elements, if that makes things any easier/faster.
Example
A = [[1,6],[1,2],[2,4],[3,5],[1,3,6],[2,3,6]]
# [1,6] is a subarray of [1,3,6], so [1,3,6] should be removed
remove_super_arrays(A)
> A = [[1,6],[1,2],[2,4],[3,5],[2,3,6]]
A = [[1,2,4],[2,3,4],[1,4,5],[2,6]]
# although there is overlap, there are no subarrays, so nothing should be removed
remove_super_arrays(A)
> A = [[1,2,4],[2,3,4],[1,4,5],[2,6]]
A = [[1],[2,1,3],[2,4],[1,4]]
# [1] is a subarray of [2,1,3] and [1,4]
remove_super_arrays(A)
> A = [[1],[2,4]]
Code
def remove_super_arrays(arr)
order = arr.each_with_index.to_a.to_h
arr.sort_by(&:size).reject.with_index do |a,i|
arr[0,i].any? { |aa| (aa.size < a.size) && (aa-a).empty? }
end.sort_by { |a| order[a] }
end
Examples
remove_super_arrays([[1,6],[1,2],[2,4],[3,5],[1,3,6],[2,3,6]] )
#=> [[1,6],[1,2],[2,4],[3,5],[2,3,6]]
remove_super_arrays([[1,2,4],[2,3,4],[1,4,5],[2,6]])
#=> [[1,2,4],[2,3,4],[1,4,5],[2,6]]
remove_super_arrays([[1],[2,1,3],[2,4],[1,4]])
#=> [[1],[2,4]]
Explanation
Consider the first example.
arr = [[1,6],[1,2],[2,4],[3,5],[1,3,6],[2,3,6]]
We first save the positions of the elements of a
order = arr.each_with_index.to_a.to_h # save original order
#=> {[1, 6]=>0, [1, 2]=>1, [2, 4]=>2, [3, 5]=>3, [1, 3, 6]=>4, [2, 3, 6]=>5}
Then reject elements of arr:
b = arr.sort_by(&:size)
#=> [[1, 6], [1, 2], [2, 4], [3, 5], [1, 3, 6], [2, 3, 6]]
c = b.reject.with_index do |a,i|
arr[0,i].any? { |aa| (aa.size < a.size) && (aa-a).empty? }
end
#=> [[1, 6], [1, 2], [2, 4], [3, 5], [2, 3, 6]]
Lastly, reorder c to correspond to the original ordering of the elements of arr.
c.sort_by { |a| order[a] }
#=> [[1, 6], [1, 2], [2, 4], [3, 5], [2, 3, 6]]
which in this case happens to be the same order as the elements of c.
Let's look more carefully at the calculation of c:
enum1 = b.reject
#=> #<Enumerator: [[1, 6], [1, 2], [2, 4], [3, 5], [1, 3, 6],
# [2, 3, 6]]:reject>
enum2 = enum1.with_index
#=> #<Enumerator: #<Enumerator: [[1, 6], [1, 2], [2, 4], [3, 5],
# [1, 3, 6], [2, 3, 6]]:reject>:with_index>
The first element is generated by the enumerator enum2 and passed to the block and assigned as values of the block variables:
a, i = enum2.next
#=> [[1, 6], 0]
a #=> [1, 6]
i #=> 0
The block calculation is then performed:
d = arr[0,i]
#=> []
d.any? { |aa| (aa.size < a.size) && (aa-a).empty? }
#=> false
so a[0] is not rejected. The next pair passed to the block by enum2 is [[1, 2], 1]. That value is retained as well, but let's skip ahead to the last element passed to the block by enum2:
a, i = enum2.next
#=> [[1, 2], 1]
a, i = enum2.next
#=> [[2, 4], 2]
a, i = enum2.next
#=> [[3, 5], 3]
a, i = enum2.next
#=> [[1, 3, 6], 4]
a #=> [1, 3, 6]
i #=> 4
Perform the block calculation:
d = arr[0,i]
#=> [[1, 6], [1, 2], [2, 4], [3, 5]]
d.any? { |aa| (aa.size < a.size) && (aa-a).empty? }
#=> true
As true is returned, a is rejected. In the last calculation the first element of d is passed to the block and the following calculation is performed:
aa = [1, 6]
(aa.size < a.size)
#=> 2 < 3 => true
(aa-a).empty?
#=> ([1, 6] - [1, 3, 6]).empty? => [].empty? => true
As true && true #=> true, a ([1, 3, 6]) is rejected.
Alternative calculation
The following is a closer match to the OP's Python equivalent, but less efficient:
def remove_super_arrays(arr)
arr.select do |a|
(arr-[a]).all? { |aa| aa.size > a.size || (aa-a).any? }
end
end
or
def remove_super_arrays(arr)
arr.reject do |a|
(arr-[a]).any? { |aa| (aa.size < a.size) && (aa-a).empty? }
end
end
This was a nice exercise for me. I have used the logic from here.
My code iterates over each subarray (except the first), then there is the magic substraction using the first index, when it is empty the other array contained both numbers.
def remove_super_arrays(arr)
arr.each_with_index.with_object([]) do |(sub_array, index), result|
next if index == 0
result << sub_array unless (arr.first - sub_array).empty?
end.unshift(arr.first)
end
arr = [[1,6],[1,2],[2,4],[3,5],[1,3,6],[2,3,6]]
p remove_super_arrays(arr)
#=> [[1, 6], [1, 2], [2, 4], [3, 5], [2, 3, 6]]

Ruby for loop prints extra things

I have an of arrays of arrays, and it looks like this:
[
[[1, 3], [3, 0]],
[[0, 0], [0, 3], [3, 2], [3, 3]],
[[0, 1], [0, 2], [1, 0], [2, 0], [2, 3], [3, 1]],
[[1, 2], [2, 2]],
[[1, 1], [2, 1]]
]
I want to print each element. I expect this output:
0,(1,3),(3,0)
1,(0,0),(0,3),(3,2),(3,3)
2,(0,1),(0,2),(1,0),(2,0),(2,3),(3,1)
3,(1,2),(2,2)
4,(1,1),(2,1)
This is my code:
for indx in 0..4
print "#{indx}"
for cell in cell_arr[indx]
print ",(#{cell[0]},#{cell[1]})"
end
if indx <= 4
puts
end
end
The output I got:
0,(1,3),(3,0)
1,(0,0),(0,3),(3,2),(3,3)
2,(0,1),(0,2),(1,0),(2,0),(2,3),(3,1)
3,(1,2),(2,2)
4,(1,1),(2,1)
0..4
During the end of the output, my code generates something extra.
0..4
is a return of for
If you call
for i in 0..100
end
the returns will be
0..100
to escape this:
for indx in 0..4
print "#{indx}"
for cell in cell_arr[indx]
print ",(#{cell[0]},#{cell[1]})"
end
if indx <= 4
puts
end
end; nil
Option nesting Enumerable#each_with_object inside Enumerable#each_with_index:
array.map.with_index { |ary, i| (ary.each_with_object ([]) { |e, tmp| tmp << "(#{e.first}, #{e.last})" }).unshift(i).join(',') }
Which puts:
# 0,(1, 3),(3, 0)
# 1,(0, 0),(0, 3),(3, 2),(3, 3)
# 2,(0, 1),(0, 2),(1, 0),(2, 0),(2, 3),(3, 1)
# 3,(1, 2),(2, 2)
# 4,(1, 1),(2, 1)
The nested loop operates on each sub-array in this way:
sub_arry = [[0, 1], [0, 2], [1, 0], [2, 0], [2, 3], [3, 1]]
res = sub_arry.each_with_object ([]) { |e, tmp| tmp << "(#{e.first}, #{e.last})" }
res #=> ["(0, 1)", "(0, 2)", "(1, 0)", "(2, 0)", "(2, 3)", "(3, 1)"]
The idea is to place the index of the element inside the array, then use Array#join to build the row to be printed:
res.unshift(2).join(',')
#=> "2,(0, 1),(0, 2),(1, 0),(2, 0),(2, 3),(3, 1)"

How to merge embedded array with array in Ruby

I have arrays and nested arrays, such that:
a = [1,2]
b = [[3,4],[5,6]]
c = [7,8]
What's the best way to create
d = [[1,2],[3,4],[5,6],[7,8]]
in Ruby?
UPDATE:
The goal is to create a method below:
def foo([a,b,c])
--some logic that iterates through each array--
end
def foo(xss)
xss.flat_map { |xs| xs.first.is_a?(Array) ? xs : [xs] }
en
foo([a, b, c]) #=> [[1, 2], [3, 4], [5, 6], [7, 8]]
This will work assuming inputs are always arrays. Might have some side effects if any input is formatted differently.
def foo(*args)
args.map {|a| a.first.is_a?(Array) ? a : [a]}.inject([], :+)
end
foo([1,2], [[3,4],[5,6]], [7,8]) #=> [[1, 2], [3, 4], [5, 6], [7, 8]]
def foo(a,b,c)
final = []
a.first.is_a?(Array) ? a.each { |x| final << x } : final << a
b.first.is_a?(Array) ? b.each { |x| final << x } : final << b
c.first.is_a?(Array) ? c.each { |x| final << x } : final << c
final
end
I'd do :
a = [1,2]
b = [[3,4],[5,6]]
c = [7,8]
final_ary = [a,b,c].inject([]) do |out_ary,ary|
if ary.first.is_a?(Array)
out_ary.concat(ary)
else
out_ary.push(ary)
end
end
final_ary
# => [[1, 2], [3, 4], [5, 6], [7, 8]]
def foo (*args)
args.flatten.each_slice(2).to_a
end
foo(a,b,c) # => [[1, 2], [3, 4], [5, 6], [7, 8]]
A general solution:
def extract(arr, result=[])
return result if arr.empty?
arr.each {|e| (e.first.is_a? Array) ? result=extract(e,result) : result << e}
end
p extract( [ [1,2],[3,4,5],[6,7] ] )
#=> [[1, 2], [3, 4, 5], [6, 7]]
p extract( [ [1,2],[ [3,4],[5,6] ],[7,8]] )
#=> [[1, 2], [3, 4], [5, 6], [7, 8]]
p extract( [ [1,2],[ [3,4],[ [5,6],[7,8] ] ],[9,10,11] ] )
#=> [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10, 11]]
p extract( [ [1,2],[ [3,4],[ [5,6],[ [7,8],[9,10] ] ] ],[11,12] ])
#=> [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]]

Array of arrays into array of hashes

i want to convert in ruby
[[1, 1], [2, 3], [3, 5], [4, 1], [1, 2], [2, 3], [3, 5], [4, 1]]
into
[{1=>1}, {2=>3}, {3=>5}, {4=>1}, {1=>2}, {2=>3}, {3=>5}, {4=>1}]
and after this to obtain sum of all different keys:
{1=>3,2=>6,3=>10,4=>2}
For the second question
sum = Hash.new(0)
original_array.each{|x, y| sum[x] += y}
sum # => {1 => 3, 2 => 6, 3 => 10, 4 => 2}
Functional approach:
xs = [[1, 1], [2, 3], [3, 5], [4, 1], [1, 2], [2, 3], [3, 5], [4, 1]]
Hash[xs.group_by(&:first).map do |k, pairs|
[k, pairs.map { |x, y| y }.inject(:+)]
end]
#=> {1=>3, 2=>6, 3=>10, 4=>2}
Using Facets is much simpler thanks to the abstractions map_by (a variation of group_by) and mash (map + Hash):
require 'facets'
xs.map_by { |k, v| [k, v] }.mash { |k, vs| [k, vs.inject(:+)] }
#=> {1=>3, 2=>6, 3=>10, 4=>2}
You don't need the intermediate form.
arrays = [[1, 1], [2, 3], [3, 5], [4, 1], [1, 2], [2, 3], [3, 5], [4, 1]]
aggregate = arrays.each_with_object Hash.new do |(key, value), hash|
hash[key] = hash.fetch(key, 0) + value
end
aggregate # => {1=>3, 2=>6, 3=>10, 4=>2}
arr= [[1, 1], [2, 3], [3, 5], [4, 1], [1, 2], [2, 3], [3, 5], [4, 1]]
final = Hash.new(0)
second_step = arr.inject([]) do |arr,inner|
arr << Hash[*inner]
final[inner.first] += inner.last
arr
end
second_step
#=> [{1=>1}, {2=>3}, {3=>5}, {4=>1}, {1=>2}, {2=>3}, {3=>5}, {4=>1}]
final
#=> {1=>3, 2=>6, 3=>10, 4=>2}
if you directly only need the last step
arr.inject(Hash.new(0)){|hash,inner| hash[inner.first] += inner.last;hash}
=> {1=>3, 2=>6, 3=>10, 4=>2}

uniq elements from array of arrays

I want to find unique elements from an array of arrays by the first element in the inner arrays.
for example
a = [[1,2],[2,3],[1,5]
I want something like
[[1,2],[2,3]]
The uniq method takes a block:
uniq_a = a.uniq(&:first)
Or if you want to do it in-place:
a.uniq!(&:first)
For example:
>> a = [[1,2],[2,3],[1,5]]
=> [[1, 2], [2, 3], [1, 5]]
>> a.uniq(&:first)
=> [[1, 2], [2, 3]]
>> a
=> [[1, 2], [2, 3], [1, 5]]
Or
>> a = [[1,2],[2,3],[1,5]]
=> [[1, 2], [2, 3], [1, 5]]
>> a.uniq!(&:first)
=> [[1, 2], [2, 3]]
>> a
=> [[1, 2], [2, 3]]
If you're stuck back in 1.8.7 land where uniq doesn't take a block, then you can do it this way:
a.group_by(&:first).values.map(&:first)
For example:
>> a = [[1,2],[2,3],[1,5]]
=> [[1, 2], [2, 3], [1, 5]]
>> a.group_by(&:first).values.map(&:first)
=> [[1, 2], [2, 3]]
Thanks for the extra prodding Jin.
Here's a ruby 1.8.7 solution
irb> [[1,2],[2,3],[1,5]].inject([]) { |memo,x| memo << x unless memo.detect { |item| item.first == x.first }; memo }
=> [[1, 2], [2, 3]]
You could also take the shorthand over a hash and be a bit lazy and take the last item instead
irb> [[1,2],[2,3],[1,5]].inject({}) { |memo,x| memo[x.first] = x; memo }.map { |x| x.last }
=> [[1, 5], [2, 3]]

Resources