Related
For example, if I have the hash {"a" => 1, "b" => 2, "c" => 1}, I want ["a", "c"].
I can do hash.min_by{|k,v| v}, but that only returns the first match ("a"=>1).
How do I get it to recognize duplicates and return {"a"=> 1, "c"=> 1}?
That operation is a bit unusual for a hash, so it’s not very neat:
min_value = hash.values.min
min_pairs = hash.select { |k, v| v == min_value }
{"a" => 1, "b" => 2, "c" => 1}.group_by(&:last).min.last.map(&:first)
# => ["a", "c"]
or
{"a" => 1, "b" => 2, "c" => 1}.group_by(&:last).min.last.to_h.keys
# => ["a", "c"]
You can write FORTRAN in any language! :)
It has the advantage of only requiring 1 pass :
hash = {"a" => 1, "b" => 2, "c" => 1}
min = Float::INFINITY
values_for_min = []
hash.each do |key, value|
case value <=> min
when 0
values_for_min << key
when -1
min = value
values_for_min = [key]
end
end
p min
#=> 1
p values_for_min
#=> ["a", "c"]
let's have this hash:
hash = {"a" => 1, "b" => {"c" => 3}}
hash.get_all_keys
=> ["a", "b", "c"]
how can i get all keys since hash.keys returns just ["a", "b"]
This will give you an array of all the keys for any level of nesting.
def get_em(h)
h.each_with_object([]) do |(k,v),keys|
keys << k
keys.concat(get_em(v)) if v.is_a? Hash
end
end
hash = {"a" => 1, "b" => {"c" => {"d" => 3}}}
get_em(hash) # => ["a", "b", "c", "d"]
I find grep useful here:
def get_keys(hash)
( hash.keys + hash.values.grep(Hash){|sub_hash| get_keys(sub_hash) } ).flatten
end
p get_keys my_nested_hash #=> ["a", "b", "c"]
I like the solution as it is short, yet it reads very nicely.
Version that keeps the hierarchy of the keys
Works with arrays
Works with nested hashes
keys_only.rb
# one-liner
def keys_only(h); h.map { |k, v| v = v.first if v.is_a?(Array); v.is_a?(Hash) ? [k, keys_only(v)] : k }; end
# nicer
def keys_only(h)
h.map do |k, v|
v = v.first if v.is_a?(Array);
if v.is_a?(Hash)
[k, keys_only(v)]
else
k
end
end
end
hash = { a: 1, b: { c: { d: 3 } }, e: [{ f: 3 }, { f: 5 }] }
keys_only(hash)
# => [:a, [:b, [[:c, [:d]]]], [:e, [:f]]]
P.S.: Yes, it looks like a lexer :D
Bonus: Print the keys in a nice nested list
# one-liner
def print_keys(a, n = 0); a.each { |el| el.is_a?(Array) ? el[1] && el[1].class == Array ? print_keys(el, n) : print_keys(el, n + 1) : (puts " " * n + "- #{el}") }; nil; end
# nicer
def print_keys(a, n = 0)
a.each do |el|
if el.is_a?(Array)
if el[1] && el[1].class == Array
print_keys(el, n)
else
print_keys(el, n + 1)
end
else
puts " " * n + "- #{el}"
end
end
nil
end
> print_keys(keys_only(hash))
- a
- b
- c
- d
- e
- f
def get_all_keys(hash)
hash.map do |k, v|
Hash === v ? [k, get_all_keys(v)] : [k]
end.flatten
end
Please take a look of following code:
hash = {"a" => 1, "b" => {"c" => 3}}
keys = hash.keys + hash.select{|_,value|value.is_a?(Hash)}
.map{|_,value| value.keys}.flatten
p keys
result:
["a", "b", "c"]
New solution, considering #Bala's comments.
class Hash
def recursive_keys
if any?{|_,value| value.is_a?(Hash)}
keys + select{|_,value|value.is_a?(Hash)}
.map{|_,value| value.recursive_keys}.flatten
else
keys
end
end
end
hash = {"a" => 1, "b" => {"c" => {"d" => 3}}, "e" => {"f" => 3}}
p hash.recursive_keys
result:
["a", "b", "e", "c", "d", "f"]
Also deal with nested arrays that include hashes
def all_keys(items)
case items
when Hash then items.keys + items.values.flat_map { |v| all_keys(v) }
when Array then items.flat_map { |i| all_keys(i) }
else []
end
end
class Hash
def get_all_keys
[].tap do |result|
result << keys
values.select { |v| v.respond_to?(:get_all_keys) }.each do |value|
result << value.get_all_keys
end
end.flatten
end
end
hash = {"a" => 1, "b" => {"c" => 3}}
puts hash.get_all_keys.inspect # => ["a", "b", "c"]
Here is another approach :
def get_all_keys(h)
h.each_with_object([]){|(k,v),a| v.is_a?(Hash) ? a.push(k,*get_all_keys(v)) : a << k }
end
hash = {"a" => 1, "b" => {"c" => {"d" => 3}}}
p get_all_keys(hash)
# >> ["a", "b", "c", "d"]
I'm sure there is a more elegant solution, but this option works:
blah = {"a" => 1, "b" => {"c" => 3}}
results = []
blah.each do |k,v|
if v.is_a? Hash
results << k
v.each_key {|key| results << key}
else
results << k
end
end
puts results
hash.keys is the simplest one I have seen to return an array of the key values in a hash.
I have
a = ["a", "d", "c", "b", "b", "c", "c"]
and need to print something like (sorted descending by number of occurrences):
c:3
b:2
I understand first part (finding NON-unique) is:
b = a.select{ |e| a.count(e) > 1 }
=> ["c", "b", "b", "c", "c"]
or
puts b.select{|e, c| [e, a.count(e)] }.uniq
c
b
How to output each non-unique with number of occurrences sorted backwards?
puts a.uniq.
map { | e | [a.count(e), e] }.
select { | c, _ | c > 1 }.
sort.reverse.
map { | c, e | "#{e}:#{c}" }
The group_by method is used for this often:
a.group_by{ |i| i }
{
"a" => [
[0] "a"
],
"d" => [
[0] "d"
],
"c" => [
[0] "c",
[1] "c",
[2] "c"
],
"b" => [
[0] "b",
[1] "b"
]
}
I like:
a.group_by{ |i| i }.each_with_object({}) { |(k,v), h| h[k] = v.size }
{
"a" => 1,
"d" => 1,
"c" => 3,
"b" => 2
}
Or:
Hash[a.group_by{ |i| i }.map{ |k,v| [k, v.size] }]
{
"a" => 1,
"d" => 1,
"c" => 3,
"b" => 2
}
One of those might scratch your itch. From there you can reduce the result using a little test:
Hash[a.group_by{ |i| i }.map{ |k,v| v.size > 1 && [k, v.size] }]
{
"c" => 3,
"b" => 2
}
If you just want to print the information use:
puts a.group_by{ |i| i }.map{ |k,v| "#{k}: #{v.size}" }
a: 1
d: 1
c: 3
b: 2
From Ruby 2.7, you can utilise Enumerable#tally and numbered block arguments:
a = ["a", "d", "c", "b", "b", "c", "c"]
puts a.tally.filter { _2 > 1 }.sort_by { -_2 }.map &:first
Here, Enumerable#tally returns a hash like { 'a' => 1, 'b' => 2, ... }, which you then have to filter and sort. After sorting, the hash would've collapsed to a nested array, e.g. [['b', 2], ...]. The last step is to take the first argument of each array element, using &:first.
How about:
a.sort.chunk{|x| a.count(x)}.sort.reverse.each do |n, v|
puts "#{v[0]}:#{n}" if n > 1
end
I personally like this solution:
a.inject({}) {|hash, val| hash[val] ||= 0; hash[val] += 1; hash}.
reject{|key, value| value == 1}.sort.reverse.
each_pair{|k,v| puts("#{k}:#{v}")}
a.reduce(Hash.new(0)) { |memo,x| memo[x] += 1; memo } # Frequency count.
.select { |_,count| count > 1 } # Choose non-unique items.
.sort_by { |x| -x[1] } # Sort by number of occurrences descending.
# => [["c", 3], ["b", 2]]
Also:
a.group_by{|x|x}.map{|k,v|[k,v.size]}.select{|x|x[1]>1}.sort_by{|x|-x[1]}
# => [["c", 3], ["b", 2]]
This will give you a hash with element => occurrences:
b.reduce(Hash.new(0)) do |hash, element|
hash[element] += 1
hash
end
puts a.uniq.
map { |e| a.count(e) > 1 ? [e, a.count(e)] : nil }.compact.
sort { |a, b| b.last <=> a.last }
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
Is there a clever way to achieve the following in Ruby?
hash1 = { "a" => 1, "b" => 2, "d" => 3}
hash2 = { "a" => 4, "b" => 5, "c" => 7}
hash3 = { "a" => 4, "d" => 7, "e" => 9}
puts hash1.csvMerge(hash2).csvMerge(hash3)
with the output being:
{ "a" => "1,4,4",
"b" => "2,5,0",
"c" => "0,7,0",
"d" => "3,0,7",
"e" => "0,0,9" }
What I'm trying to do is merge a bunch of Ruby hashes, in practice I have over a dozen, into a single hash where the values are combined into a comma separated string.
hash1.merge(hash2){|key, oldval, newval| [oldval,newval].join(",")}
=> {"a"=>"1,4", "b"=>"2,5", "d"=>3, "c"=>7}
hashes = [hash1, hash2, hash3]
keys = hashes.inject({}){|hh, h| hh = hh.merge(h); hh}.keys # the set of all keys #
default_hash = keys.inject({}){|d, k| d[k] = 0; d} # hash with value 0 for all keys #
complemented_hashes = hashes.map{|h| default_hash.merge(h)} # missing values filled in #
p Hash[complemented_hashes.map{|h| h.to_a}.flatten(1).group_by{|k, v| k}.
map{|k, v| [k, v.map{|k, v| v}.join(",")]}]
I dont know if this has performance issues
hashes = [hash1, hash2, hash3]
hash_with_all_keys = {}
hashes.each{|hash| hash_with_all_keys.merge!(hash)}
keys = hash_with_all_keys.keys
result_hash_mapping = keys.map do |key|
value = hashes.map{|hash| hash[key].to_i}.join(",")
[key, value]
end
result_hash = Hash[result_hash_mapping]
I could have replaced lines 2 to 4 with keys = hashes.inject({}){|merge_hash, hash| merge_hash.merge(hash)}.keys but I find it hard to read.
hash1 = { "a" => 1, "b" => 2, "d" => 3}
hash2 = { "a" => 4, "b" => 5, "c" => 7}
hash3 = { "a" => 4, "d" => 7, "e" => 9}
hashes = [hash1, hash2, hash3]
hashes.each{|h| h.each{|k,v| h[k] = v.to_s }}
res = hashes.inject{|m,h| m.merge(h){|k,old,new| "#{old},#{new}" }} #merge and glue
p res
#=> {"a"=>"1,4,4", "b"=>"2,5", "d"=>"3,7", "c"=>"7", "e"=>"9"}
Functional approach:
hashes = [hash1, hash2, hash3]
result = Hash[hashes.flat_map(&:keys).uniq.map do |key|
[key, hashes.map { |h| h[key] || 0 }.join(",")]
end]
#=> {"a"=>"1,4,4", "b"=>"2,5,0", "c"=>"0,7,0", "d"=>"3,0,7", "e"=>"0,0,9"}