Ruby hash of arrays from array - ruby

I have the following array
a=[["kvm16", "one-415"], ["kvm16", "one-416"], ["kvm5", "one-417"]]
I would like to convert this to a hash that looks like this
{"kvm5"=>["one-417"], "kvm16"=>["one-417", "one-416"]}
everything I have tried squashes the value.
v=Hash[ a.collect do |p| [p[0], [ p[1] ] ] end ]
=> {"kvm5"=>["one-417"], "kvm16"=>["one-416"] }
I was thinking I could check to see if v[p[0]] is an array, but the variable isn't defined inside this block.
Is there a clean way to accomplish what I am looking for?

Yeah, you have to do it yourself, I'm afraid.
a = [["kvm16", "one-415"], ["kvm16", "one-416"], ["kvm5", "one-417"]]
h = a.each_with_object({}) do |(k, v), memo|
(memo[k] ||= []) << v
end
h # => {"kvm16"=>["one-415", "one-416"], "kvm5"=>["one-417"]}
Or, if you're on older ruby (1.8.x):
h = {}
a.each do |k, v|
(h[k] ||= []) << v
end
h # => {"kvm16"=>["one-415", "one-416"], "kvm5"=>["one-417"]}

Let's see some functional approaches. This is a common abstraction, you can find it as Enumerable#map_by in Facets:
require 'facets'
hs.map_by { |k, v| [k, v] }
#=> {"kvm16"=>["one-415", "one-416"], "kvm5"=>["one-417"]}
This pattern replaces the cumbersome group_by + map + Hash:
Hash[hs.group_by(&:first).map { |k, gs| [k, gs.map(&:last)] }]
#=> {"kvm16"=>["one-415", "one-416"], "kvm5"=>["one-417"]}

this has it. in the face of not having each_with_object in my version of ruby and some googleze I arrived at
{}.tap{ |h| items.each{ |item| h[item.id] = item } }
=> {"kvm5"=>["one-417"], "kvm16"=>["one-415", "one-416"]}
mucho thanks to Sergio for getting me thinking along that line

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}

Is there a better solution to partition a hash into two hashes?

I wrote a method to split a hash into two hashes based on a criteria (a particular hash value). My question is different from another question on Hash. Here is an example of what I expect:
h={
:a => "FOO",
:b => "FOO",
:c => "BAR",
:d => "BAR",
:e => "FOO"
}
h_foo, h_bar = partition(h)
I need h_foo and h_bar to be like:
h_foo={
:a => "FOO",
:b => "FOO",
:e => "FOO"
}
h_bar={
:c => "BAR",
:d => "BAR"
}
My solution is:
def partition h
h.group_by{|k,v| v=="FOO"}.values.collect{|ary| Hash[*ary.flatten]}
end
Is there a clever solution?
There's Enumerable#partition:
h.partition { |k, v| v == "FOO" }.map(&:to_h)
#=> [{:a=>"FOO", :b=>"FOO", :e=>"FOO"}, {:c=>"BAR", :d=>"BAR"}]
Or you could use Enumerable#each_with_object to avoid the intermediate arrays:
h.each_with_object([{}, {}]) { |(k, v), (h_foo, h_bar)|
v == "FOO" ? h_foo[k] = v : h_bar[k] = v
}
#=> [{:a=>"FOO", :b=>"FOO", :e=>"FOO"}, {:c=>"BAR", :d=>"BAR"}]
I don't think there is a clever one liner, but you can make it slightly more generic by doing something like:
def transpose(h,k,v)
h[v] ||= []
h[v] << k
end
def partition(h)
n = {}
h.map{|k,v| transpose(n,k,v)}
result = n.map{|k,v| Hash[v.map{|e| [e, k]}] }
end
which will yield
[{:a=>"FOO", :b=>"FOO", :e=>"FOO"}, {:c=>"BAR", :d=>"BAR"}]
when run against your initial hash h
Edit - TIL about partition. Wicked.
Why not use builtin partition, which is doing almost exactly what you are looking for?
h_foo, h_bar = h.partition { |key, value| value == 'FOO' }
The only downside is that you will get arrays instead of hashes (but you already know how to convert that). In ruby 2.1+ you could simply call .map(&:to_h) at the end of call chain.

Array in value of hash

How to push inputs into a value of a hash? My problem is that I got multiple keys and all of them reference arrays.
{"A"=>["C"], "B"=>["E"], "C"=>["D"], "D"=>["B"]}
How can I push another String onto one of these? For example I want to add a "Z" to the array of key "A"?
Currently I either overwrite the former array or all data is in one.
Its about converting a Array ["AB3", "DC2", "FG4", "AC1", "AF4"] into a hash with {"A"=>["B", "C", "F"]}.
Any command <<, push, unshift will do a job
if h["A"]
h["A"] << "Z"
else
h["A"] = ["Z"]
end
You said your original problem is converting the array ["AB3", "DC2", "FG4", "AC1", "AF4"] into the hash {"A"=>["B", "C", "F"]}, which can be done like this:
Hash[a.group_by { |s| s[0] }.map { |k, v| [k, v.map { |s| s[1] }] }]
Or like this:
a.inject(Hash.new{|h, k| h[k]=[]}) { |h, s| h[s[0]] << s[1] ; h }
Note that Hash.new{|h, k| h[k]=[]} creates an array with a default value of [] (an empty array), so you'll always be able to use << to add elements to it.
Better approach:
Add a new class method in Hash as below:
class Hash
def add (k,v)
unless self.key?k
self[k] = [v]
else
self[k] = self[k] << v
end
self
end
end
h={}
h.add('A','B') #=> {"A"=>["B"]}
h.add('A','C') #=> {"A"=>["B", "C"]}
h.add('B','X') #=> {"A"=>["B", "C"], "B"=>["X"]}
Done.
This can be even more idiomatic according to your precise problem; say, you want to send multiple values at once, then code can be DRY-ed to handle multiple arguments.
Hope this helps.
All the best.

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 to Array converting

I need this Hash
{"peter" => ["apple", "orange", "mango"], "sandra" => ["flowers", "bike"]}
convert to this Array:
[["peter", "apple"], ["peter", "orange"], ["peter", "mango"], ["sandra", "flowers"], ["sandra", "bike"]]
Now I have got this solution
my_hash.inject([]){|ar, (k,v)| ar << v.map{|c| [k,c]}}.flatten(1)
But I believe here is more elegant solution with those zip or transpose magick :)
You are right to be suspicious about Enumerable#inject solutions. In Ruby, inject/reduce is somewhat abused, we must be careful and choose the right abstraction (map, select, zip, flatten...) if they fit the problem at hand. In this case:
h = {"peter" => ["apple", "orange", "mango"], "sandra" => ["flowers", "bike"]}
h.map { |k, vs| vs.map { |v| [k, v] } }.flatten(1)
#=> [["peter", "apple"], ["peter", "orange"], ["peter", "mango"], ["sandra", "flowers"], ["sandra", "bike"]]
But if you want to use Enumerable#zip don't let anyone stop you ;-)
h.map { |k, vs| [k].cycle(vs.size).zip(vs) }.flatten(1)
And as #steenslag says, also:
h.map { |k, vs| [k].product(vs) }.flatten(1)
So at the end we can write:
h.flat_map { |k, vs| [k].product(vs) }
h.inject([]){|a,(k,vs)| a+vs.map {|v| [k,v]}}
You could also use this version
h.inject([]){|a,(k,vs)| a+=vs.map {|v| [k,v]}}
Which is most efficient because it use the same list rather than creating a new one at each iteration. However it feels wrong (for me) to use inject and modify a variable in place. An each version would do the same.
a = []; h.each {|k,vs| a+=vs.map {|v| [k,v]}}
It's slightly shorter and as expressive.
With zip
hash.inject([]){ |ar, (k,v)| ar << ([k]*v.size).zip(v) }
A plausible solution using transpose too:
[
hash.keys.map{|k| [k]*hash[k].size }.flatten,
hash.keys.map{|k| hash[k] }.flatten
].transpose
Take into account that:
hash.keys should return the keys in the same order in both cases, so don't use it in other language unless you are sure of this.
I would go with the first option.

Resources