Using inject with an array of hashes - ruby

I have an array of hashes, each with a key lol which has an integer value. I'd like to sum the values, inject always worked but now I get an exception:
array = [{lol: 1}, {lol: 2}]
array.inject {|memo, (key, value)| memo + value} =>
NoMethodError: undefined method `+' for {:lol=>1}:Hash
from (irb):26:in `block in irb_binding'
from (irb):26:in `each'
from (irb):26:in `inject'
from (irb):26
Por que?

You can just get all the hash values with flat_map(&:values), then use inject(:+) to sum them.
[{lol: 1}, {lol: 2}].flat_map(&:values).inject(:+)
The reason your approach doesn't work is that inject is going to yield each hash to the block, rather than each key/value pair of each hash in the array. If you wanted to keep your solution, you'd want something like:
array.map {|hash| hash.inject(0) {|memo, (key, value)| memo + value } }.inject(:+)

From .inject documentation
If you do not explicitly specify an initial value for memo, then the first element of collection is used as the initial value of memo.
array.inject {|memo, (key, value)| memo + value}
The value for memo is a hash instead of a number.
Correct it as
array.inject(0) {|memo, hash| memo + hash[:lol]} # => 3

If you don't specify an argument to inject, the value for the memo object for the first iteration is the first element of the enumerable, an hash in this case. So you just have to pass 0 as the argument to inject:
array = [{lol: 1}, {lol: 2}]
array.inject(0) { |sum, h| sum + h[:lol] }
# => 3

Related

Trouble about iterating over an array to generate frequencies in a hash

I have an array with some different data (in string format) and I would like to count the frequencies of each value and store it in hash/dictonary, but i'm getting error trying to do it.
I would like to do something like this:
words = ["foo", "var", "spam", "egg", "foo", "foo", "egg"]
frequency = {}
words.each{|word| frequency[word] += 1}
and get the following output:
puts(frequency) #{"foo" => 3, "var" => 1, "spam" => 1, "egg" => 2}
but I'm getting the following problem:
prueba.rb:3:in `block in <main>': undefined method `+' for nil:NilClass (NoMethodError)
Is there another way of accomplishing the same result?
If you query the hash for a key not present you get nil, that's the problem. Let's make 0 the default if a key is not present
frequency = Hash.new(0)
When you write:
words.each{|word| frequency[word] += 1}
It's equivalent to:
words.each{|word| frequency[word] = frequency[word] + 1}
However, frequency hash is empty and frequency[word] returns nil -- essentially, you are trying to do nil + 1, which results in the error you are getting.
You can initialize new elements of the hash manually:
words.each{|word| frequency[word] ||= 0; frequency[word] += 1}
Or, as other answers have already suggested, use frequency = Hash.new(0) as a shortcut.
words = ["foo", "var", "spam", "egg", "foo", "foo", "egg"]
words.each_with_object(Hash.new(0)) { |w, h| h[w] += 1 }
#=> {"foo"=>3, "var"=>1, "spam"=>1, "egg"=>2}
#each_with_object is the enumerator which iterates by passing each element with an object supplied (here Hash.new(0)) and returns that supplied object in next iteration.
Hash.new(0) creates a hash where default value of any key is 0.
hash = Hash.new(0)
hash['a'] #=> 0
We use this property of default value and simply pass each word to the object and increment the counter.
:)
Issue in your code: You don't have default value in your case, so you would need to do that manually by checking if a key exists in the hash or not.
You can use this. By initializing it with Hash.new(0)
names = ["foo", "var", "spam", "egg", "foo", "foo", "egg"]
names.inject(Hash.new(0)) { |total, e| total[e] += 1 ;total}
# {"foo"=>3, "var"=>1, "spam"=>1, "egg"=>2}

Modifying an existing hash value by x and returning the hash

I'm trying to increment all values of a hash by a given amount and return the hash. I am expecting:
add_to_value({"a" => 1, "c" => 2,"b"=> 3}, 1)
# => {"a" => 2, "c" => 3,"b"=> 4}
I'm thinking:
def add_to_value(hash, x)
hash.each {|key,value| value + x}
end
This returns:
{"a"=>1, "b"=>3, "c"=>2}
Why is hash sorted alphabetically?
You're super close, without any extra gems needed:
def add_to_value(hash, x)
hash.each {|key,value| hash[key] += x }
end
Just iterate the hash and update each value-by-key. #each returns the object being iterated on, so the result will be the original hash, which has been modified in place.
If you want a copy of the original hash, you can do that pretty easily, too:
def add_to_value(hash, x)
hash.each.with_object({}) {|(key, value), out| out[key] = value + x }
end
That'll define a new empty hash, pass it to the block, where it collects the new values. The new hash is returned from #with_object, and is thus returned out of add_to_value.
You can do the following to increment values:
hash = {}
{"a" => 1, "c" => 2,"b"=> 3}.each {|k,v| hash[k]=v+1}
hash
=>{"a"=>2, "c"=>3, "b"=>4}
And the hash will be sorted as you want.
The problem becomes trivial if you use certain gems, such as y_support. Type in your command line gem install y_support, and enjoy the extended hash iterators:
require 'y_support/core_ext/hash'
h = { "a"=>1, "c"=>3, "b"=>2 }
h.with_values do |v| v + 1 end
#=> {"a"=>2, "c"=>4, "b"=>3}
As for your sorting problem, I was unable to reproduce it.
Of course, a less elegant solution is possible without installing a gem:
h.each_with_object Hash.new do |(k, v), h| h[k] = v + 1 end
The gem also gives you Hash#with_keys (which modifies keys) and Hash#modify (which modifies both keys and values, kind of mapping from hash to hash), and banged versions Hash#with_values!, #with_keys! that modify the hash in place.

What is meant: "Hash.new takes a default value for the hash, which is the value of the hash for a nonexistent key"

I'm currently going through the Ruby on Rails tutorial by Michael Hartl
Not understanding the meaning of this statement found in section 4.4.1:
Hashes, in contrast, are different. While the array constructor
Array.new takes an initial value for the array, Hash.new takes a
default value for the hash, which is the value of the hash for a
nonexistent key:
Could someone help explain what is meant by this? I don't understand what the author is trying to get at regarding how hashes differ from arrays in the context of this section of the book
You can always try out the code in irb or rails console to find out what they mean.
Array.new
# => []
Array.new(7)
# => [nil, nil, nil, nil, nil, nil, nil]
h1 = Hash.new
h1['abc']
# => nil
h2 = Hash.new(7)
h2['abc']
# => 7
Arrays and hashes both have a constructor method that takes a value. What this value is used for is different between the two.
For arrays, the value is used to initialize the array (example taken from mentioned tutorial):
a = Array.new([1, 3, 2])
# `a` is equal to [1, 3, 2]
Unlike arrays, the new constructor for hashes doesn't use its passed arguments to initialize the hash. So, for example, typing h = Hash.new('a', 1) does not initialize the hash with a (key, value) pair of a and 1:
h = Hash.new('a', 1) # NO. Does not give you { 'a' => 1 }!
Instead, passing a value to Hash.new causes the hash to use that value as a default when a non-existent key is passed. Normally, hashes return nil for non-existent keys, but by passing a default value, you can have hashes return the default in those cases:
nilHash = { 'x' => 5 }
nilHash['x'] # Return 5, because the key 'x' exists in nilHash
nilHash['foo'] # Returns nil, because there is no key 'foo' in nilHash
defaultHash = Hash.new(100)
defaultHash['x'] = 5
defaultHash['x'] # Return 5, because the key 'x' exists in defaultHash
defaultHash['foo']
# Returns 100 instead of nil, because you passed 100
# as the default value for non-existent keys for this hash
Begin by reading the docs for the class method Hash#new. You will see there are three forms:
new → new_hash
new(obj) → new_hash
new {|hash, key| block } → new_hash
Creating an Empty Hash
The first form is used to create an empty hash:
h = Hash.new #=> {}
which is more commonly written:
h = {} #=> {}
The other two ways of creating a hash with Hash#new establish a default value for a key/value pair when the hash does not already contain the key.
Hash.new with an argument
You can create a hash with a default value in one of two ways:
Hash.new(<default value>)
or
h = Hash.new # or h = {}
h.default = <default value>
Suppose the default value for the hash were 4; that is:
h = Hash.new(4) #=> {}
h[:pop] = 7 #=> 7
h[:pop] += 1 #=> 8
h[:pop] #=> 8
h #=> {:pop=>8}
h[:chips] #=> 4
h #=> {:pop=>8}
h[:chips] += 1 #=> 5
h #=> {:pop=>8, :chips=>5}
h[:chips] #=> 5
Notice that the default value does not affect the value of :pop. That's because it was created with an assignment:
h[:pop] = 7
h[:chips] by itself merely returns the default value (4); it does not add the key/value pair :chips=>4 to the hash! I repeat: it does not add the key/value pair to the hash. That's important!
h[:chips] += 1
is shorthand for:
h[:chips] = h[:chips] + 1
Since the hash h does not have a key :chips when h[:chips] on the right side of the equals sign is evaluated, it returns the default value of 4, then 1 is added to make it 5 and that value is assigned to h[:chips], which adds the key value pair :chips=>5 to the hash, as seen in following line. The last line merely reports the value for the existing key :chips.
So why would you want to establish a default value? I would venture that the main reason is to be able to initialize it with zero, so you can use:
h[k] += 1
instead of
k[k] = (h.key?(k)) ? h[k] + 1 : 1
or the trick:
h[k] = (h[k] ||= 0) + 1
(which only works when hash values are intended to be non-nil). Incidentally, key? is aka has_key?.
Can we make the default a string instead? Of course:
h = Hash.new('magpie')
h[:bluebird] #=> "magpie"
h #=> {}
h[:bluebird] = h[:bluebird] #=> "magpie"
h #=> {:bluebird=>"magpie"}
h[:redbird] = h[:redbird] #=> "magpie"
h #=> {:bluebird=>"magpie", :redbird=>"magpie"}
h[:bluebird] << "jay" #=> "magpiejay"
h #=> {:bluebird=>"magpiejay", :redbird=>"magpiejay"}
You may be scratching your head over the last line: why did h[:bluebird] << "jay" cause h[:redbird] to change?? Perhaps this will explain what's going on here:
h[:robin] #=> "magpiejay"
h[:robin].object_id #=> 2156227520
h[:bluebird].object_id #=> 2156227520
h[:redbird].object_id #=> 2156227520
h[:robin] merely returns the default value, which we see has been changed from "magpie" to "magpiejay". Now look at the object_id's for the default value and for the values associated with the keys :bluebird and :redbird. As you see, all values are the same object, so if we change one, we change all the the others, including the default value. It is now evident why h[:bluebird] << "jay" changed the default value.
We can clarify this further by adding a stately eagle:
h[:eagle] #=> "magpiejay"
h[:eagle] += "starling" #=> "magpiejaystarling"
h[:eagle].object_id #=> 2157098780
h #=> {:bluebird=>"magpiejay", :redbird=>"magpiejay", :eagle=>"magpiejaystarling"}
Because
h[:eagle] += "starling" #=> "magpiejaystarling"
is equivalent to:
h[:eagle] = h[:eagle] + "starling"
we have created a new object on the right side of the equals sign and assigned it to h[:eagle]. That's why the values for the keys :bluebird and :redbird are unaffected and h[:eagle] has a different object_id.
We have the similar problems if we write: Hash.new([]) or Hash.new({}). If there are ever reasons to use those defaults, I'm not aware of them. It certainly can be very useful for the default value to be an empty string, array or hash, but for that you need the third form of Hash.new, which takes a block.
Hash.new with a block
We now consider the third and final version of Hash#new, which takes a block, like so:
Hash.new { |h,k| ??? }
You may be expecting this to be devilishly complex and subtle, certainly much harder to grasp than the other two forms of the method. If so, you'd be wrong. It's actually quite simple, if you think of it as looking like this:
Hash.new { |h,k| h[k] = ??? }
In other words, Ruby is saying to you, "The hash h doesn't have the key k. What would you like it's value to be? Now consider the following:
h7 = Hash.new { |h,k| h[k]=7 }
hs = Hash.new { |h,k| h[k]='cat' }
ha = Hash.new { |h,k| h[k]=[] }
hh = Hash.new { |h,k| h[k]={} }
h7[:a] += 3 #=> 10
hs[:b] << 'nip' #=> "catnip"
ha[:c] << 4 << 6 #=> [4, 6]
ha[:d] << 7 #=> [7]
ha #=> {:c=>[4, 6], :d=>[7]}
hh[:k].merge({b: 4}) #=> {:b=>4}
hh #=> {}
hh[:k].merge!({b: 4} ) #=> {:b=>4}
hh #=> {:k=>{:b=>4}}
Notice that you cannot write ha = Hash.new { |h,k| [] } (or equivalently, ha = Hash.new { [] }) and expect h[k] => [] to be added to the hash. You can do whatever you like within the block; you are neither required nor limited to specifying a value for the key. In effect, within the block Ruby is actually saying, "A key that is not in the hash has been referenced without a value. I'm giving you that reference and also a reference to the hash. That will allow you to add that key with a value to the hash, if that's what you want to do, but what you do in this block is entirely your business."
The default values for the hashes h7, hs, ha and hh are respectively the number 7 (though it would be easier to simply enter 7 as An argument), an empty string, an empty array or an empty hash. Probably the last two are the most common use of Hash#new with a block, as in:
array = [[:a, 1], [:b, 3], [:a, 4], [:b, 6]]
array.each_with_object(Hash.new {|h,k| h[k] = []}) { |(k,v),h| h[k] << v }
#=> {:a=>[1, 4], :b=>[3, 6]}
That's really about all there is to the last form of Hash#new.

Using Hash.new as an initial value when reducing an array

I have an array like so
[1,1,2,3,3,3,4,5,5]
and I want to count the number of occurrences of each number, which I'm trying to do like so
[1,1,2,3,3,3,4,5,5].reduce(Hash.new(0)) { |hash,number| hash[number] += 1 }
The problem is I get the following error when I try to run it
NoMethodError: undefined method `[]=' for 1:Fixnum
from (irb):6:in `block in irb_binding'
from (irb):6:in `each'
from (irb):6:in `reduce'
from (irb):6
Am I able to set the initial value like this, or am I getting this wrong?
You can use each_with_object for cleaner syntax
[1,1,2,3,3,3,4,5,5].each_with_object(Hash.new(0)) { |number, hash| hash[number] += 1 }
Note that order of arguments is reverse i.e. |number, hash|
You can use reduce, but if you want so, you have to return the hash again:
[1,1,2,3,3,3,4,5,5].reduce(Hash.new(0)) do |hash,number|
hash[number] += 1
hash
end
Without that the value of hash[number] would be returned. The value of a hash assignment is the value itself. The block builds on the value you have returned previously.
Which means, that after the first it would try something like this: 1[1] += 1, which of course does not work, because Fixnums do not implement the method []=.
I like to use reduce with hash.update. It returns the array and I do not need the ; hash part:
[1,1,2,3,3,3,4,5,5].reduce(Hash.new(0)) { |h, n| h.update(n => h[n].next) }
Your question has been answered, but here's another way to skin the cat:
[Edited to adopt #David's suggestion.]
a = [1,1,2,3,3,3,4,5,5]
Hash[a.group_by(&:to_i).map {|g| [g.first, g.last.size]}]
This also works:
a.group_by{|e| e}.inject({}) {|h, (k,v)| h[k] = v.size; h}
and can be improved by adopting #spickermann's use of update (or it's synonym, merge!), to get rid of that irritating ; h at the end:
a.group_by{|e| e}.inject({}) {|h, (k,v)| h.merge!(k => v.size)}
Formerly, I had:
Hash[*a.group_by(&:to_i).to_a.map {|g| [g.first, g.last.size]}.flatten]
I don't like to_i here. I wanted to use to_proc instead of ...group_by {|x| x|} (as used in one of the solutions above) and was looking for a method m that returns the receiver or its value. to_i was the best I could do (e.g., 2.to_i => 2), but only for Integers. Can anyone suggest a method that returns the receiver for a wide range of objects whose use with to_proc makes it's purpose obvious?
That's simple:
[1,1,2,3,3,3,4,5,5].group_by {|e| e}.collect {|k,v| [k,v.count]}
#=> [[1, 2], [2, 1], [3, 3], [4, 1], [5, 2]]
if result required in a Hash object
Hash[[1,1,2,3,3,3,4,5,5].group_by {|e| e}.collect {|k,v| [k,v.count]}]
#=> {1=>2, 2=>1, 3=>3, 4=>1, 5=>2}

How do I pass a lambda to Hash.each?

How can I pass a lambda to hash.each, so I can re-use some code?
> h = { a: 'b' }
> h.each do |key, value| end
=> {:a=>"b"}
> test = lambda do |key, value| puts "#{key} = #{value}" end
> test.call('a','b')
a = b
> h.each &test
ArgumentError: wrong number of arguments (1 for 2)
from (irb):1:in `block in irb_binding'
from (irb):5:in `each'
from (irb):5
from /Users/jstillwell/.rvm/rubies/ruby-1.9.3-p362/bin/irb:16:in `<main>'
> h.each test
ArgumentError: wrong number of arguments(1 for 0)
from (irb):8:in `each'
from (irb):8
from /Users/jstillwell/.rvm/rubies/ruby-1.9.3-p362/bin/irb:16:in `<main>'
each yields the current element to the block, ergo the lambda needs to take one argument, not two. In case of a Hash the element being yielded is a two-element Array with the first element being the key and the second element being the value.
test = lambda do |el| puts "#{el.first} = #{el.last}" end
h.each &test
# a = b
Alternatively, you can use Ruby's support for destructuring bind in parameter lists:
test = lambda do |(k, v)| puts "#{k} = #{v}" end
h.each &test
# a = b
Or, instead of using a lambda which has the same argument binding semantics as a method, you could use a Proc, which has the same argument binding semantics as a block, i.e. it will unpack a single Array argument into multiple parameter bindings:
test = proc do |k, v| puts "#{k} = #{v}" end
h.each &test
# a = b
I still feel this is inconsistent syntax, I found the answer (but no apology) in another question
Inconsistency of arity between Hash.each and lambdas
I switched it to
lambda do |(key, value)|
then I can pass in
hash.each &test
or I can call it directly with
test.call([key, value])
If someone has a better answer, or at least a succinct excuse why this is necessary. I'll gladly give them the points.
I have come across a similar case where I wanted not to print but to return a key-value pair after doing some operation with the value... the above solution applies when you just want to print but it can become tricky when you need to return a Hash of those key-value pairs. so I thought this could be helpful
I used it to solve the caesar-cipher puzzle
hash1 = {"a" => 1, "b" => 2, "c" => 3}
test = lambda {|k,v| true ? {k => v+2} : {k => v+1} }
after mapping this to an hash it will return an array of Hashes (here is where things get interesting)
hash2 = hash1.map(&test) # [{"a"=>3}, {"b"=>4}, {"c"=>5}]
we can convert it to hash rubystically!
hash2.inject(:merge!) # {"a" => 3, "b" => 4, "c" => 5}
Bingo!!! right?

Resources