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
Differencebetween 'Try' and '&.' (Safe Navigation Operator) in Ruby
Reading and Updating Yaml File by Ruby Code
Redirect User After Log in Only If It's on Root_Path
Ruby-Debug19 on Ruby-1.9.3-Preview1
What's the Difference Between Rspec's Subject and Let? When Should They Be Used or Not
Insecure World Writable Dir /Users/Username in Path, Mode 040777 When Running Ruby Commands
Ruby: What Is the Easiest Method to Update Hash Values
How to Set Private Instance Variable Used Within a Method Test
Loaderror Running Mongrel with Rails3 and Ruby 1.9.2
Trying to Install Ruby-Filemagic on Snow Leopard Using Brew Rather Than Ports
Zlib in Ruby to Uncompress .Gz