Hash tree depth Ruby - 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}}}

Related

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) }

Rewriting Ruby Inject

Here is a brain bender.
I am trying to rewrite the Ruby Inject method. I have got as far as below.
class Array
def injector(input = nil)
if input == nil
num = self.first
else
num = input
end
self[0..-1].each do |x|
num = yield(num, x)
end
return num
end
end
It is passing some tests, but it is not fully accurate, for example;
[1,2,3,4,5].injector(0) {|x,y| x + y} #=> 14
As opposed to the expected output 15, is it a rounding error? I cannot seem to figure this one out
Additional example (above updated [0..-1]):
[9,8,7,6,5].injector {|x,y| x * y} #=> 136080
Ruby .inject outputs 15120
The starting index is important as it depends on your input.
class Array
def injector(input = nil)
if input.nil?
start = 1
num = self.first
else
start = 0
num = input
end
self[start..-1].each do |x|
num = yield(num, x)
end
return num
end
end
Using nil as the default is probably wrong, I should be able to pass nil in as the default memo.
class Array
def injector(memo = (i=1; first))
(i||0).upto(length-1) { |i| memo = yield memo, self[i] }
memo
end
end
[1,2,3,4,5].injector(1) { |sum, n| sum + n }
[1,2,3,4,5].injector(0) { |sum, n| sum + n }
[1,2,3,4,5].injector { |sum, n| sum + n }
[1,2,3].injector(2) { |product, n| product * n }
[1,2,3].injector(1) { |product, n| product * n }
[1,2,3].injector { |product, n| product * n }
['b', 'c', 'd'].injector('a') { |str, char| str + char } # => "abcd"
['b', 'c', 'd'].injector { |str, char| str + char } # => "bcd"
seen = []
[1].injector(nil) { |prev, crnt| seen << prev << crnt }
seen # => [nil, 1]

How to recreate .select()?

I'm trying to recreate the #select method. So far, I have array portion working, but when I try to call #my_select on a Hash, I get an empty Hash.
FYI, for starters, I had to recreate my own `#each' method. Here's that.
module Enumerable
def my_each
i = 0
while i < self.size
yield(self[i])
i += 1
end
self
end
Now, here's the #my_select method I created:
def my_select
if self.instance_of?(Array)
ret = []
self.my_each do |item|
ret << item if yield(item)
end
ret
elsif self.instance_of?(Hash)
ret = {}
self.my_each do |key, value|
if yield(key,value)
ret[key] = value
end
end
ret
end
end
end
...my input/output...
> h = { "a" => 100, "b" => 200, "c" => 300 }
> h.select { |k,v| k == "a"}
=> {"a"=>100}
> h.my_select { |k,v| k == "a"}
=> {}
Maybe you could change my_each to handle Hash as well?
def my_each
if self.instance_of?(Array)
i = 0
while i < self.size
yield(self[i])
i += 1
end
self
elsif self.instance_of?(Hash)
i = 0
arr = self.to_a
while i < arr.size
yield(arr[i][0], arr[i][1])
i += 1
end
self
end
end

Return the elements present exactly once in an array

I am a beginner in Ruby. Can anyone help me to write code for this, please?
Given an Array, return the elements that are present exactly once in the array.
For example, it should pass the following test cases:
returns [1,4,5], given [1,2,2,3,3,4,5]
returns [1,3], given [1,2,2,3,4,4]
Put the items in an array. a = [1,2,2,3,4,4] Then run a few filters to get the items you want.
a.group_by { |x| x }.reject { |k,v| v.count > 1 }.keys
#=> [1,3]
Updated With Stefan's keys suggestion.
a = [1,2,2,3,3,4,5]
p a.select{|i| a.count(i) == 1}
# >> [1, 4, 5]
a = [1,2,2,3,4,4]
p a.select{|i| a.count(i) == 1}
# >> [1, 3]
Benchmarks
require 'benchmark'
a = [1,2,2,3,3,4,5]
n = 1000000
Benchmark.bm(15) do |x|
x.report('priti') { n.times { a.select{|i| a.count(i) == 1} } }
x.report('Jason') { n.times { a.group_by { |x| x }.reject { |k,v| v.count > 1 }.keys } }
x.report('rogerdpack2') { n.times {
bad = {}
good = {}
a.each{|v|
if bad.key? v
# do nothing
else
if good.key? v
bad[v] = true
good.delete(v)
else
good[v] = true;
end
end
}
good.keys
}
}
end
with this result
priti 3.152000 0.000000 3.152000 ( 3.247000)
Jason 4.633000 0.000000 4.633000 ( 4.845000)
rogerdpack2 3.853000 0.000000 3.853000 ( 3.886000)
and with a larger array:
require 'benchmark'
a = [1,2,2,3,3,4,5]*5 + [33,34]
n = 1000000
Benchmark.bm(15) do |x|
x.report('priti') { n.times { a.select{|i| a.count(i) == 1} } }
x.report('Jason') { n.times { a.group_by { |x| x }.reject { |k,v| v.count > 1 }.keys } }
x.report('rogerdpack2') { n.times {
bad = {}
good = {}
a.each{|v|
if bad.key? v
# do nothing
else
if good.key? v
bad[v] = true
good.delete(v)
else
good[v] = true;
end
end
}
good.keys
}
}
x.report('priti2') { n.times { a.uniq.select{|i| a.count(i) == 1} }}
end
you get result:
user system total real
priti 60.435000 0.000000 60.435000 ( 60.769151)
Jason 10.827000 0.016000 10.843000 ( 10.978195)
rogerdpack2 9.141000 0.000000 9.141000 ( 9.213843)
priti2 15.897000 0.000000 15.897000 ( 16.007201)
Here's another option:
a = [1,2,2,3,3,4,5]
b = {}
a.each{|v|
b[v] ||= 0
b[v] += 1
}
b.select{|k, v| v == 1}.keys
and here's a potentially faster one (though more complex) that is hard coded to look for items "just listed once":
a = [1,2,2,3,3,4,5]
bad = {}
good = {}
a.each{|v|
if bad.key? v
# do nothing
else
if good.key? v
bad[v] = true
good.delete(v)
else
good[v] = true;
end
end
}
good.keys

Resources