Subtracting Values in two identical hashes in ruby - ruby

I have two hashes that are identical in structure...
hash1 = {:total=>{:gold=>100, :dark=>500}, :defensive=>{:gold=>100, :dark=>500}}
hash2 = {:total=>{:gold=>20, :dark=>200}, :defensive=>{:gold=>20, :dark=>200}}
I want to subtract and return the following result...
hash1 - hash2 => {:total=>{:gold=>80, :dark=>300}, :defensive=>{:gold=>80, :dark=>300}}
Maybe this type of operation is not recommended. I'd appreciate that feedback as well. :-)

I would just do:
hash1 = {:total=>{:gold=>100, :dark=>500}, :defensive=>{:gold=>100, :dark=>500}}
hash2 = {:total=>{:gold=>20, :dark=>200}, :defensive=>{:gold=>20, :dark=>200}}
hash1.merge(hash2) { |_, l, r| l.merge(r) { |_, x, y| x - y } }
#=> {:total=>{:gold=>80, :dark=>300}, :defensive=>{:gold=>80, :dark=>300}}

You could use recursion:
def diff(f,g)
f.each_with_object({}) do |(k,v),h|
h[k] =
case v
when Fixnum then v-g[k]
else diff v,g[k]
end
end
end
diff hash1, hash2
#=> {:total=> {:gold=>80, :dark=>300},
# :defensive=>{:gold=>80, :dark=>300}}
#under_gongor pointed out that this works for parallel, nested hashes. Here's an example:
hash1 = {:total=>{:gold=>350, :dark=>500},
:defensive=>{:next=>{:gold=>300, :dark=>500},
:last=>{:gold=>150, :dark=>300}}}
hash2 = {:total=>{:gold=>300, :dark=>100},
:defensive=>{:next=>{:gold=>100, :dark=>200},
:last=>{:gold=>100, :dark=>200}}}
diff hash1, hash2
#=> {:total=>{:gold=> 50, :dark=>400},
# :defensive=>{:next=>{:gold=>200, :dark=>300},
# :last=>{:gold=> 50, :dark=>100}}}

hash1 = {:total=>{:gold=>100, :dark=>500},
:defensive=>{:gold=>100, :dark=>500}}
hash2 = {:total=>{:gold=>20, :dark=>200},
:defensive=>{:gold=>20, :dark=>200}}
{}.tap do |hash|
hash1.each do |key, subhash1|
subhash2 = hash2[key]
hash[key] ||= {}
subhash1.each do |k, val1|
val2 = subhash2[k]
hash[key][k] = val1 - val2
end
end
end
Output is:
{:total=>{:gold=>80, :dark=>300},
:defensive=>{:gold=>80, :dark=>300}}

Here's another approach. It may not be preferred for the present problem, but it illustrates a general technique that is sometimes useful.
Step 1: Extract the inner and outer keys:
okeys, ikeys = hash1.keys, hash1.values.first.keys
#=> [[:total, :defensive], [:gold, :dark]]
Step 2: Extract the numerical values and compute differences
a = [hash1,hash2].
map { |h| h.values.map { |g| g.values_at(*ikeys) } }.
transpose.
map(&:transpose).
map { |a| a.reduce(:-) }
#=> [[100, 20], [100, 20]]
Step 3: Construct the output hash
okeys.zip(a.map { |b| ikeys.zip(b).to_h }).to_h
#=> {:total=>{:gold=>100, :dark=>20}, :defensive=>{:gold=>100, :dark=>20}}
One could combine Steps 2 and 3 by substituting out a in Step 3. ikeys and okeys could also be substituted out, to make it a one-liner, but I would not advocate that.
Explanation for Step 2
Step 2 may appear a bit complex, but it's really not if you go through the operations one at a time:
Remove the numerical values, using Hash#values_at to ensure correct ordering:
b = [hash1,hash2].map { |h| h.values.map { |g| g.values_at(*ikeys) } }
#=> [[[100, 500], [100, 500]], [[20, 200], [20, 200]]]
Manipulate the array until it is in the proper form for calculating differences:
c = b.transpose
#=> [[[100, 500], [20, 200]], [[100, 500], [20, 200]]]
d = c.map(&:transpose)
#=> [[[100, 20], [500, 200]], [[100, 20], [500, 200]]]
Compute differences:
a = d.map { |a| a.reduce(:-) }
#=> [[100, 20], [100, 20]]

Related

Sum of same values in ruby hash

everybody.
I have hash for example
{-2=>"a", -1=>"c", 1=>"a", 3=>"a", 49=>"a", -43=>"ab", 5=>"ab"}
There can be equal values. My task is to sum keys where values are equal. Result:
{51=>"a", -1=>"c", -38=>"ab"}
How can I do this?
hash.group_by{|key,val| val}
Gives awful result.
hash = {-2=>"a", -1=>"c", 1=>"a", 3=>"a", 49=>"a", -43=>"ab", 5=>"ab"}
hash.reduce({}) do |memo, (k,v)|
memo[v] ||= 0
memo[v] += k
memo
end.invert
# => {51=>"a", -1=>"c", -38=>"ab"}
reduce - lets you build up a new value by iterating over the values of a collection, in this case hash. See the docs for more.
invert - swaps the keys and values of a hash. See the docs for more.
Other ways to do this:
hash.reduce(Hash.new(0)) { |memo, (k,v)| memo[v] += k; memo }.invert
h = {-2=>"a", -1=>"c", 1=>"a", 3=>"a", 49=>"a", -43=>"ab", 5=>"ab"}
then
h.group_by(&:last).each_with_object({}) { |(k,v),h| h[v.map(&:first).sum] = k }
#=> {51=>"a", -1=>"c", -38=>"ab"}
but that would be crazy as it relies on the sums being unique. (Recall that hashes have unique keys.) Suppose
h = {-54=>"a", -1=>"c", 1=>"a", 3=>"a", 49=>"a", -43=>"ab", 5=>"ab"}
then
h.group_by(&:last).each_with_object({}) { |(k,v),h| h[v.map(&:first).sum] = k }
#=> {-1=>"c", -38=>"ab"}
as -1=>"a" is overwritten by -1=<"c". I doubt that this is wanted.
It would be better to save the contents of h in an array:
a = [[-2, "a"], [-1, "c"], [-1, "a"], [49, "a"], [-43, "ab"], [5, "ab"]]
(as it permits duplicate values of the integers--here -1) and then compute
a.group_by(&:last).each_with_object({}) { |(e,ar),h| h[e] = ar.map(&:first).sum }
#=> {"a"=>46, "c"=>-1, "ab"=>-38}
Note that (for the original value of h)
h.group_by(&:last)
#=> {"a"=>[[-2, "a"], [1, "a"], [3, "a"], [49, "a"]],
# "c"=>[[-1, "c"]], "ab"=>[[-43, "ab"], [5, "ab"]]}
and v.map(&:first).sum could be replaced with
v.reduce(0) { |t,(n,_)| t+n }

How to merge hash of hashes and set default value if value don't exists

I need to merge values of hash a into out with sort keys in a.
a = {"X"=>{12=>1, 11=>4}, "Y"=>{11=>5}, "Z"=>{12=>5}}
out = [
{"X": [4, 1]},
{"Y": [5, 0]},
{"Z": [0, 5]},
]
I would do something like this:
a = {"X"=>{12=>1, 11=>4}, "Y"=>{11=>5}, "Z"=>{12=>5}}
sorted_keys = a.values.flat_map(&:keys).uniq.sort
#=> [11, 12]
a.map { |k, v| { k => v.values_at(*sorted_keys).map(&:to_i) } }
#=> [ { "X" => [4, 1] }, { "Y" => [5, 0] }, { "Z" => [0, 5] }]
Code
def modify_values(g)
sorted_keys = g.reduce([]) {|arr,(_,v)| arr | v.keys}.sort
g.each_with_object({}) {|(k,v),h| h[k] = Hash.new(0).merge(v).values_at(*sorted_keys)}
end
Example
g = {"X"=>{12=>1, 11=>4}, "Y"=>{11=>5}, "Z"=>{12=>5}}
modify_values(g)
#=> {"X"=>[4, 1], "Y"=>[5, 0], "Z"=>[0, 5]}
Explanation
The steps are as follows (for the hash a in the example). First obtain an array of the unique keys from g's values (see Enumerable#reduce and Array#|), then sort that array.
b = a.reduce([]) {|arr,(_,v)| arr | v.keys}
#=> [12, 11]
sorted_keys = b.sort
#=> [11, 12]
The first key-value pair of a, together with an empty hash, is passed to each_with_object's block. The block variables are computed using parallel assignment:
(k,v),h = [["X", {12=>1, 11=>4}], {}]
k #=> "X"
v #=> {12=>1, 11=>4}
h #=> {}
The block calculation is then performed. First an empty hash with a default value 0 is created:
f = Hash.new(0)
#=> {}
The hash v is then merged into f. The result is hash with the same key-value pairs as v but with a default value of 0. The significance of the default value is that if f does not have a key k, f[k] returns the default value. See Hash::new.
g = f.merge(v)
#=> {12=>1, 11=>4}
g.default
#=> 0 (yup)
Then extract the values corresponding to sorted_keys:
h[k] = g.values_at(*sorted_keys)
#=> {12=>1, 11=>4}.values_at(11, 12)
#=> [4, 1]
When a's next key-value pair is passed to the block, the calculations are as follows.
(k,v),h = [["Y", {11=>5}], {"X"=>[4, 1]}] # Note `h` has been updated
k #=> "Y"
v #=> {11=>5}
h #=> {"X"=>[4, 1]}
f = Hash.new(0)
#=> {}
g = f.merge(v)
#=> {11=>5}
h[k] = g.values_at(*sorted_keys)
#=> {11=>5}.values_at(11, 12)
#=> [5, 0] (Note h[12] equals h's default value)
and now
h #=> {"X"=>[4, 1], "Y"=>[5, 0]}
The calculation for the third key-value pair of a is similar.

Ruby read a file, create a hash with a key and data, then sort alphabetically

I have a file like below, which I need to put into a hash:
GHIThree, Line, Number
DEFNumber, Two, Line
ABCLine, Number, One
I need to do is take the first 3 characters and turn that into a key and then the rest of the line into the value.
So when I print the hash it should look something like this:
Keys Values
ABC Line, Number, One
DEF Number, Two, Line
GHI Three, Line, Number
Here is what I've got, its a little all over the place but here it is:
lines = File.open("homework02.txt").read.split
fHash = {}
lines.each do |line|
next if line == ""
fHash[line[0..2]] = line[3..-1]
end
f = File.open("homework02.txt")
fHash = {}
loop do
x = f.gets
break unless x
fHash[x[0..2]] = x[3..-1]
end
puts fHash
f.close
fHash = fHash.to_a.sort.to_h
Try in this way:
result = {}
CSV.foreach('file.csv', skip_blanks: true) do |row|
result[row[0].slice!(0..2)] = row
end
result.sort.to_h
Another way:
fHash = { 4=>1, 1=>1, 3=>2 }
fHash.keys.sort.each_with_object({}) { |k,h| h[k]=fHash[k] }
#=> {1=>1, 3=>2, 4=>1}
The steps:
a = fHash.keys
#=> [4, 1, 3]
b = a.sort
#=> [1, 3, 4]
enum = b.each_with_object({})
#=> #<Enumerator: [1, 3, 4]:each_with_object({})>
We can view the contents of this enumerator by converting it to an array:
enum.to_a
#=> [[1, {}], [3, {}], [4, {}]]
You will see that the value of the hash will change as elements of enum are passed to the block.
The block variables k and h are assigned to the first element of enum:
k,h = enum.next
#=> [1, {}]
k #=> 1
h #=> {}
and the block calculation is performed:
h[k]=fHash[k]
#=> h[1] = { 4=>1, 1=>1, 3=>2 }[1]
# h[1] = 1
h #=> {1=>1}
The second element of enum is passed to the block and the operations are repeated:
k,h = enum.next
#=> [3, {1=>1}]
h[k]=fHash[k]
#=> h[3] = { 4=>1, 1=>1, 3=>2 }[3]
# h[3] = 2
h #=> {1=>1, 3=>2}
The third and last element of enum is passed to the block:
k,h = enum.next
#=> [4, {1=>1, 3=>2}]
h[k]=fHash[k]
#=> h[4] = { 4=>1, 1=>1, 3=>2 }[4]
# h[4] = 1
h #=> {1=>1, 3=>2, 4=>1}

Using hash to tell positive, odd, even, and negative numbers

I have an array:
ak = [10, 20, 3, 4, 5, -5, 28, 27]
I want a solution like this:
#even:4
#odd:3
#positive:7
#negative:1
How do I use hash to do that?
You could do this in a fairly general (reusable) way as follows.
Code
def analyze_array(ak, ops)
ops.each_with_object({}) { |(k,m),h| h.update(k=>ak.count(&m)) }
end
Example
ak = [10, 20, 3, 4, 5, -5, 28, 27]
ops = [[:even, :even? ],
[:odd, :odd? ],
[:positive, ->(n) { n>0 }],
[:negative, ->(n) { n<0 }]]
analyze_array(ak, ops)
#=> {:even=>4, :odd=>4, :positive=>7, :negative=>1}
Explanation
For the example above:
enum = ops.each_with_object({})
#=> #<Enumerator: [[:even, :even?], [:odd, :odd?],
# [:positive, #<Proc:0x007fe90395aaf8#(irb):9 (lambda)>],
# [:negative, #<Proc:0x007fe90395aaa8#(irb):10 (lambda)>]]
# :each_with_object({})>
Note that :even? and :odd? are symbols (not to be confused with methods).
The elements of enum are passed into the block by Enumerator#each, which calls Array#each. We can see what the elements of enum are by converting it to an array:
enum.to_a
#=> [[[:even, :even?], {}], [[:odd, :odd?], {}],
# [[:positive, #<Proc:0x007fe90395aaf8#(irb):9 (lambda)>], {}],
# [[:negative, #<Proc:0x007fe90395aaa8#(irb):10 (lambda)>], {}]]
and simulate the passing of the (4) elements of enum into the block with Enumerator#next. The first element of enum ([[:even, :even?], {}]) is passed to the block and assigned to the block variables:
(k,m),h = enum.next
#=> [[:even, :even?], {}]
k #=> :even
m #=> :even?
h #=> {}
Next, we use Hash#update (aka merge!) to merge a one-key hash into h and return the new value of h:
h.update(k=>ak.count(&m))
#=> {}.update(:even=>[10, 20, 3, 4, 5, -5, 28, 27].count(&:even?))
#=> {:even=>4}
(Ruby allows us to write (k=>ak.count(&m)) as shorthand for ({k=>ak.count(&m)})).
As usual, & invokes Symbol#to_proc to convert the symbol :even? to a proc and then converts the proc to a block for count.
The next value of enum is then passed to the block ("odd"), similar calculations are performed and the hash { :odd=>4 } is merged into h, resulting in:
h #=> { :even=>4, :odd=>4 }
The third and fourth values of enum are then passed to the block. The only difference is that m in ak.count(&m) is already a proc (a lambda, actually), so & just converts it to a block for count.
h = Hash.new
h["even"] = ak.select {|x| x.even? && x > 0}.count
h["odd"] = ak.select {|x| x.odd? && x > 0}.count
h["positive"] = ak.select {|x| x > 0}.count
h["negative"] = ak.select {|x| x < 0}.count
and add
put h
Another solution:
ak = [10, 20, 3, 4, 5, -5, 28, 27]
akp = ak.select{ |n| n > 0 }
h = {
even: akp.count(&:even?),
odd: akp.count(&:odd?),
positive: akp.count,
negative: ak.count{ |n| n < 0 }
}
puts ak, h
Assuming (based on the output that you expect) that you only want the positive even or odd numbers:
h = Hash.new
h["even"] = ak.select {|x| x.even? && x > 0}.count
h["odd"] = ak.select {|x| x.odd? && x > 0}.count
h["positive"] = ak.select {|x| x > 0}.count
h["negative"] = ak.select {|x| x < 0}.count
puts h
You can iterate over you array and test each value like this:
def evaluate(array)
response = { even: 0, odd: 0, positive: 0, negative: 0 }
array.each do |item|
response[:even] += 1 if item.even?
response[:odd] += 1 if item.odd?
...
end
response
end
Or something like that. You can optimize it after.
def calculate(arr)
even = arr.select {|e| e.even?}.size
odd = arr.select {|e| e.odd?}.size
pos = arr.select {|e| e >= 0}.size
neg = arr.select {|e| e < 0}.size
hash = {even: even, odd: odd, positive: pos, negative:neg}
end

Grouping an array on the basis of its first element, without duplication in Ruby

I'm executing an active record command Product.pluck(:category_id, :price), which returns an array of 2 element arrays:
[
[1, 500],
[1, 100],
[2, 300]
]
I want to group on the basis of the first element, creating a hash that looks like:
{1 => [500, 100], 2 => [300]}
group_by seems logical, but replicates the entire array. I.e. a.group_by(&:first) produces:
{1=>[[1, 500], [1, 100]], 2=>[[2, 300]]}
You can do a secondary transform to it:
Hash[
array.group_by(&:first).collect do |key, values|
[ key, values.collect { |v| v[1] } ]
end
]
Alternatively just map out the logic directly:
array.each_with_object({ }) do |item, result|
(result[item[0]] ||= [ ]) << item[1]
end
This one-liner seemed to work for me.
array.group_by(&:first).map { |k, v| [k, v.each(&:shift)] }.to_h
Since you're grouping by the first element, just remove it with shift and turn the result into a hash:
array.group_by(&:first).map do |key, value|
value = value.flat_map { |x| x.shift; x }
[key, value]
end #=> {1=>[500, 100], 2=>[300]}
I do not like the destructive operation.
array.group_by(&:first).map { |id, a| [id, a.map(&:last)] }.to_h
Used this functionality several times in my app, added extension to an array:
# config/initializers/array_ext.rb
class Array
# given an array of two-element arrays groups second element by first element, eg:
# [[1, 2], [1, 3], [2, 4]].group_second_by_first #=> {1 => [2, 3], 2 => [4]}
def group_second_by_first
each_with_object({}) { |(first, second), h| (h[first] ||= []) << second }
end
end

Resources