Add previous value to each hash value - ruby

I have a hash with integer values:
h = {
a: 1,
b: 1,
c: 1,
d: 2,
e: 2,
}
I need to add 100 to the first value, and for the second value and on, I need to add the preceding value to the original value to get:
{
a: 101,
b: 102,
c: 103,
d: 105,
e: 107,
}
Is there a good way to achieve this?

You could use inject to calculate the total sum:
h = { a: 1, b: 1, c: 1, d: 2, e: 2}
h.inject(100) { |s, (k, v)| s + v }
#=> 107
And while doing so, you can also set the hash values to get an accumulated sum:
h.inject(100) { |s, (k, v)| h[k] = s + v }
h #=> {:a=>101, :b=>102, :c=>103, :d=>105, :e=>107}

Immutable solution that does not modify the input:
h.each_with_object({sum: 100, outcome: {}}) do |(k, v), acc|
acc[:outcome][k] = acc[:sum] += v
end
#⇒ {:sum=>107, :outcome=>{:a=>101, :b=>102, :c=>103, :d=>105, :e=>107}}

You can just keep track of the sum as an external variable:
sum = 100
h.transform_values{|v| sum += v} # => {:a=>101, :b=>102, :c=>103, :d=>105, :e=>107}

Maybe this is not the most efficient solution, but it is definitely nice and readable.
accumulated_sum = 0
h.each do |key, value|
accumulated_sum += value
hash[key] = 100 + accumulated_sum
end

Related

Hash tree depth Ruby

I'm supposed to write a method that takes a nested hash as input and returns that hash with added "depth" keys. So, for example, the following input:
tree = {
a: 1,
b: 2,
c: { d: { e: 3 } }
}
would yield the following return value:
{
a: 1,
b: 2,
c: {
d: {
e: 3,
depth: 2
},
depth: 1
},
depth: 0
}
If the input is not a hash, then the function should return nil.
This is what I came up with:
def depth(hash)
num = 0
hash.each do |key, value|
if value.class == Hash
num += 1
v[:depth] = num
value.each do |k, v|
if v.class == Hash
num += 1
v[:depth] = num
v.each do |ky, val|
if val.class == Hash
num += 1
v[:depth] = num
val.each do |ke, vl|
if vl.class == Hash
num += 1
v[:depth] = num
end
end
end
end
end
end
end
num = 0
end
end
but it's limited to hash depth of 4, and I can't just keep making the method bigger.
Try this.
def depth(h, i=0)
h.each_with_object(depth: i) { |(k,v),g| g[k] = v.is_a?(Hash) ? depth(v, i+1) : v }
end
depth { a: 1, b: 2, c: { d: { e: 3 } }
#=> {:depth=>0, :a=>1, :b=>2, :c=>{:depth=>1, :d=>{:depth=>2, :e=>3}}}

Iterate over an array and initialize multiple variables in one line in Ruby

I am trying to iterate over an array and count the number of positive, negative and zeros in an array. Right now I am doing it like this
arr = [1, -1, 0, 2, 3, -2, -5]
pos = arr.select { |i| i > 0 }.count
neg = arr.select { |i| i < 0 }.count
zero = arr.select { |i| i == 0 }.count
puts pos
puts neg
puts zero
But is there any way where I can do this in one line? Something like this?
pos, neg, zero = arr.select { |i| i > 0; i < 0; i == 0; }.count
Use inject and the <=> operator:
neg, zero, pos = arr.inject([0,0,0]) { |a,b| a[(b<=>0)+1] += 1; a }
Alternatively, as #HolgerJust mentioned:
neg, zero, pos = arr.each_with_object([0,0,0]) { |a,b| b[(a<=>0)+1] += 1 }
is slightly longer but doesn't have the extra ; a in the block.
Inspired by #steenslag's use of tally:
neg, zero, pos = arr.map { |x| x<=>0 }.tally.values_at(-1,0,1)
If you use a counting hash the code is short and the results are returned in a hash, which may be convenient.
arr = [1, -1, 0, 2, 3, -2, -5, 4]
You could write
arr.each_with_object(Hash.new(0)) { |n,h| h[n<=>0] += 1 }
#=> {1=>4, -1=>3, 0=>1}
or perhaps you would prefer
labels = { -1=>:neg, 0=>:zero, 1=>:pos }
arr.each_with_object(Hash.new(0)) { |n,h| h[labels[n<=>0]] += 1 }
#=> {:pos=>4, :neg=>3, :zero=>1}
the last line of which could alternatively be written
arr.each_with_object({}) { |n,h| h[labels[n<=>0]] = (h[labels[n<=>0]] ||= 0) + 1 }
See Hash::new, specifically the (second) form that takes an argument called the default value (here zero), and no block. If a hash is defined h = Hash.new(0), then if h has no key k, h[k] returns 0 (and h is not changed).
arr = [1, -1, 0, 2, 3, -2, -5]
neg, zero, pos = arr.map{|n| n <=> 0}.tally.values_at(-1, 0, 1)
Using the new tally method.
As others have already said, you should just use inject and count using the <=> operator. If you plan to use similar logic frequently, you could monkey patch a #tally_by method into Enumerable like so:
class Enumerable
def my_tally(*keys, &proc)
proc ||= -> e {e} # Default identity proc
h = keys.empty? ? Hash.new(0) : Hash[keys.map{|k|[k, 0]}]
inject(h){|a, e| a[proc.call(e)] += 1; a}
end
end
This allows you to write:
neg, zero, pos = arr.my_tally(-1, 0, 1){|e| e <=> 0}
While this is certainly more upfront code than the others, it may be nice to have if you find yourself using similar logic frequently. You could also just make this a regular method somewhere if you don't like monkey-patching.

easier and shorter way to sum values in a hash of hashes in ruby

Having a hash of hashes like this:
d = {a: {c: 1, d:3 }, b: {c: 2, d: 6}, ...}
What is the easier way to sum all values in c:?
d.values.map { |val| val[:c] }.reduce(&:+)
To explain:
d.values
=> [{:c=>1, :d=>3}, {:c=>2, :d=>6}]
d.values.map { |val| val[:c] }
=> [1, 2]
From this point you can use reduce(&:+) to get the sum, or if you're using rails (or have required active support), you can use Array#sum
reduce(&:+), by the way, is a shorthand for reduce { |memo, val| memo + val }
Just go over the hashes and sum their :c values.
d.values.sum { |h| h[:c] }
=> 3
Even shorter (from Sagar Pandya's comment):
d.sum { |_, v| v[:c] }
=> 3
If you wish to permit nested hashes of arbitrary depth you can use the following recursive method.
def sum_cees(h)
h.sum { |k,v| v.is_a?(Hash) ? sum_cees(v) : k == :c ? v : 0 }
end
sum_cees({ a: { c: 1, d:3 }, b: { d: { m: { c: 2, e: 6 } }, f: { c: 3} },
g: { c: 4 }, n: { r: 3 } })
#=> 10

Determine if a hash is a nested hash in Ruby

Is there a way to determine if a hash is a nested hash in ruby ?
For example
a = { a: 1, b: 2, c: 2 }
should return false
a = { a: {a1: 1, a2: 2}, b: {b1: 1}, c: 2 }
should return true
a.any? { |_, v| v.is_a?(Hash) }
You can check it by iterating over your hash values with Hash#values method:
a.values.any? { |v| v.is_a?(Hash) }

Can I do the union operator for two hashes and keep the max value

I wonder is there efficient way to get my goal in Ruby.
Keep all the keys in the two hashed, and keep the larger value.
input
h1 = {
a: 0,
b: 1,
c: 2
}
h2 = {
a: 7,
c: 9
}
output
{
a: 7,
b: 1,
c: 9
}
Hash#merge can take a block:
h1.merge(h2) {|key, old, new| old > new ? old : new}
# => {:a=>7, :b=>1, :c=>9}
Hash#merge can take block. You can use it to get max value:
h1.merge(h2) { |key, v1, v2| [v1, v2].max }
# => {:a=>7, :b=>1, :c=>9}

Resources