Hash to Array converting - ruby

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.

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}

Ruby: cleaner returns from loop iteration methods

I find that I frequently have methods that iterate through an enumerable in order to return a different enumerable or a hash. These methods almost always look like this simplistic example:
def build_hash(array)
hash = {}
array.each do |item|
hash[ item[:id] ]= item
end
hash
end
This approach works works, but I've often wondered if there's a cleaner way to do this, specifically without having to wrap the loop in a temporary object so that the return is correct.
Does anyone know of an improved and/or cleaner and/or faster way to do this, or is this pretty much the best way?
Here are a few ways, considering your specific example
arr = [{:id => 1, :name => :foo}, {:id => 2, :name => :bar}]
Hash[arr.map{ |o| [o[:id], o] }]
arr.each_with_object({}){ |o, h| h[o[:id]] = o }
arr.reduce({}){ |h, o| h[o[:id]] = o; h }
arr.reduce({}){ |h, o| h.merge o[:id] => o }
# each of these return the same Hash
# {1=>{:id=>1, :name=>:foo}, 2=>{:id=>2, :name=>:bar}}
Well in this case, you can use inject and do something like this :
def build_hash(array)
array.inject({}) { |init, item| init[item[:id]] = item; init }
end
{}.tap { |h| array.each { |a| h[a[:id]] = a } }
Here is also a way how to convert Array into Hash.
list_items = ["1", "Foo", "2", "Bar", "3" , "Baz"]
hss = Hash[*list_items]
parameters must be even, otherwise a fatal error is raised, because an odd
number of arguments can’t be mapped to a series of key/value pairs.
{"1"=>"Foo", "2"=>"Bar", "3"=>"Baz"}
You can use ActiveSupport's index_by.
Your example becomes trivial:
def build_hash(array)
array.index_by{|item| item[:id]}
end
There is no really great way to build a hash in Ruby currently, even in Ruby 2.0.
You can use Hash[], although I find that very ugly:
def build_hash(array)
Hash[array.map{|item| [item[:id], item]}]
end
If we can convince Matz, you could at least:
def build_hash(array)
array.map{|item| [item[:id], item]}.to_h
end
There are other requests for new ways to create hashes.

Ruby hash of arrays from array

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

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 }

How can I reject values from one array based on a parallel array?

Given
number_strings = ["ichi", "ni", "san", "ku"]
fixnums = [1, 2, 3, 9]
how would I get a list of number_strings where the corresponding fixnum is not even?
number_strings.reject.each_with_index do |string, index|
fixnums.fetch(index).even?
end
works, as does
pairs = number_strings.zip(fixnums)
pairs.reject{|_, fixnum| fixnum.even?}.map(&:first)
but both are a bit verbose.
I think they all come out quite verbose really.
Hash[number_strings.zip(fixnums)].select { |k, v| v.odd? }.keys
I actually think your solutions are fine, the only alternative I can come up with is this and that's as verbose:
number_strings.zip(fixnums).map { |str, fn| str if fn.odd? }.compact
Use fix and select:
fixnums.zip(number_strings).select { |k, v| k.odd? }.map(&:last)
=> ["ichi", "san", "ku"]
In Ruby 1.9 you can do:
number_strings = ["ichi", "ni", "san", "ku"]
fixnums = [1, 2, 3, 9]
enum = fixnums.each
number_strings.select{ enum.next.odd? }
You are right to think it's a bit verbose, and that's because Ruby has no syntax for list-comprehensions. A simple abstracion like Enumerable#comprehend -if not as beautiful- comes pretty close (comprehend = map+compact):
number_strings.zip(fixnums).comprehend { |s, n| s if n.odd? }

Resources