David B. David B. - 5 months ago 16
Ruby Question

Ruby: Merging nested array between each other depending on a condition

What would be the best way to merge arrays nested in an array that shares at least an element ? Here's an example:

some_method([[1, 2], [2, 3], [4, 5]])
#=> [[1, 2, 3], [4, 5]]
some_method([[1, 2], [2, 3], [3, 4], [5,6]])
#=> [[1, 2, 3, 4], [5, 6]]

Answer

This would work:

def some_method(arrays)
  h = Hash.new { |h, k| h[k] = [] }
  arrays.each do |array|
    tmp = h.values_at(*array).push(array).inject(:|)
    tmp.each { |k| h[k] = tmp }
  end
  h.values | h.values
end

Examples:

some_method([[1, 2], [2, 3], [4, 5]])          #=> [[1, 2, 3], [4, 5]]    
some_method([[1, 2], [2, 3], [3, 4], [5, 6]])  #=> [[1, 2, 3, 4], [5, 6]]    
some_method([[1, 3], [3, 4], [2, 5], [4, 5]])  #=> [[1, 3, 4, 2, 5]]

I'm using a hash h to store the array that correspond to a given element. The hash returns [] if a key doesn't exist.

After inserting [1, 2], the hash looks like this:

{
  1 => [1, 2],
  2 => [1, 2]
}

When inserting [2, 3], the arrays for 2 and 3 are fetched via:

h.values_at(2, 3)
#=> [[1, 2], []]

then [2, 3] itself is added:

h.values_at(2, 3).push([2, 3])
#=> [[1, 2], [], [2, 3]]

and everything is |-ed:

h.values_at(2, 3).push([2, 3]).inject(:|)
#=> [1, 2, 3]

This result is stored in tmp. It becomes the new value for the contained keys:

tmp.each { |k| h[k] = tmp }

Which is equivalent to:

h[1] = tmp
h[2] = tmp
h[3] = tmp

Afterwards, h looks like this:

{
  1 => [1, 2, 3],
  2 => [1, 2, 3],
  3 => [1, 2, 3]
}

At the end, the distinct values are returned via h.values | h.values.

Comments