Search code examples
rubyloopsprintingarrayofarrays

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


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>"

Solution

  • 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.