Is there a Ruby one-liner to join() nested Ruby arrays, with different join() string for inner/outer arrays?

Viewed 102

For:

a = [ ["John", "Doe"], ["Sue", "Smith"]]

The desired output is: "John Doe, Sue Smith"

The brute-force code is easy:

a = [ ["John", "Doe"], ["Sue", "Smith"]]
name_array = []
a.each { |n| name_array << n.join(" ") } # first, inner join w/ space
s = name_array.join(", ") # then, outer join with comma

But is there a more succint (one-liner?) to accomplish this in Ruby?

3 Answers

Map and Join

You can do this with Array#map and Array#join. For example, in Ruby 2.7.1:

array = [["John", "Doe"], ["Sue", "Smith"]]
array.map { _1.join ?\s }.join ", "
#=> "John Doe, Sue Smith"

If you aren't using a recent Ruby, or find a more explicit syntax preferrable, you can do the same thing like so:

array.map { |subarray| subarray.join " " }.join ", "
#=> "John Doe, Sue Smith"

There are certainly other ways to do this, but this one-liner seems to fit your examples and use case.

When you want to construct a new array from an existing array (or any enumerable) really what you want to use is Enumerable#map.

Map iterates over your existing array, runs the block, and collects the results.

 result = a.map { |people| people.join(' ') }.join(', ')

Broken down:

intermediate = a.map { |people| people.join(' ') } 
# transforms
#   [ ["John", "Doe"], ["Sue", "Smith"]]
# into
#   [ "John Doe", "Sue Smith" ]
# we take the result of that and run
result = intermediate.join(', ')
# which then transforms your array in the the final string:
# "John Doe, Sue Smith"
a = [["John", "Doe"], ["Sue", "Smith"], ["Melba", "Jones"]]

The obvious way of doing this, that has been mentioned by others, is:

a.map { |arr| arr.join(' ') }.join(', ')
  #=> "John Doe, Sue Smith, Melba Jones"

As an exercise, here are three ways this can be done without using Array#map

Use Enumerable#reduce (aka inject)

a.drop(1).reduce(a.first.join(' ')) { |s,name| s + ", %s %s" % name }
  #=> "John Doe, Sue Smith, Melba Jones"

Use recursion

def doit((name, *rest))
  rest.empty? ? name.join(' ') : "%s %s, %s" % [*name, doit(rest)]
end
doit(a)
  #=> "John Doe, Sue Smith, Melba Jones"

Flatten, join with a space, use String#gsub to insert commas

r = /
    \w+[ ]\w+ # match two words separated by a space
    (?=[ ])   # positive lookahead asserts that next character is a space
    \K        # reset start of match to current location and discard all
              # previously matched characters from match that is returned
    /x        # free-spacing regex definition mode

a.flatten.join(' ').gsub(r, ',')
  #=> "John Doe, Sue Smith, Melba Jones"
Related