Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reduce hash with key, value and index as block parameters

Tags:

ruby

h = { "a" => 1, "b" => 2 }

Is there a way to reduce a hash and have the key, value and index as block parameters?

As a starting point I can iterate over a hash getting key, value and index:

h.each_with_index { |(k,v), i| puts [k,v,i].inspect }

# => ["a", 1, 0]
# => ["b", 2, 1]

However when I add reduce I seem to loose the ability to have the key and value as separate values and instead they are provided as a two element array:

h.each_with_index.reduce([]) { |memo, (kv,i)| puts [kv,i].inspect }

# => [["a", 1], 0]
# => [["b", 2], 1]

This is okay, I can in the block do kv[0] and kv[1], but I'd like something like this:

h.each_with_index.reduce([]) { |memo, (k,v), i| puts [k,v,i].inspect }

I'd like to do this without monkey-patching.

like image 694
Kris Avatar asked Jan 24 '26 10:01

Kris


2 Answers

Maybe something like this?:

h.each_with_index.reduce([]) { |memo, ((k,v), i)| puts [k,v,i].inspect }
#=> ["a", 1, 0]
#=> ["b", 2, 1]
#=> nil

All you need is scoping: ((k,v), i).

Keeping in mind with reduce, we always have to return the object at the end of block. Which is kind of an extra overhead unless last operation isn't on the memo object which returns the object itself.Otherwise it won't return the desired result.

Same thing can be achieved with each_with_index chained with with_object like so:

h.each_with_index.with_object([]) { |((k,v), i), memo| memo << [k,v,i].inspect }
#=> ["a", 1, 0]
#=> ["b", 2, 1]
#=> []

See the array at last line of output? That's our memo object, which isn't same as reduce that we used above.

like image 151
Surya Avatar answered Jan 27 '26 00:01

Surya


When in doubt what the block arguments are, create an instance of an Enumerator and call #next on it:

▶ h = {a: 1, b: 2}
#⇒ {:a=>1, :b=>2}
▶ enum = h.each.with_index.with_object([])
#⇒ #<Enumerator: ...>

▶ enum.next
#⇒ [[[:a, 1], 0], []]

The returned value consists of:

  • array of key and value, joined into:
  • array with an index, joined into:
  • array with an accumulator (for reduce it’d go in front, if reduce returned an enumerator when called without a block—credits to @Stefan for nitpicking.)

Hence, the proper parentheses for decomposing it would be:

#   ⇓ ⇓     ⇓       ⇓
# [ [ [:a, 1],    0 ],    [] ]
{ | ( (k,  v),  idx ),   memo| ...
like image 44
Aleksei Matiushkin Avatar answered Jan 26 '26 23:01

Aleksei Matiushkin



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!