Anand
Anand

Reputation: 3760

How can I chain with_index and with_object on an enumerable in ruby?

I want to process the array ['a', 'b', 'c'] to return the string '0a1b2c' (i.e, string formed by concatenating each index with its value).

I can do this:

result = ''
['a', 'b', 'c'].each.with_index do |char, i|
  result += "#{i}#{char}"
end  
result

I want to eliminate the result variable outside the block by using with_object.

Something like this:

['a', 'b', 'c'].each.with_index.with_object('') do |char, i, result|
  result += "#{i}#{char}"
end

But this raises an error undefined method '+' for nil:NilClass

Upvotes: 2

Views: 1016

Answers (4)

guillaumesrl
guillaumesrl

Reputation: 1

Something important about this solution, which is nice :

%w|a b c|.each_with_object('').with_index do |(char, result), i|
  result << "#{i}#{char}"
end

Here we use << instead of +=.

with_object only works with mutable objects, and you might think that string is mutable, so why wouldn't we be able to use +=? Because += is equivalent to x = x+y, so it generates a new object each time.

In the end, if you use += with with_object, you never mutate the original object you created, you just create a new object you won't keep track of in the block at each iteration.

More details here: How is each_with_object supposed to work?

Upvotes: 0

Eric Duminil
Eric Duminil

Reputation: 54223

It doesn't use the methods you ask for, but it does the job and is relatively compact :

array = ['a', 'b', 'c']
(0...array.size).zip(array).join
#=> "0a1b2c"

Upvotes: 2

akuhn
akuhn

Reputation: 27793

Try this

arr.each.with_index.with_object('') { |(each, n), obj| ... }

How does this work?

  • Applying both with_index and with_object creates nested tuples
  • (each, n), obj unpacks both tuples

Fun fact—or maybe rather sad fact—the nested tuple actually materialized as a short-lived array so this will create O(n) arrays. If this is a critical production codepath I would away nesting these two enumeration functions. Since you most likely are going to assign obj to a variable in the outer scope anyway it would make most sense to rewrite this as

obj = ''
arr.each_with_index { |each, n| ... }

Upvotes: 3

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121000

Both operations should be done without abusing each iterator:

%w|a b c|.map.with_index do |char, i|
  "#{i}#{char}"
end.join

%w|a b c|.each_with_object('').with_index do |(char, result), i|
  result << "#{i}#{char}"
end

Or, if you still want to use each:

%w|a b c|.each.with_index.with_object('') do |char_idx, result|
  result << char_idx.join
end

Upvotes: 1

Related Questions