Print elements of array of arrays of different size in same line in Ruby

188 Views Asked by At

Maybe someone could help me with this. I have an array of arrays. The internal arrays have different sizes (from 2 to 4 elements).

letters = [["A", "B"],["C", "D", "F", "G"],["H", "I", "J" ]] 

I'm trying to print in a same line each array havins as first column element[0] and element[1] joined, as 2nd column element[0], element[1], element[2] joined as 3rd column element[0], element[1], element[3] joined. Elements 2 and 3 not always exist.

The output I'm trying to get is like this:

AB
CD CDF CDG
HI HIJ

I'm doing in this way but I'm getting this error.

letters.map{|x| puts x[0]+x[1] + "," + x[0]+x[1]+x[2] + "," + x[0]+x[1]+x[3]}
TypeError: no implicit conversion of nil into String
        from (irb):1915:in "+"
        from (irb):1915:in "block in irb_binding"
        from (irb):1915:in "map"
        from (irb):1915
        from /usr/bin/irb:11:in "<main>"
3

There are 3 best solutions below

5
On BEST ANSWER
letters.each do |a,b,*rest|
  puts rest.each_with_object([a+b]) { |s,arr| arr << arr.first + s }.join(' ')
end

prints

AB
CD CDF CDG
HI HIJ

The steps are as follows.

Suppose

letters =  [["C", "D", "F", "G"],["H", "I", "J" ]] 

Then

enum0 = letters.each
  #=> #<Enumerator: [["C", "D", "F", "G"], ["H", "I", "J"]]:each>

The first element of this enumerator is generated and passed to the block, and the three block variables are assigned values.

a, b, *rest = enum0.next
  #=> ["C", "D", "F", "G"]
a
  #=> "C"
b 
  #=> "D"
rest
  #=> ["F", "G"]

Next, we obtain

enum1 = rest.each_with_object([a+b])
  #=> rest.each_with_object(["CD"]) 
  #=> #<Enumerator: ["F", "G"]:each_with_object(["CD"])>

The first element of this enumerator is generated and passed to the block, and the block variables are assigned values.

s, arr = enum1.next
  #=> ["F", ["CD"]]
s
  #=> "F"
arr
  #=> ["CD"]

The block calculation is now performed.

arr << arr.first + s
  #=> arr << "CD" + "F"
  #=> ["CD", "CDF"] 

The second and last element of enum1 is generated and passed to the block, and block variables are assigned values and the block is computed.

s, arr = enum1.next
  #=> ["G", ["CD", "CDF"]]
arr << arr.first + s
  #=> ["CD", "CDF", "CDG"]

When an attempt to generate another element from enum1 we obtain

enum1.next
  #StopIteration: iteration reached an end

Ruby handles the exception by breaking out of the block and returning arr. The elements of arr are then joined:

arr.join(' ')
  #=> "CD CDF CDG"

and printed.

The second and last element of enum0 is now generated, passed to the block, and the three block variables are assigned values.

a, b, *rest = enum0.next
  #=> ["H", "I", "J"]
a
  #=> "H"
b
  #=> "I"
rest
  #=> ["J"]

The remaining calculations are similar.

Some readers may be unfamiliar with the method Enumerable#each_with_object, which is widely used. Read the doc, but note that here it yields the same result as the code written as follows.

letters.each do |a,b,*rest|
  arr = [a+b]
  rest.each { |s| arr << arr.first + s }
  puts arr.join(' ')
end

By using each_with_object we avoid the need for the statement arr = [a+b] and the statement puts arr.join(' '). The functions of those two statements are of course there in the line using each_with_object, but most Ruby users prefer the flow when when chaining each_with_object to join(' '). One other difference is that the value of arr is confined to each_with_object's block, which is good programming practice.

1
On

Just out of curiosity, Enumerable#map-only solution.

letters = [["A", "B"],["C", "D", "F", "G"],["H", "I", "J" ]]

letters.map do |f, s, *rest|
  rest.unshift(nil).map { |l| [f, s, l].join }.join(' ')
end.each(&method(:puts))

#⇒ AB
#  CD CDF CDG
#  HI HIJ
2
On

Looks like you want to join the first two letters, then take the cartesian product with the remaining.

letters.each do |arr|
   first = arr.take(2).join
   rest = arr.drop(2)
   puts [first, [first].product(rest).map(&:join)].join(" ")
end

This provides the exact output you specified.