How Is Each_With_Object Supposed to Work

How is each_with_object supposed to work?

each_with_object does not work on immutable objects like integer.

(1..3).each_with_object(0) {|i,sum| sum += i} #=> 0

This is because each_with_object iterates over a collection, passing each element and the given object to the block. It does not update the value of object after each iteration and returns the original given object.

It would work with a hash since changing value of a hash key changes it for original object by itself.

(1..3).each_with_object({:sum => 0}) {|i,hsh| hsh[:sum] += i}
#=> {:sum => 6}

String objects are interesting case. They are mutable so you might expect the following
to return "abc"

("a".."c").each_with_object("") {|i,str| str += i} # => ""

but it does not. This is because str += "a" returns a new object and the original object stays the same. However if we do

("a".."c").each_with_object("") {|i,str| str << i} # => "abc"

it works because str << "a" modifies the original object.

For more info see ruby docs for each_with_object

For your purpose, use inject

(1..3).inject(0) {|sum,i| sum += i} #=> 6
# or
(1..3).inject(:+) #=> 6

.each_with_object ruby explanation?

each_with_object is very literally what it says. It's like each, but with an extra object every time.

So for this:

win_lose.each_with_object(Hash.new(0)) { |word,counts| counts[word] += 1 }

You're calling each, with the object created via Hash.new(0) passed in as well, every time. word is the word you'd get in a normal each, and counts is the "object" referred to be "with_object" (so, the hash).

Important to this shortcut is the Hash.new(0). It means create a new empty hash with 0 as the value for all keys that did not previously exist, which lets you do a counts[word] += 1 even if it wasn't in there before.

At the end, each_with_object returns the "object", so counts is returned, having been modified for every word.

Why does Ruby's each_with_object drop data that was appended with the += operator to the array memo?

memo is local variable, that points to an array object. these loop variables are set at each iteration.

In the first example, you change this array object.

In the second example, you override the local variable with a new array. Because memo += [elem] is just a shorthand for memo = momo + [elem]
The old array stays empty.

Very often you better use inject instead of each_with_object. With inject, the new memo variable is set with the result of the block, so you can use non-destructive functions.

[1,2,3].inject([]) do |memo, elem| 
memo + [elem]
end

Ruby each with object syntatic sugar explanation

Symbol#to_proc returns a Proc instance of arity -1:

:<<.to_proc.arity
#⇒ -1

That said, it will silently accept any number of arguments, then basically call args.first.method(self).to_proc and pass the result of the above as codeblock parameter to the caller. That is why ['a'].reduce('b', :<<) works as you expected: reduce’s first block argument is memo, and

'b'.method(:<<, 'a')

returns "ba" string.

Your code works the same way:

arr = ['a']
arr.each_with_object('b', &:<<)
arr
#⇒ ['ab']

You’ve just expected the wrong receiver for String#<< method. In this case, the receiver is an array element, since it’s passed to the block as the first argument. Look:

arr = ['a', 'b']
arr.each_with_object('z', &:<<)
arr
#⇒ ['az', 'bz']

memo itself stays untouched and returned as a result of call to each_with_object. You might even have it frozen and the code will still work.

That said, there is no way to achieve the functionality you requested with syntactic sugar using Symbol#to_proc and each_with_object.

Accumulator for each_with_object keeps re-initializing

Each iteration gets the same object, so you either need to mutate the object inside the block, or use reduce.

def gcp(dims)
first = dims.shift
dims.reduce((0...first).to_a) do |v, dim|
puts "\nv: #{v}, dim: #{dim}"
p v.product((0...dim).to_a)
end
end

gcp([3,2,4])

Results in:

v: [0, 1, 2], dim: 2
[[0, 0], [0, 1], [1, 0], [1, 1], [2, 0], [2, 1]]

v: [[0, 0], [0, 1], [1, 0], [1, 1], [2, 0], [2, 1]], dim: 4
[[[0, 0], 0], [[0, 0], 1], [[0, 0], 2], [[0, 0], 3], [[0, 1], 0], [[0, 1], 1], [[0, 1], 2], [[0, 1], 3], [[1, 0], 0], [[1, 0], 1], [[1, 0], 2], [[1, 0], 3], [[1, 1], 0], [[1, 1], 1], [[1, 1], 2], [[1, 1], 3], [[2, 0], 0], [[2, 0], 1], [[2, 0], 2], [[2, 0], 3], [[2, 1], 0], [[2, 1], 1], [[2, 1], 2], [[2, 1], 3]]

How should I use each_with_object on Hashes?

Use ():

hash.each_with_object([]) { |(k, v), array| array << k }

Ruby2.3; refactor each_with_object


body = line_item_body_elements.map do |element|
send("fetch_invoice_#{element}#{'_amount' unless element == 'products'}")
end

Optimise large CSV whilst using #group_by & each_with_object

A more direct way to compute the desired hash from csv_data follows. Because it requires a single pass through the array, I expect it will speed things up but have not done a benchmark.

require 'ostruct'

csv_data.each_with_object(Hash.new(0)) do |os,h|
h[os[:charge_id]] += os[:total_amount]
end
#=> {"ch_1G79Pi4Kqv3kyKfABfXoXycx"=>70.0,
# "ch_1G79Xt4Kqv3kyKfAnBz9ZJGJ"=>10.0,
# "ch_1G79Yu4Kqv3kyKfA7CnwoNEo"=>10.0,
# "ch_1G79ZQ4Kqv3kyKfAYZMLs8tW"=>10.0,
# "ch_1G79Ze4Kqv3kyKfAmNbovTjO"=>10.0,
# "ch_1G79Zs4Kqv3kyKfA38s1yVmq"=>10.0,
# "ch_1G79Zy4Kqv3kyKfA99Arn1Lh"=>10.0,
# "ch_1G79b04Kqv3kyKfA8uYHL0DY"=>10.0,
# "ch_1G79bS4Kqv3kyKfAAWxowFGO"=>10.0,
# "ch_1G79dS4Kqv3kyKfADejRhlbZ"=>10.0,
# "ch_1G79gM4Kqv3kyKfA30s5NTAj"=>10.0,
# "ch_1G79hc4Kqv3kyKfAxJWbu8Ny"=>10.0,
# "ch_1G79j64Kqv3kyKfATjAI1JcC"=>10.0,
# "ch_1G79jk4Kqv3kyKfAKYdakMAk"=>10.0,
# "ch_1G79k64Kqv3kyKfAXmpONrNI"=>10.0,
# "ch_1G79le4Kqv3kyKfAJMzltr6U"=>10.0,
# "ch_1G79lu4Kqv3kyKfAdHG5Qw6r"=>10.0}

See the doc for the version of Hash::new that takes an argument called the default value.

If the data is received from a remote source a line at a time one could do the processing on the fly, while receiving the data, by writing something like the following.

CSV.foreach(@file, headers: true).
with_object(Hash.new(0)) do |csv,h|
# <your processing to produce `os`, a line of csv_data>
h[os[:charge_id]] += os[:total_amount]
end

If this could be done it would have to be benchmarked to see it if actually improved performance.

For readers unfamiliar with this form of Hash::new, suppose

h = Hash.new(0)

making h's default value zero. All that means is that if h does not have a key k h[k] returns zero, which I'll write

h[k] #=> 0

Let's add a key-value pair: h[:dog] = 1. Then

h #=> { :dog=>1 }

and

h[:dog] #=> 1

Since h does not have a key :cat

h[:cat] #=> 0

Suppose now we write

h[:dog] += 1

That's the same as

h[:dog] = h[:dog] + 1

which equals

h[:dog] = 1 + 1 #=> 2

Similarly,

h[:cat] += 1

means

h[:cat] = h[:cat] + 1
= 0 + 1
= 1

because h[:cat] on the right (the method Hash#[], as contrasted with the method Hash#[]= on the left) returns zero. At this point

h #=> { :dog=>2, :cat=>1 }

When a hash is defined in this way it is sometimes called a counting hash. It's effectively the same as

h = {}
[1,3,1,2,2].each do |n|
h[n] = 0 unless h.key?(n)
h[n] += 1
end
h #=> {1=>2, 3=>1, 2=>2}

How does `each_with_object(Hash.new([]))` work?

Wow, what a Shakespear’s passions are here in comments. Well, I can reproduce a problem and I would provide an answer.

According to the documentation on Hash#new:

If obj is specified, this single object will be used for all default values.

That said, all the newly created hash elements will share the only instance of array. In other words, according to spec, your hash values in such a case will always be the same by definition. To yield what you originally wanted, simply initialize the hash value every time you need it with new instance of empty Array:

food = ["fruit:orange", "fruit:apple", "fruit:cherry", "veg:pea", "veg:parsley"]
food.each_with_object({}) do |food_item, hash|
category, value = food_item.split(":")
(hash[category] ||= []).push(value)
end
#⇒ {"fruit"=>["orange", "apple", "cherry"], "veg"=>["pea", "parsley"]}

Getting unique values and their count in a ruby 2-d array


@result = @mastertest.inject(Hash.new(0)) { |hash,element|
hash[element] +=1
hash
}


Related Topics



Leave a reply



Submit