Sum previous value in hash - ruby-on-rails

Basically I´ve got a hash and I would like to sum the current value with the previous.
i.e
what I have
hash = {:a=>5, :b=>10, :c=>15, :d=>3}
The result that I want
{:a=>5, :b=>15, :c=>30, :d=>33}

hash.inject(0) { |s, (k, v)| hash[k] = s + v }
# => 33
hash
# => {:a=>5, :b=>15, :c=>30, :d=>33}
If you want to preserve the original hash, you can use each_with_object instead:
hash.each_with_object({}) { |(k, v), h| h[k] = v + (h.values.last||0) }
# => {:a=>5, :b=>15, :c=>30, :d=>33}

The following will return a new hash instance:
hash.each_with_object({}) { |(key, val), new_hash| new_hash[key] = val + (new_hash.values.last||0) }

Related

Hash and frequency of a value

I have the following hash in Ruby :
{
0 => {
:method=> "POST",
:path=> "/api/customer/191023",
:host=> "host.8",
:duration=> "1221"
},
1 => {
:method=> "GET",
:path=> "/api/customer/191023",
:host=> "host.8",
:duration=> "99"
},
2 => {
:method=> "POST",
:path=> "/api/customer/191023",
:host=> "host.10",
:duration=> "142"
},
3 => {
:method=> "POST",
:path=> "/api/customer/191023",
:host=> "host.8",
:duration=> "243"
}
4 => {
:method=> "POST",
:path=> "/api/customer/191023",
:host=> "host.10",
:duration=> "132"
}
}
I would like to do a simple search within these hashes to find the host with the highest frequency. For example, in the previous example, I should get host.8.
Thank you for your help,
M.
To find host value with highest frequency do:
hs = hash.values.group_by { |h| h[:host] =~ /host\.(\d+)/ && $1.to_i || 0 }.to_a
hs.reduce([-1,0]) { |sum,v| v[1].size > sum[1] && [ v[0], v[1].size ] || sum }.first
Description: [-1,0] is the default value for set for #reduce method, where -1 is a number (like in host.number), and 0 is a count of the number. So, when reduce encounters the number with size more than of passed sum, it replaces with the new value on next iteration.
Here's one way to do that.
Code
def max_host(hash)
hash.each_with_object(Hash.new(0)) { |(_,v),h| h[v[:host]] += 1 }
.max_by { |_,v| v }
.first
end
Example
Let's take the simplified example below. Note that I've changed, for example, :host = \"host.10\" to :host = "host.10", as the former is not a correct syntax. You could write the string as '\"host.10\" (=> "\\\"host.10\\\""), but I assume you simply want "host.10". The code is the same for both.
hash = {
0 => {
:method=>"POST",
:host =>"host.8"
},
1 => {
:method=>"GET",
:host =>"host.10"
},
2 => {
:method=>"POST",
:host =>"host.10"
}
}
max_host(hash)
#=> "host.10"
Explanation
For the example hash above,
enum = hash.each_with_object(Hash.new(0))
#=> #<Enumerator: {
# 0=>{:method=>"POST", :host=>"host.8"},
# 1=>{:method=>"GET", :host=>"host.10"},
# 2=>{:method=>"POST", :host=>"host.10"}}:each_with_object({})>
The enumerator will invoke the method Hash#each to pass each element of the enumerator into the block. We can see what those elements are by converting the enumerator to an array:
enum.to_a
#=> [[[0, {:method=>"POST", :host=>"host.8"}], {}],
# [[1, {:method=>"GET", :host=>"host.10"}], {}],
# [[2, {:method=>"POST", :host=>"host.10"}], {}]]
The empty hash shown in the first element is the initial value of the hash created by
Hash.new(0)
This creates a hash h with a default value of zero. By doing it this way, if h does not have a key k, h[k] will return the default value (0), but (important!) this does not change the hash.
The first value passed into the block is
[[0, {:method=>"POST", :host=>"host.8"}], {}]
This is then decomposed (or "disambiguated") into individual objects that are assigned to three block variables:
k => 0
v => {:method=>"POST", :host=>"host.8"}
h => Hash.new(0)
We then execute:
h[v[:host]] += 1
which is
h["host.8"] += 1
which is shorthand for
h["host.8"] = h["host.8"] + 1
[Aside: you may have noticed that in the code I show the block variables as |(_,v),h|, whereas above I refer to them above as |(k,v),h|. I could have used the latter, but since k is not reference in the block, I've chosen to replace it with a "placeholder" _. This ensures k won't be referenced and also tells any readers that I'm not using what would be the first block variable.]
As h does not have a key "host.8", h["host.8"] to the right of = returns the default value:
h["host.8"] = 0 + 1
#=> 1
so now
h #=> {"host.8"=>1}
The second element passed into the block is
[[1, {:method=>"GET", :host=>"host.10"}], {"host.8"=>1}]
so the block variables become:
v => {:method=>"GET", :host=>"host.10"}
h => {"host.8"=>1}
Notice that the hash h has been updated. We execute
h[v[:host]] += 1
#=> h["host.10"] += 1
#=> h["host.10"] = h["host.10"] + 1
#=> h["host.10"] = 0 + 1
#=> 1
so now
h #=> {"host.8"=>1, "host.10"=>1}
Lastly, the block variables are assigned the values
v = {:method=>"POST", :host=>"host.10"}
h => {"host.8"=>1, "host.10"=>1}
so
h[v[:host]] += 1
#=> h["host.10"] += 1
#=> h["host.10"] = h["host.10"] + 1
#=> h["host.10"] = 1 + 1
#=> 2
h #=> {"host.8"=>1, "host.10"=>2}
and the value of h is returned by the method.

How to calculate deltas between two hashes?

I would like to calculate deltas for my stats. I already tried HashDiff gem to compare hashes.
a = {"Lima, Peru"=>"83", "Chicago, IL"=>"35"}
b = {"Lima, Peru"=>"80", "Chicago, IL"=>"40", "Krakow, Poland" => '3'}
CalculateDelta.new(a,b).execute
b = {"Lima, Peru"=>"-3", "Chicago, IL"=>"5", "Krakow, Poland" => '3'}
or even better
b = {"Lima, Peru"=>["-", "3"], "Chicago, IL"=>["+", "5"], "Krakow, Poland" => ["+", '3']}
I already wrote something like this
class CalculateDeltas < Struct.new(:a, :b)
def calculate
aa = a.to_a
ba = b.to_a
c = aa + ba
c.group_by(&:first).map{|k,v| [k, v.map(&:last).inject(:+)]}
end
end
Something like this?:
class CalculateDelta
attr_reader :source, :target
def initialize(source, target)
#source = source
#target = target
end
def execute
target.each_with_object({}) do |(k, v), result|
result[k] = if source[k]
source_value, v = source[k].to_i, v.to_i
source_value > v ? ['-', "#{source_value - v}"]: ['+', "#{v - source_value}"]
else
['+', v]
end
end
end
end
a = {"Lima, Peru"=>"83", "Chicago, IL"=>"35"}
b = {"Lima, Peru"=>"80", "Chicago, IL"=>"40", "Krakow, Poland" => '3'}
puts CalculateDelta.new(a,b).execute
#=> {"Lima, Peru"=>["-", "3"], "Chicago, IL"=>["+", "5"], "Krakow, Poland"=>["+", "3"]}
Keep it simple:
b.keys.each { |k| b[k] = (b[k].to_i-a[k].to_i).to_s if a.key?(k) }
b #=> {"Lima, Peru"=>"-3", "Chicago, IL"=>"5", "Krakow, Poland" => "3"}
Note that the spec is to mutate b.
A further simplification is possible, but I wouldn't advise it:
b.keys.each { |k| b[k] = (b[k].to_i-a[k].to_i).to_s }
b
I hear someone objecting that a[k] = nil if a does not have the key k. That's true, but nil.to_i => 0. :-)
Hash.new().tap{|h| b.each{|k,v| h[k] = v.to_i}; a.each{|k,v| h[k] -= v.to_i}}
Returns {"Lima, Peru"=>-3, "Chicago, IL"=>5, "Krakow, Poland"=>3}
EDIT
To make this code return only strings in the hash use:
Hash.new().tap{|h| b.each{|k,v| h[k] = v.to_i}; a.each{|k,v| h[k] -= v.to_i}}.
tap{|h| h.each{|k,v| h[k] = h[k].to_s }}
Returns {"Lima, Peru"=>"-3", "Chicago, IL"=>"5", "Krakow, Poland"=>"3"}

Clean way to create a hash with 3 levels of nesting which creates levels if they don't exist

This is what I do now
h = Hash.new { |h1, k1| h1[k1] = Hash.new { |h2, k2| h2[k2] = {} } }
Although it works, it looks kind of ambiguous. Maybe there is a better way to do the same thing?
h = hash.new{ |h,k| h[k] = Hash.new(&h.default_proc) }
Then you can assign in anyway you like,
h[:a][:b][:c][:d] = 3
Reference: ref
You can create one recursively like this.
def create n
return {} if n == 0
Hash.new {|h, k| h[k] = create(n - 1)}
end
h = create 3
h[1][1][1] = 2
p h[1][1][1] # => 2
p h[2][1][2] # => {}
h[2][1][2] = 3
p h # => {1=>{1=>{1=>2}}, 2=>{1=>{2=>3}}}
Your code is correct. You can just classify it:
class NestedHash < Hash
def initialize(depth)
self.default_proc = Proc.new {|h,k| h[k] = NestedHash.new(depth-1)} if depth && depth > 1
end
end
h = NestedHash.new(3)

Rails mapping array of hashes onto single hash

I have an array of hashes like so:
[{"testPARAM1"=>"testVAL1"}, {"testPARAM2"=>"testVAL2"}]
And I'm trying to map this onto single hash like this:
{"testPARAM2"=>"testVAL2", "testPARAM1"=>"testVAL1"}
I have achieved it using
par={}
mitem["params"].each { |h| h.each {|k,v| par[k]=v} }
But I was wondering if it's possible to do this in a more idiomatic way (preferably without using a local variable).
How can I do this?
You could compose Enumerable#reduce and Hash#merge to accomplish what you want.
input = [{"testPARAM1"=>"testVAL1"}, {"testPARAM2"=>"testVAL2"}]
input.reduce({}, :merge)
is {"testPARAM2"=>"testVAL2", "testPARAM1"=>"testVAL1"}
Reducing an array sort of like sticking a method call between each element of it.
For example [1, 2, 3].reduce(0, :+) is like saying 0 + 1 + 2 + 3 and gives 6.
In our case we do something similar, but with the merge function, which merges two hashes.
[{:a => 1}, {:b => 2}, {:c => 3}].reduce({}, :merge)
is {}.merge({:a => 1}.merge({:b => 2}.merge({:c => 3})))
is {:a => 1, :b => 2, :c => 3}
How about:
h = [{"testPARAM1"=>"testVAL1"}, {"testPARAM2"=>"testVAL2"}]
r = h.inject(:merge)
Every answers until now are advising to use Enumerable#reduce (or inject which is an alias) + Hash#merge but beware, while being clean, concise and human readable this solution will be hugely time consuming and have a large memory footprint on large arrays.
I have compiled different solutions and benchmarked them.
Some options
a = [{'a' => {'x' => 1}}, {'b' => {'x' => 2}}]
# to_h
a.to_h { |h| [h.keys.first, h.values.first] }
# each_with_object
a.each_with_object({}) { |x, h| h.store(x.keys.first, x.values.first) }
# each_with_object (nested)
a.each_with_object({}) { |x, h| x.each { |k, v| h.store(k, v) } }
# map.with_object
a.map.with_object({}) { |x, h| h.store(x.keys.first, x.values.first) }
# map.with_object (nested)
a.map.with_object({}) { |x, h| x.each { |k, v| h.store(k, v) } }
# reduce + merge
a.reduce(:merge) # take wayyyyyy to much time on large arrays because Hash#merge creates a new hash on each iteration
# reduce + merge!
a.reduce(:merge!) # will modify a in an unexpected way
Benchmark script
It's important to use bmbm and not bm to avoid differences are due to the cost of memory allocation and garbage collection.
require 'benchmark'
a = (1..50_000).map { |x| { "a#{x}" => { 'x' => x } } }
Benchmark.bmbm do |x|
x.report('to_h:') { a.to_h { |h| [h.keys.first, h.values.first] } }
x.report('each_with_object:') { a.each_with_object({}) { |x, h| h.store(x.keys.first, x.values.first) } }
x.report('each_with_object (nested):') { a.each_with_object({}) { |x, h| x.each { |k, v| h.store(k, v) } } }
x.report('map.with_object:') { a.map.with_object({}) { |x, h| h.store(x.keys.first, x.values.first) } }
x.report('map.with_object (nested):') { a.map.with_object({}) { |x, h| x.each { |k, v| h.store(k, v) } } }
x.report('reduce + merge:') { a.reduce(:merge) }
x.report('reduce + merge!:') { a.reduce(:merge!) }
end
Note: I initially tested with a 1_000_000 items array but as reduce + merge is costing exponentially much time it will take to much time to end.
Benchmark results
50k items array
Rehearsal --------------------------------------------------------------
to_h: 0.031464 0.004003 0.035467 ( 0.035644)
each_with_object: 0.018782 0.003025 0.021807 ( 0.021978)
each_with_object (nested): 0.018848 0.000000 0.018848 ( 0.018973)
map.with_object: 0.022634 0.000000 0.022634 ( 0.022777)
map.with_object (nested): 0.020958 0.000222 0.021180 ( 0.021325)
reduce + merge: 9.409533 0.222870 9.632403 ( 9.713789)
reduce + merge!: 0.008547 0.000000 0.008547 ( 0.008627)
----------------------------------------------------- total: 9.760886sec
user system total real
to_h: 0.019744 0.000000 0.019744 ( 0.019851)
each_with_object: 0.018324 0.000000 0.018324 ( 0.018395)
each_with_object (nested): 0.029053 0.000000 0.029053 ( 0.029251)
map.with_object: 0.021635 0.000000 0.021635 ( 0.021782)
map.with_object (nested): 0.028842 0.000005 0.028847 ( 0.029046)
reduce + merge: 17.331742 6.387505 23.719247 ( 23.925125)
reduce + merge!: 0.008255 0.000395 0.008650 ( 0.008681)
2M items array (excluding reduce + merge)
Rehearsal --------------------------------------------------------------
to_h: 2.036005 0.062571 2.098576 ( 2.116110)
each_with_object: 1.241308 0.023036 1.264344 ( 1.273338)
each_with_object (nested): 1.126841 0.039636 1.166477 ( 1.173382)
map.with_object: 2.208696 0.026286 2.234982 ( 2.252559)
map.with_object (nested): 1.238949 0.023128 1.262077 ( 1.270945)
reduce + merge!: 0.777382 0.013279 0.790661 ( 0.797180)
----------------------------------------------------- total: 8.817117sec
user system total real
to_h: 1.237030 0.000000 1.237030 ( 1.247476)
each_with_object: 1.361288 0.016369 1.377657 ( 1.388984)
each_with_object (nested): 1.765759 0.000000 1.765759 ( 1.776274)
map.with_object: 1.439949 0.029580 1.469529 ( 1.481832)
map.with_object (nested): 2.016688 0.019809 2.036497 ( 2.051029)
reduce + merge!: 0.788528 0.000000 0.788528 ( 0.794186)
Use #inject
hashes = [{"testPARAM1"=>"testVAL1"}, {"testPARAM2"=>"testVAL2"}]
merged = hashes.inject({}) { |aggregate, hash| aggregate.merge hash }
merged # => {"testPARAM1"=>"testVAL1", "testPARAM2"=>"testVAL2"}
Here you can use either inject or reduce from Enumerable class as both of them are aliases of each other so there is no performance benefit to either.
sample = [{"testPARAM1"=>"testVAL1"}, {"testPARAM2"=>"testVAL2"}]
result1 = sample.reduce(:merge)
# {"testPARAM1"=>"testVAL1", "testPARAM2"=>"testVAL2"}
result2 = sample.inject(:merge)
# {"testPARAM1"=>"testVAL1", "testPARAM2"=>"testVAL2"}

Ruby Hash value traversal and changing value is not working

a = {"a" => 100, "b" => 200, "c" => 300}
b = a.map{|k,v| v = v + 10}
is returning an array, i need to change the values of a hash by call by reference
I am expecting the following output
{"a" => 110, "b" => 210, "c" => 310}
Thanks
Here's my non-mutating one-liner :P
Hash[original_hash.map { |k,v| [k, v+10] }]
Gotta love ruby one-liners :)
Maybe you can do something like this:
a.keys.each do |key| a[key] += 10 end
a.each_pair do |x,y| a[x] += 10 end
Reality check:
require "benchmark"
include Benchmark
h0, h1, h2, h3, h4 = (0..4).map { Hash[(0..1000).map{ |i| [i,i] }] }
bm do |x|
x.report("0") { 1000.times { h0.each_key{ |k| h0[k] += 10 } } }
x.report("1") { 1000.times { h1.keys.each{ |k| h1[k] += 10 } } }
x.report("2") { 1000.times { Hash[h2.map { |k,v| [k, v+10] }] } }
x.report("3") { 1000.times { h3.inject({}){ |h,(k,v)| h[k] = v + 10; h } } }
x.report("4") { 1000.times { h4.inject({}){ |h,(k,v)| h.update( k => v + 10) } } }
end
user system total real
0 0.490000 0.000000 0.490000 ( 0.540795)
1 0.490000 0.010000 0.500000 ( 0.545050)
2 1.210000 0.010000 1.220000 ( 1.388739)
3 1.570000 0.010000 1.580000 ( 1.660317)
4 2.460000 0.010000 2.470000 ( 3.057287)
Imperative programming wins.
Dude change the map with each and you are good to go :)
I believe in every Ruby question inject should be presented :D
b = a.inject({}){ |h,(k,v)| h[k] = v + 10; h }
#=> {"a"=>110, "b"=>210, "c"=>310}

Resources