Cast an array to some arrays

I would like to expand a Ruby array (which may contain some subarrays) into another array of arrays like in these examples:

Example 1: [:foo, :bar]

[
  [:foo, :bar]
]

      

Example 2: [:foo, :bar, [:ki, :ku]]

[
  [:foo, :bar, :ki],
  [:foo, :bar, :ku]
]

      

Example 3: [:foo, :bar, :baz, [:a, :i, :u, :e, :o], :qux]

[
  [:foo, :bar, :baz, :a, :qux],
  [:foo, :bar, :baz, :i, :qux],
  [:foo, :bar, :baz, :u, :qux],
  [:foo, :bar, :baz, :e, :qux],
  [:foo, :bar, :baz, :o, :qux]
]

      

Example 4: [:foo, :bar, :baz, [:a, :i, :u, :e, :o], [1, 2], :qux]

[
  [:foo, :bar, :baz, :a, 1, :qux],
  [:foo, :bar, :baz, :i, 1, :qux],
  [:foo, :bar, :baz, :u, 1, :qux],
  [:foo, :bar, :baz, :e, 1, :qux],
  [:foo, :bar, :baz, :o, 1, :qux],
  [:foo, :bar, :baz, :a, 2, :qux],
  [:foo, :bar, :baz, :i, 2, :qux],
  [:foo, :bar, :baz, :u, 2, :qux],
  [:foo, :bar, :baz, :e, 2, :qux],
  [:foo, :bar, :baz, :o, 2, :qux]
]

      

Example 5: [:foo, [[], :c], :bar]

[
  [:foo, [], :bar],
  [:foo, :c, :bar]
]

      

Example 6: [:foo, [[:a, :b], :c], :bar]

[
  [:foo, [:a, :b], :bar],
  [:foo, :c, :bar]
]

      

Note. Only subarrays need to be expanded. Therefore, in examples 5 and 6, the sub-arrays are not expanded.

Thanks a lot for any suggestions or piece of code.

+3


source to share


3 answers


I used an idea product

to achieve this functionality:

def trans(a)
  b = a.map{|e| [e].flatten(1)}
  b.first.product(*b.slice(1..-1))
end

      

For example, this code:

puts trans([:foo, :bar]).inspect
puts trans([:foo, :bar, :baz, [:a, :i, :u, :e, :o], [1, 2], :qux]).inspect
puts trans([:foo, [[], :c], :bar]).inspect
puts trans([:foo, [[:a, :b], :c], :bar]).inspect

      

Gives the following:

[[:foo, :bar]]
[[:foo, :bar, :baz, :a, 1, :qux],
 [:foo, :bar, :baz, :a, 2, :qux],
 [:foo, :bar, :baz, :i, 1, :qux],
 [:foo, :bar, :baz, :i, 2, :qux],
 [:foo, :bar, :baz, :u, 1, :qux],
 [:foo, :bar, :baz, :u, 2, :qux],
 [:foo, :bar, :baz, :e, 1, :qux],
 [:foo, :bar, :baz, :e, 2, :qux],
 [:foo, :bar, :baz, :o, 1, :qux],
 [:foo, :bar, :baz, :o, 2, :qux]]
[[:foo, [], :bar],
 [:foo, :c, :bar]]
[[:foo, [:a, :b], :bar],
 [:foo, :c, :bar]]

      

EDIT: Explanation of the above code.



The general idea is that we want to get the product of all the elements in the array. If you look at the Array # product documentation , you can see that it does what you want - we just need to name it appropriately.

First, it product

works with arrays, so we have to make sure that all the elements in our original array are the array itself. This is the task of the first line of the function:

b = a.map{|e| [e].flatten(1)}

      

We will convert all elements to an array using map

. The transformation makes an array with an element e

inside and then aligns that new array. Either the original element was an array, or it was not; if it was not an array, [e].flatten(1)

it will do nothing and return [e]

; if it was an array, it [e]

will evaluate to [[x]]

, which will then be flattened to [x]

. 1

tells you flatten

to go only 1 depth level.

Then we only need to call product

for the first element passing the remaining elements of the modified array as an argument:

b.first.product(*b.slice(1..-1))

      

Here b.slice(1..-1)

means: take elements from b, starting from the second to the last. Finally, the asterisk indicates that we do not want to pass the array as an argument, but instead the elements of the array.

+8


source


You seem to want to have the Cartesian product of the elements of the array in question. This code should work for you:

array = [:foo, :bar, :baz, [:a, :i, :u, :e, :o], [1, 2], :qux]
array.inject([[]]) do |product,element|
  result = []
  if element.is_a?(Array)
    product.each do |tuple|
      element.each do |last|
        result << tuple + [last]
      end
    end
  else
    product.each do |tuple|
      result << tuple + [element]
    end
  end
  result
end

      



You can simplify it a bit by moving the conditional expression into a loop, but this will make it less efficient.

+2


source


Given that you are using examples and not something explicit, I suggest having a trawl through the Array documentation . To get started, consider the following methods:

.combination.to_a
.shift
.transpose
.flatten
.zip
.take

      

How you implement depends on whether you know what you are transforming in each case, or if you are trying to create something in common (ie extend the Array class. For each, I will manipulate the input array into the target array. Example 1 is simple:

input = [:foo,:bar]
target = Array.new
target << input        => [[:foo,:bar]]

      

In the rest of the examples, there are several ways to get there, depending on what you are trying to do and how you want the ruby ​​to know what to do when the entrance is presented. In example 2, the first is to write directly:

input = [:foo, :bar, [:ki, :ku]]
target = Array.new

target << [input[0], input[1], input[2][0]]
target << [input[0], input[1], input[2][1]]

      

Or playing with array methods:

target = input.pop
target = [input, input].zip(target).flatten
target = [target[0..(target.size/2)-1], target[target.size/2..-1]]

      

Or, if you don't know which part of the array contains the submatrix, you can detect it:

input.each do |i|
  if i.class == Array
    holding = i
  end
end

      

It really depends on how you want to identify and manipulate the array!

+1


source







All Articles