How to print only the keys which have values? - ruby

I have a hash with keys and values as follows:
hash = {"lili" => [] , "john" => ["a", "b"], "andrew" => [], "megh" => ["b","e"]}
As we can see some of the keys have values as empty arrays.
Some keys have array values where there are actual values in the array.
I want to loop over the hash and generate a new hash that includes only those keys which have values in their arrays (not the ones which have empty arrays). How can I do that?

The title says:
PRINT only the keys, but reading the post you are trying to generate a hash subset given a hash.
The solution for the TITLE of the POST:
hash.each {|k,v| p k unless v.empty? }
If you want to generate a new hash subset give the original hash:
hash.reject { |k,v| v.nil? || v.empty? }
If you want to PRINT the subset generated give the original hash:
hash.reject { |k,v| v.nil? || v.empty? }.each { |k,v| p k }

You could use reject to filter out those elements in your hash where the value is an empty array or the value is nil and then iterate to print their content;
{:lili=>[], :john=>[:a, :b], :andrew=>[], :megh=>[:b, :e], :other=>nil}
.reject { (_2 || []).empty? }
.each { puts "#{_1} has the value(s) #{_2}" }
that prints
john has the value(s) [:a, :b]
megh has the value(s) [:b, :e]

Unsure of your desired output or exact data structure, but you can simply use reject along with empty? to remove any hash value that contains an empty array:
hash = {"lili" => [] , "john" => ["a", "b"], "andrew" => [], "megh" => ["b","e"]}
hash.reject {|k, v| v.empty?}
#=> {"john"=>["a", "b"], "megh"=>["b", "e"]}
It should however be noted that this approach will not work if any hash values are nil. To address that situation, I would recommend either using compact to remove any hash elements with nil values prior to using reject OR by testing for either nil? or empty? (and in that order):
hash = {"lili" => [] , "john" => ["a", "b"], "andrew" => [], "megh" => ["b","e"], "other" => nil}
hash.reject {|k, v| v.empty?}
#=> Evaluation Error: Ruby NoMethodError: undefined method `empty?' for nil:NilClass
hash.compact.reject {|k, v| v.empty?}
#=> {"john"=>["a", "b"], "megh"=>["b", "e"]}
hash.reject {|k, v| v.empty? or v.nil?}
#=> Evaluation Error: Ruby NoMethodError: undefined method `empty?' for nil:NilClass
hash.reject {|k, v| v.nil? or v.empty?}
#=> {"john"=>["a", "b"], "megh"=>["b", "e"]}
hash.reject {|k, v| v.empty? || v.nil?}
#=> Evaluation Error: Ruby NoMethodError: undefined method `empty?' for nil:NilClass
hash.reject {|k, v| v.nil? || v.empty?}
#=> {"john"=>["a", "b"], "megh"=>["b", "e"]}

Related

Counting unique occurrences of values in a hash

I'm trying to count occurrences of unique values matching a regex pattern in a hash.
If there's three different values, multiple times, I want to know how much each value occurs.
This is the code I've developed to achieve that so far:
def trim(results)
open = []
results.map { |k, v| v }.each { |n| open << n.to_s.scan(/^closed/) }
puts open.size
end
For some reason, it returns the length of all the values, not just the ones I tried a match on. I've also tried using results.each_value, to no avail.
Another way:
hash = {a: 'foo', b: 'bar', c: 'baz', d: 'foo'}
hash.each_with_object(Hash.new(0)) {|(k,v),h| h[v]+=1 if v.start_with?('foo')}
#=> {"foo"=>2}
or
hash.each_with_object(Hash.new(0)) {|(k,v),h| h[v]+=1 if v =~ /^foo|bar/}
#=> {"foo"=>2, "bar"=>1}
Something like this?
hash = {a: 'foo', b: 'bar', c: 'baz', d: 'foo'}
groups = hash.group_by{ |k, v| v[/(?:foo|bar)/] }
# => {"foo"=>[[:a, "foo"], [:d, "foo"]],
# "bar"=>[[:b, "bar"]],
# nil=>[[:c, "baz"]]}
Notice that there is a nil key, which means the regex didn't match anything. We can get rid of it because we (probably) don't care. Or maybe you do care, in which case, don't get rid of it.
groups.delete(nil)
This counts the number of matching "hits":
groups.map{ |k, v| [k, v.size] }
# => [["foo", 2], ["bar", 1]]
group_by is a magical method and well worthy of learning.
def count(hash, pattern)
hash.each_with_object({}) do |(k, v), counts|
counts[k] = v.count{|s| s.to_s =~ pattern}
end
end
h = { a: ['open', 'closed'], b: ['closed'] }
count(h, /^closed/)
=> {:a=>1, :b=>1}
Does that work for you?
I think it worths to update for RUBY_VERSION #=> "2.7.0" which introduces Enumerable#tally:
h = {a: 'foo', b: 'bar', c: 'baz', d: 'foo'}
h.values.tally #=> {"foo"=>2, "bar"=>1, "baz"=>1}
h.values.tally.select{ |k, _| k=~ /^foo|bar/ } #=> {"foo"=>2, "bar"=>1}

Hash with array as key

I'm defining a hash with an array as a key and another array as its value. For example:
for_example = {[0,1] => [:a, :b, :c]}
Everything is as expected below.
my_hash = Hash.new([])
an_array_as_key = [4,2]
my_hash[an_array_as_key] #=> []
my_hash[an_array_as_key] << "the" #=> ["the"]
my_hash[an_array_as_key] << "universal" #=> ["the", "universal"]
my_hash[an_array_as_key] << "answer" #=> ["the", "universal", "answer"]
But if I try to access the keys:
my_hash #=> {}
my_hash.keys #=> []
my_hash.count #=> 0
my_hash.values #=> []
my_hash.fetch(an_array_as_key) # KeyError: key not found: [4, 2]
my_hash.has_key?(an_array_as_key) #=> false
Rehash doesn't help:
my_hash #=> {}
my_hash.rehash #=> {}
my_hash.keys #=> []
But the values are saved:
my_hash[an_array_as_key] #=> ["the", "universal", "answer"]
Am I missing something?
To understand this, You need to understand the difference between Hash::new and Hash::new(ob). Suppose you define a hash object using Hash::new or hash literal {}. Now whenever you will write a code hsh[any_key], there is two kind of output may be seen, if any_key don't exist, then default value nil will be returned,otherwise whatever value is associated with the key will be returned. The same explanation will be applicable if you create any Hash object using Hash.new.
Now Hash.new(ob) is same as Hash.new, with one difference is, you can set any default value you want, for non existent keys of that hash object.
my_hash = Hash.new([])
my_hash[2] # => []
my_hash[2].object_id # => 83664630
my_hash[4] # => []
my_hash[4].object_id # => 83664630
my_hash[3] << 4 # => [4]
my_hash[3] # => [4]
my_hash[3].object_id # => 83664630
my_hash[5] << 8 # => [4, 8]
my_hash[5] # => [4, 8]
my_hash[5].object_id # => 83664630
Now see in the above example my_hash has no keys like 2,3 and 4. But the object_id proved that, all key access results in to return the same array object. my_hash[2] is not adding the key to the hash my_hash, rather trying to access the value of the key 2 if that key exist, otherwise it is returning the default value of my_hash. Remember all lines like my_hash[2],my_hash[3] etc is nothing but a call to Hash#[] method.
But there is a third way to go, may be you are looking for, which is Hash::new {|hash, key| block }.With this style you can add key to the hash object if that key doesn't exist, with a default value of same class instance,but not the same instance., while you are doing actually Hash#[] method call.
my_hash = Hash.new { |hash, key| hash[key] = []}
my_hash[2] # => []
my_hash[2].object_id # => 76312700
my_hash[3] # => []
my_hash[3].object_id # => 76312060
my_hash.keys # => [2, 3]

Exposing key/value pair in code block parameters

Is it possible to expose the key/value pair from an array of hashes in the parameter of the block?
array = [{:a=>'a'}, {:b=>'b'}] # an array of hashes
array.each {|key, value| puts "#{key},#{value}"}
array.map {|key, value| "(#{key},#{value})"}
array.inject([]) {|accum, (key,value)| key == :a ? value : accum}
Currently the results of the code block parameters |key, value| are (hash, nil)
I would like to get (symbol, string) in the declaration of the |key,value| parameters. Is this possible or am I stuck having to pass in a hash and extracting the key/value pair myself?
I know that passing a hash instead of an array will automatically give access to the key/value pair, but much of Ruby returns arrays.
UPDATE: It seems possible with arrays, but not hashes?
a = [['a','b'],['c','d']]
a.each {|(x,y)| puts "#{x}=>#{y}"} # => a=>b
# => c=>d
a.each {|x| p x} # => ["a", "b"]
# => ["c", "d"]
Ok.. If I catch you wright :
array = [{:a=>'a'}, {:b=>'b'}]
array.map {|k,v| [k,v]}
# => [[{:a=>"a"}, nil], [{:b=>"b"}, nil]]
you are getting above. But you want [[:a,"a"], [:b,"b"]] . No It is not possible. With #each or #map, when you have array of hashes like you given,you can't assign the block parameter k,v as :a,"a" and :b,"b" with each pass.
More simply try on your IRB,you will get to see the effect:
k,v = {:a=>'a'}
p k,v
# >> {:a=>"a"}
# >> nil

How to convert the values of a hash from String to Array in Ruby?

I'm looking to perform a conversion of the values in a Ruby hash from String to Integer.
I thought this would be fairly similar to the way you perform a conversion in a Ruby array (using the map method), but I have not been able to find an elegant solution that doesn't involve converting the hash to an array, flattening it, etc.
Is there a clean solution to do this?
Eg. From
x = { "a" => "1", "b" => "2", "c"=> "3" }
To
x = { "a" => 1, "b" => 2, "c" => 3 }
To avoid modifying the original Hash (unlike the existing answers), I'd use
newhash = x.reduce({}) do |h, (k, v)|
h[k] = v.to_i and h
end
If you're using Ruby 1.9, you can also use Enumerable#each_with_object to achieve the same effect a bit more cleanly.
newhash = x.each_with_object({}) do |(k, v), h|
h[k] = v.to_i
end
If you want to, you can also extract the logic into a module and extend the Hash class with it.
module HMap
def hmap
self.each_with_object({}) do |(k, v), h|
h[k] = yield(k, v)
end
end
end
class Hash
include HMap
end
Now you can use
newhash = x.hmap { |k, v| v.to_i } # => {"a"=>1, "b"=>2, "c"=>3}
My preferred solution:
Hash[x.map { |k, v| [k, v.to_i]}] #=> {"a"=>1, "b"=>2, "c"=>3}
A somewhat wasteful one (has to iterate over the values twice):
Hash[x.keys.zip(x.values.map(&:to_i))] #=> {"a"=>1, "b"=>2, "c"=>3}
Try this:
x.each{|k,v| x[k]=v.to_i}
p.keys.map { |key| p[key] = p[key].to_i }

Hash invert in Ruby?

I've got a hash of the format:
{key1 => [a, b, c], key2 => [d, e, f]}
and I want to end up with:
{ a => key1, b => key1, c => key1, d => key2 ... }
What's the easiest way of achieving this?
I'm using Ruby on Rails.
UPDATE
OK I managed to extract the real object from the server log, it is being pushed via AJAX.
Parameters: {"status"=>{"1"=>["1", "14"], "2"=>["7", "12", "8", "13"]}}
hash = {:key1 => ["a", "b", "c"], :key2 => ["d", "e", "f"]}
first variant
hash.map{|k, v| v.map{|f| {f => k}}}.flatten
#=> [{"a"=>:key1}, {"b"=>:key1}, {"c"=>:key1}, {"d"=>:key2}, {"e"=>:key2}, {"f"=>:key2}]
or
hash.inject({}){|h, (k,v)| v.map{|f| h[f] = k}; h}
#=> {"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2}
UPD
ok, your hash is:
hash = {"status"=>{"1"=>["1", "14"], "2"=>["7", "12", "8", "13"]}}
hash["status"].inject({}){|h, (k,v)| v.map{|f| h[f] = k}; h}
#=> {"12"=>"2", "7"=>"2", "13"=>"2", "8"=>"2", "14"=>"1", "1"=>"1"}
Lots of other good answers. Just wanted to toss this one in too for Ruby 2.0 and 1.9.3:
hash = {apple: [1, 14], orange: [7, 12, 8, 13]}
Hash[hash.flat_map{ |k, v| v.map{ |i| [i, k] } }]
# => {1=>:apple, 14=>:apple, 7=>:orange, 12=>:orange, 8=>:orange, 13=>:orange}
This is leveraging: Hash::[] and Enumerable#flat_map
Also in these new versions there is Enumerable::each_with_object which is very similar to Enumerable::inject/Enumerable::reduce:
hash.each_with_object(Hash.new){ |(k, v), inverse|
v.each{ |e| inverse[e] = k }
}
Performing a quick benchmark (Ruby 2.0.0p0; 2012 Macbook Air) using an original hash with 100 keys, each with 100 distinct values:
Hash::[] w/ Enumerable#flat_map
155.7 (±9.0%) i/s - 780 in 5.066286s
Enumerable#each_with_object w/ Enumerable#each
199.7 (±21.0%) i/s - 940 in 5.068926s
Shows that the each_with_object variant is faster for that data set.
Ok, let's guess. You say you have an array but I agree with Benoit that what you probably have is a hash. A functional approach:
h = {:key1 => ["a", "b", "c"], :key2 => ["d", "e", "f"]}
h.map { |k, vs| Hash[vs.map { |v| [v, k] }] }.inject(:merge)
#=> {"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2}
Also:
h.map { |k, vs| Hash[vs.product([k])] }.inject(:merge)
#=> {"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2}
In the case where a value corresponds to more than one key, like "c" in this example...
{ :key1 => ["a", "b", "c"], :key2 => ["c", "d", "e"]}
...some of the other answers will not give the expected result. We will need the reversed hash to store the keys in arrays, like so:
{ "a" => [:key1], "b" => [:key1], "c" => [:key1, :key2], "d" => [:key2], "e" => [:key2] }
This should do the trick:
reverse = {}
hash.each{ |k,vs|
vs.each{ |v|
reverse[v] ||= []
reverse[v] << k
}
}
This was my use case, and I would have defined my problem much the same way as the OP (in fact, a search for a similar phrase got me here), so I suspect this answer may help other searchers.
If you're looking to reverse a hash formatted like this, the following may help you:
a = {:key1 => ["a", "b", "c"], :key2 => ["d", "e", "f"]}
a.inject({}) do |memo, (key, values)|
values.each {|value| memo[value] = key }
memo
end
this returns:
{"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2}
new_hash={}
hash = {"key1" => ['a', 'b', 'c'], "key2" => ['d','e','f']}
hash.each_pair{|key, val|val.each{|v| new_hash[v] = key }}
This gives
new_hash # {"a"=>"key1", "b"=>"key1", "c"=>"key1", "d"=>"key2", "e"=>"key2", "f"=>"key2"}
If you want to correctly deal with duplicate values, then you should use the Hash#inverse
from Facets of Ruby
Hash#inverse preserves duplicate values,
e.g. it ensures that hash.inverse.inverse == hash
either:
use Hash#inverse from here: http://www.unixgods.org/Ruby/invert_hash.html
use Hash#inverse from FacetsOfRuby library 'facets'
usage like this:
require 'facets'
h = {:key1 => [:a, :b, :c], :key2 => [:d, :e, :f]}
=> {:key1=>[:a, :b, :c], :key2=>[:d, :e, :f]}
h.inverse
=> {:a=>:key1, :b=>:key1, :c=>:key1, :d=>:key2, :e=>:key2, :f=>:key2}
The code looks like this:
# this doesn't looks quite as elegant as the other solutions here,
# but if you call inverse twice, it will preserve the elements of the original hash
# true inversion of Ruby Hash / preserves all elements in original hash
# e.g. hash.inverse.inverse ~ h
class Hash
def inverse
i = Hash.new
self.each_pair{ |k,v|
if (v.class == Array)
v.each{ |x|
i[x] = i.has_key?(x) ? [k,i[x]].flatten : k
}
else
i[v] = i.has_key?(v) ? [k,i[v]].flatten : k
end
}
return i
end
end
h = {:key1 => [:a, :b, :c], :key2 => [:d, :e, :f]}
=> {:key1=>[:a, :b, :c], :key2=>[:d, :e, :f]}
h.inverse
=> {:a=>:key1, :b=>:key1, :c=>:key1, :d=>:key2, :e=>:key2, :f=>:key2}
One way to achieve what you're looking for:
arr = [{["k1"] => ["a", "b", "c"]}, {["k2"] => ["d", "e", "f"]}]
results_arr = []
arr.each do |hsh|
hsh.values.flatten.each do |val|
results_arr << { [val] => hsh.keys.first }···
end
end
Result: [{["a"]=>["k1"]}, {["b"]=>["k1"]}, {["c"]=>["k1"]}, {["d"]=>["k2"]}, {["e"]=>["k2"]}, {["f"]=>["k2"]}]

Resources