I have a hash like
h = {1 => {"inner" => 45}, 2 => {"inner" => 46}, "inner" => 47}
How do I delete every pair that contains the key "inner"?
You can see that some of the "inner" pairs appear directly in h while others appear in pairs in h
Note that I only want to delete the "inner" pairs, so if I call my mass delete method on the above hash, I should get
h = {1 => {}, 2 => {}}
Since these pairs don't have a key == "inner"
Really, this is what reject! is for:
def f! x
x.reject!{|k,v| 'inner' == k} if x.is_a? Hash
x.each{|k,v| f! x[k]}
end
def f x
x.inject({}) do |m, (k, v)|
v = f v if v.is_a? Hash # note, arbitrarily recursive
m[k] = v unless k == 'inner'
m
end
end
p f h
Update: slightly improved...
def f x
x.is_a?(Hash) ? x.inject({}) do |m, (k, v)|
m[k] = f v unless k == 'inner'
m
end : x
end
def except_nested(x,key)
case x
when Hash then x = x.inject({}) {|m, (k, v)| m[k] = except_nested(v,key) unless k == key ; m }
when Array then x.map! {|e| except_nested(e,key)}
end
x
end
Here is what I came up with:
class Hash
def deep_reject_key!(key)
keys.each {|k| delete(k) if k == key || self[k] == self[key] }
values.each {|v| v.deep_reject_key!(key) if v.is_a? Hash }
self
end
end
Works for a Hash or a HashWithIndifferentAccess
> x = {'1' => 'cat', '2' => { '1' => 'dog', '2' => 'elephant' }}
=> {"1"=>"cat", "2"=>{"1"=>"dog", "2"=>"elephant"}}
> y = x.with_indifferent_access
=> {"1"=>"cat", "2"=>{"1"=>"dog", "2"=>"elephant"}}
> x.deep_reject_key!(:"1")
=> {"1"=>"cat", "2"=>{"1"=>"dog", "2"=>"elephant"}}
> x.deep_reject_key!("1")
=> {"2"=>{"2"=>"elephant"}}
> y.deep_reject_key!(:"1")
=> {"2"=>{"2"=>"elephant"}}
Similar answer but it is a whitelist type approach. For ruby 1.9+
# recursive remove keys
def deep_simplify_record(hash, keep)
hash.keep_if do |key, value|
if keep.include?(key)
deep_simplify_record(value, keep) if value.is_a?(Hash)
true
end
end
end
hash = {:a => 1, :b => 2, :c => {:a => 1, :b => 2, :c => {:a => 1, :b => 2, :c => 4}} }
deep_simplify_record(hash, [:b, :c])
# => {:b=>2, :c=>{:b=>2, :c=>{:b=>2, :c=>4}}}
Also here are some other methods which I like to use for hashes.
https://gist.github.com/earlonrails/2048705
Related
Let's say I have a Ruby Hash where at least two keys are identical, except for their case, for instance:
{ 'Foo' => 1, 'foo' => 2, 'bar' => 3 }
Is there a way I can combine like keys (except for their case) such that the resulting Hash might look like this?
{ 'foo' => 3, 'bar' => 3 }
Thank you!
You can build a new hash:
new_hash = Hash.new(0)
old_hash.each_pair { |k, v| new_hash[k.downcase] += v }
You can use inject to loop all the hash items and build a new hash.
hash = { 'Foo' => 1, 'foo' => 2, 'bar' => 3 }
hash.inject({}) do |result, (key, value)|
key = key.downcase
result[key] = result[key] ? result[key] + value : value
result
end
Here is one more way of doing this
h = { 'Foo' => 1, 'foo' => 2, 'bar' => 3 }
p h.collect{|k, v| {k.downcase => v}}.reduce { |a, v| a.update(v) {|k, o, n| o + n } }
#=> {"foo"=>3, "bar"=>3}
h = { 'Foo' => 1, 'foo' => 2, 'bar' => 3 }
h.each_with_object({}) { |(k,v),g| g[k.downcase] = g[k.downcase].to_i + v }
#=> {"foo"=>3, "bar"=>3}
This makes use of the fact that if g does not have a key e, g[e] on the right side will equal nil and nil.to_i #=> 0. On the other hand, if g has a key e, h[e].to_i will equal h[e].
Another way:
h.each_with_object({}) { |(k,v),g| g.update(k.downcase=>v) { |_,o,v| o+v } }
#=> {"foo"=>3, "bar"=>3}
This question is the inverse of this question.
Given a nested hash like
{
:a => {
:b => {:c => 1, :d => 2},
:e => 3,
},
:f => 4,
}
what is the best way to convert it into a flat hash like
{
[:a, :b, :c] => 1,
[:a, :b, :d] => 2,
[:a, :e] => 3,
[:f] => 4,
}
Another way:
def flat_hash(h,f=[],g={})
return g.update({ f=>h }) unless h.is_a? Hash
h.each { |k,r| flat_hash(r,f+[k],g) }
g
end
h = { :a => { :b => { :c => 1,
:d => 2 },
:e => 3 },
:f => 4 }
flat_hash(h) #=> {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}
Very similar to Adiel Mittmann's solution
def flat_hash(h, k = [])
new_hash = {}
h.each_pair do |key, val|
if val.is_a?(Hash)
new_hash.merge!(flat_hash(val, k + [key]))
else
new_hash[k + [key]] = val
end
end
new_hash
end
Edit: Refactored for elegance. Should be almost as fast.
def flat_hash(hash, k = [])
return {k => hash} unless hash.is_a?(Hash)
hash.inject({}){ |h, v| h.merge! flat_hash(v[-1], k + [v[0]]) }
end
My attempt:
def flatten_hash(h)
return { [] => h } unless h.is_a?(Hash)
Hash[h.map { |a,v1| flatten_hash(v1).map { |b,v2| [[a] + b, v2] } }.flatten(1)]
end
Sorry for the bad variables names, had to fit it in one line.
This is not an attempt to give you the best way to do it, but it is a way :P
def flatten(hash)
return {[] => hash} if !hash.is_a?(Hash)
map = {}
hash.each_pair do |key1, value1|
flatten(value1).each_pair do |key2, value2|
map[[key1] + key2] = value2
end
end
return map
end
It works for your example, producing this result:
{[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}
It may not produce the result you expect if there are empty hashes.
A functional approach (see the history for an alternative implementations):
def recursive_flatten(hash)
hash.flat_map do |key, value|
if value.is_a?(Hash)
recursive_flatten(value).map { |ks, v| [[key] + ks, v] }
else
[[[key], value]]
end
end.to_h
end
Inspired by #cary-swoveland way, but in Hash class :
class Hash
def deep_flatten(previous_key=[])
flat_hash = {}
self.each do |key, value|
next_key = previous_key+[key]
flat_hash.update(value.is_a?(Hash) ? value.deep_flatten(next_key) : {next_key=>value})
end
return flat_hash
end
end
h = { :a => { :b => { :c => 1, :d => 2 }, :e => 3 }, :f => 4 }
h.deep_flatten #=> {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}
A declarative solution using DeepEnumerable:
require 'deep_enumerable'
h = { :a => { :b => { :c => 1, :d => 2 }, :e => 3 }, :f => 4 }
h.deep_each.map do |k, v|
[DeepEnumerable.deep_key_to_array(k), v]
end.to_h
or, for those who prefer point-free style
h.deep_each.to_h.shallow_map_keys(&DeepEnumerable.method(:deep_key_to_array))
Array support / readable names / no update for speed / stringified results keys
def flat_hash(input, base = nil, all = {})
if input.is_a?(Array)
input = input.each_with_index.to_a.each(&:reverse!)
end
if input.is_a?(Hash) || input.is_a?(Array)
input.each do |k, v|
flat_hash(v, base ? "#{base}.#{k}" : k, all)
end
else
all[base] = input
end
all
end
I am using Ruby on Rails 4 and I would like to replace all hash keys so to change the hash from
h_before = {:"aaa.bbb" => 1, :c => 2, ...}
to
h_after = {:bbb => 1, :c => 2, ...}
That is, I would like to someway "demodulize" all hash keys having the .. How can I make that?
each_with_object is a cleaner and shorter approach than inject from the answer:
h_before.each_with_object({}){|(k, v),h| h[k.to_s.split(".").last.to_sym] = v}
=> {:bbb=>1, :c=>2}
h_before = {:"aaa.bbb" => 1, :c => 2}
h_after =
h_before.inject({}){|h, (k, v)| h[k.to_s.split(".").last.to_sym] = v; h}
# => {:bbb = > 1, :c => 2}
Since there are a bunch of answers claiming to do the same thing, I thought it was time to post some benchmarks:
require 'fruity'
h_before = {:"aaa.bbb" => 1, :c => 2}
def cdub_test(hash)
Hash[hash.map {|k, v| [k.to_s.gsub(/^.*\./,"").to_sym, v]}]
end
def matt_test(old_hash)
Hash[old_hash.map { |k,v| [ k.to_s.sub(/.*\./,'').to_sym, v ] }]
end
class Hash
require 'active_support/core_ext/hash/indifferent_access'
def grep_keys(pattern)
return inject(HashWithIndifferentAccess.new){|h, (k, v)|
h[$1 || k] = v if pattern =~ k.to_s ; h }
end
end
def phlip_test(hash)
hash.grep_keys(/\.(\w+)$/)
end
def bjhaid_test(hash)
hash.each_with_object({}){|(k, v),h| h[k.to_s.split(".").last.to_sym] = v}
end
def sawa_test(hash)
hash.inject({}){|h, (k, v)| h[k.to_s.split(".").last.to_sym] = v; h}
end
compare do
cdub { cdub_test(h_before) }
matt { matt_test(h_before) }
phlip { phlip_test(h_before) }
bjhaid { bjhaid_test(h_before) }
sawa { sawa_test(h_before) }
end
Which outputs:
Running each test 1024 times. Test will take about 1 second.
bjhaid is similar to sawa
sawa is faster than matt by 60.00000000000001% ± 10.0%
matt is faster than phlip by 30.000000000000004% ± 10.0% (results differ: {:bbb=>1, :c=>2} vs {"bbb"=>1})
phlip is similar to cdub (results differ: {"bbb"=>1} vs {:bbb=>1, :c=>2})
Notice that phlip's code doesn't return the desired results.
old_hash = {:"aaa.bbb" => 1, :c => 2 }
new_hash = Hash[old_hash.map { |k,v| [ k.to_s.sub(/.*\./,'').to_sym, v ] }]
1.9.3p448 :001 > hash = {:"aaa.bbb" => 1, :c => 2 }
=> {:"aaa.bbb"=>1, :c=>2}
1.9.3p448 :002 > Hash[hash.map {|k, v| [k.to_s.gsub(/^.*\./,"").to_sym, v]}]
=> {:bbb=>1, :c=>2}
My grep_keys has never failed me here:
class Hash
def grep_keys(pattern)
return inject(HashWithIndifferentAccess.new){|h, (k, v)|
h[$1 || k] = v if pattern =~ k.to_s ; h }
end
end
It returns a shallow-copy of the Hash, but only with the matched keys. If the input regular expression contains a () match, the method replaces the key with the matched value. (Note this might merge two or more keys, and discard all but a random value for them!) I use it all the time to cut up a Rails param into sub-params containing only the keys that some module needs.
{:"aaa.bbb" => 1, :c => 2 }.grep_keys(/\.(\w+)$/) returns {"bbb"=>1}.
This method upgrades your actual problem to "how to define a regexp that matches what you mean by 'having a ".".'"
Write a method, which given an array, returns a hash whose keys are words in the array and whose values are the number of times each word appears.
arr=["A", "man", "a", "plan", "a", "canal","Panama"]
# => {'a' => 3, 'man' => 1, 'canal' => 1, 'panama' => 1, 'plan' => 1}
How do I achieve that? Here's my code:
hash={}
arr.each do |i|
hash.each do |c,v|
hash[c]=v+1
end
end
hash = arr.inject({}) do |hash, element|
element.downcase!
hash[element] ||= 0
hash[element] += 1
hash
end
arr.inject(Hash.new(0)){|h,k| k.downcase!; h[k] += 1; h}
arr = ["A", "man", "a", "plan", "a", "canal","Panama"]
r = {}
arr.each { |e| e.downcase!; r[e] = arr.count(e) if r[e].nil? }
Output
p r
#==> {"a"=>3, "man"=>1, "plan"=>1, "canal"=>1, "panama"=>1}
This question is the inverse of this question.
Given a nested hash like
{
:a => {
:b => {:c => 1, :d => 2},
:e => 3,
},
:f => 4,
}
what is the best way to convert it into a flat hash like
{
[:a, :b, :c] => 1,
[:a, :b, :d] => 2,
[:a, :e] => 3,
[:f] => 4,
}
Another way:
def flat_hash(h,f=[],g={})
return g.update({ f=>h }) unless h.is_a? Hash
h.each { |k,r| flat_hash(r,f+[k],g) }
g
end
h = { :a => { :b => { :c => 1,
:d => 2 },
:e => 3 },
:f => 4 }
flat_hash(h) #=> {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}
Very similar to Adiel Mittmann's solution
def flat_hash(h, k = [])
new_hash = {}
h.each_pair do |key, val|
if val.is_a?(Hash)
new_hash.merge!(flat_hash(val, k + [key]))
else
new_hash[k + [key]] = val
end
end
new_hash
end
Edit: Refactored for elegance. Should be almost as fast.
def flat_hash(hash, k = [])
return {k => hash} unless hash.is_a?(Hash)
hash.inject({}){ |h, v| h.merge! flat_hash(v[-1], k + [v[0]]) }
end
My attempt:
def flatten_hash(h)
return { [] => h } unless h.is_a?(Hash)
Hash[h.map { |a,v1| flatten_hash(v1).map { |b,v2| [[a] + b, v2] } }.flatten(1)]
end
Sorry for the bad variables names, had to fit it in one line.
This is not an attempt to give you the best way to do it, but it is a way :P
def flatten(hash)
return {[] => hash} if !hash.is_a?(Hash)
map = {}
hash.each_pair do |key1, value1|
flatten(value1).each_pair do |key2, value2|
map[[key1] + key2] = value2
end
end
return map
end
It works for your example, producing this result:
{[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}
It may not produce the result you expect if there are empty hashes.
A functional approach (see the history for an alternative implementations):
def recursive_flatten(hash)
hash.flat_map do |key, value|
if value.is_a?(Hash)
recursive_flatten(value).map { |ks, v| [[key] + ks, v] }
else
[[[key], value]]
end
end.to_h
end
Inspired by #cary-swoveland way, but in Hash class :
class Hash
def deep_flatten(previous_key=[])
flat_hash = {}
self.each do |key, value|
next_key = previous_key+[key]
flat_hash.update(value.is_a?(Hash) ? value.deep_flatten(next_key) : {next_key=>value})
end
return flat_hash
end
end
h = { :a => { :b => { :c => 1, :d => 2 }, :e => 3 }, :f => 4 }
h.deep_flatten #=> {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}
A declarative solution using DeepEnumerable:
require 'deep_enumerable'
h = { :a => { :b => { :c => 1, :d => 2 }, :e => 3 }, :f => 4 }
h.deep_each.map do |k, v|
[DeepEnumerable.deep_key_to_array(k), v]
end.to_h
or, for those who prefer point-free style
h.deep_each.to_h.shallow_map_keys(&DeepEnumerable.method(:deep_key_to_array))
Array support / readable names / no update for speed / stringified results keys
def flat_hash(input, base = nil, all = {})
if input.is_a?(Array)
input = input.each_with_index.to_a.each(&:reverse!)
end
if input.is_a?(Hash) || input.is_a?(Array)
input.each do |k, v|
flat_hash(v, base ? "#{base}.#{k}" : k, all)
end
else
all[base] = input
end
all
end