MackHalliday
MackHalliday

Reputation: 15

Zip all array values of hash

I'd like to zip all the array values of a hash. I know there's a way to zip arrays together. I'd like to do that with the values of my hash below.

current_hash = {:a=>["k", "r", "u"],
                :b=>["e", " ", "l"],
                :c=>["d", "o", "w"],
                :d=>["e", "h"]
                }

 desired_outcome = "keder ohulw"

I have included my desired outcome above.

Upvotes: 0

Views: 335

Answers (2)

Cary Swoveland
Cary Swoveland

Reputation: 110725

Here I'm fleshing out @Amadan's remark below the horizontal line in is answer. Suppose:

current_hash = { a:["k","r"], b:["e"," ","l"], c:["d","o","w"], d:["e", "h"] }

and you wished to return "keder ohlw". If you made ["k","r"] and [["e"," ","l"], ["d","o","w"], ["e", "h"]] zip's receiver and argument, respectively, you would get "keder oh", which omits "l" and "w". (See Array#zip, especially the 3rd paragraph.)

To include those strings you would need to fill out ["k","r"] with nils to make it as long as the longest value, or make zip's receiver an array of nils of the same length. The latter approach can be implemented as follows:

vals = current_hash.values
  #=> [["k", "r"], ["e", " ", "l"], ["d", "o", "w"], ["e", "h"]] 
([nil]*vals.map(&:size).max).zip(*vals).flatten.compact.join
  #=> "keder ohlw" 

Note:

a = [nil]*vals.map(&:size).max
  #=> [nil, nil, nil] 

and

a.zip(*vals)
  #=> [[nil, "k", "e", "d", "e"],
  #    [nil, "r", " ", "o", "h"],
  #    [nil, nil, "l", "w", nil]] 

One could alternatively use Array#transpose rather than zip.

vals = current_hash.values
idx = (0..vals.map(&:size).max-1).to_a
  #=> [0, 1, 2] 
vals.map { |a| a.values_at(*idx) }.transpose.flatten.compact.join
  #=> "keder ohlw" 

See Array#values_at. Note:

a = vals.map { |a| a.values_at(*idx) }
  #=> [["k", "r", nil],
  #    ["e", " ", "l"],
  #    ["d", "o", "w"],
  #    ["e", "h", nil]] 
a.transpose
  #=> [["k", "e", "d", "e"],
  #    ["r", " ", "o", "h"],
  #    [nil, "l", "w", nil]] 

Upvotes: 0

Amadan
Amadan

Reputation: 198436

current_hash.values.then { |first, *rest| first.zip(*rest) }.flatten.compact.join

An unfortunate thing with Ruby zip is that the first enumerable needs to be the receiver, and the others need to be parameters. Here, I use then, parameter deconstruction and splat to separate the first enumerable from the rest. flatten gets rid of the column arrays, compact gets rid of the nil (though it's not really necessary as join will ignore it), and join turns the array into the string.


Note that Ruby zip will stop at length of the receiver; so if :a is shorter than the others, you will likely have a surprising result. If that is a concern, please update with an example that reflects that scenario, and the desired outcome.

Upvotes: 2

Related Questions