2

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

Anand
  • 3,690
  • 4
  • 33
  • 64
  • 1
    Try this: `enum = ['a', 'b', 'c'].each.with_index.with_object('') #=> #:with_index>:with_object("")>`. The elements generated by this enumerator and passed to the block will be `enum.to_a #=> [[["a", 0], ""], [["b", 1], ""], [["c", 2], ""]]` This tells you how the block variables need to be written: `(char, i), result = enum.next #=> [["a", 0], ""]`, so `char #=> "a"; i #=> 0; result #=> ""`. Note `result` will change in the course of the calculations. – Cary Swoveland Jan 14 '17 at 07:54

4 Answers4

2

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| ... }
akuhn
  • 27,477
  • 2
  • 76
  • 91
  • Actually, they should not be nested. Updated with performance discussion. – akuhn Jan 14 '17 at 06:19
  • What does `Benchmark` say? Sometimes things in Ruby are counter-intuitively fast. – tadman Jan 14 '17 at 06:22
  • Short-lived objects are a common main performance issue in Ruby applications, it is hard to measure this with `benchmark` though since the cost of object creation occurs in garbage collection. – akuhn Jan 17 '17 at 07:30
  • It's not hard to measure it, you just need to run it for more than a trivial number of cycles. General rule of thumb is if you're not getting 10s-60s runs for each of your iterations you're not doing enough cycles to collect meaningful data. You're right that the garbage collector can be the biggest price to pay, which is why you really have to lay into it when testing. – tadman Jan 17 '17 at 08:16
2

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"
Eric Duminil
  • 52,989
  • 9
  • 71
  • 124
1

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
Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
0

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?

hschne
  • 704
  • 5
  • 21
  • Hello and welcome to SO! Please have a look at [how to format your posts](https://stackoverflow.com/editing-help) (specifically in regards to code formatting) to improve the readability of future posts. – hschne Nov 16 '20 at 15:44
  • Done & absolutely sorry about that! – guillaumesrl Nov 16 '20 at 19:28