Reputation: 313
I have two arrays:
a=["joe","mark","mark","wilson","joe"]
b=[1,2,2,3,4]
I need a hash:
h={"joe"=>[1,4],"mark"=>[2,2],"wilson"=>[3]}
The main problem is the keys repeat and can have multiple values. I have tried zip
, inject
, and map
but I'm not able to come even remotely close to what I need. I need to use Ruby.
Upvotes: 0
Views: 61
Reputation: 62698
The question's been answered well, but here's my code golfing attempt :)
a=["joe","mark","mark","wilson","joe"]
b=[1,2,2,3,4]
a.zip(b).reduce({}) {|o,(k,v)| (o[k] ||= []) << v; o}
The general idea is pretty close to the Tin Man's, but rather than several intermediate steps, it just takes the zipped arrays and reduces them into the target form directly.
Upvotes: 0
Reputation: 110755
There are several ways to do this. Here are two.
#1
a = ["joe","mark","mark","wilson","joe"]
b = [1,2,2,3,4]
Hash[a.zip(b)
.group_by(&:first)
.values
.map { |arr| [arr.first.first, arr.map(&:last)] }]
#=> { "joe"=>[1, 4], "mark"=>[2, 2], "wilson"=>[3] }
With Ruby 2.1:
a.zip(b)
.group_by(&:first)
.values
.map { |arr| [arr.first.first, arr.map(&:last)] }
.to_h
#2
a.zip(b)
.each_with_object({}) { |(name,val),h|
h.update({name=>[val]}) { |_,ov,nv| ov+nv } }
=> {"joe"=>[1, 4], "mark"=>[2, 2], "wilson"=>[3]}
This second approach uses the form of Hash#update (a.k.a. merge!
) that takes a block.
Upvotes: 0
Reputation: 2568
There are 2 variants, the fastest and the shortest:
def fastest(a, b)
result = {}
i = 0
a.each do |elem|
result[elem] ? result[elem] << b[i] : result[elem] = [b[i]]
i += 1
end
result
end
def shortest(a, b)
a.zip(b).group_by{ |i,j| i }.map{ |k, v| [k, v.map(&:last)] }.to_h
end
And the benchmark results are:
require 'benchmark/ips'
Benchmark.ips do |x|
%w(fastest shortest).each do |method|
x.report(method) { send method, ["joe","mark","mark","wilson","joe"], [1,2,2,3,4] }
end
end
fastest 185435.9 (±19.9%) i/s - 892388 in 5.017412s
shortest 93222.0 (±20.1%) i/s - 445398 in 5.011817s
Upvotes: 0
Reputation: 160631
I'd use:
a=["joe","mark","mark","wilson","joe"]
b=[1,2,2,3,4]
a.zip(b).group_by{ |i,j| i }.map{ |k, v| [k, v.map(&:last)] }.to_h
# => {"joe"=>[1, 4], "mark"=>[2, 2], "wilson"=>[3]}
If you're not on Ruby 2.1+, you won't have Array#to_h
so, instead, you can do:
Hash[a.zip(b).group_by{ |i,j| i }.map{ |k, v| [k, v.map(&:last)] }]
# => {"joe"=>[1, 4], "mark"=>[2, 2], "wilson"=>[3]}
Here's what it's doing in some intermediate steps:
a.zip(b) # => [["joe", 1], ["mark", 2], ["mark", 2], ["wilson", 3], ["joe", 4]]
a.zip(b).group_by{ |i,j| i } # => {"joe"=>[["joe", 1], ["joe", 4]], "mark"=>[["mark", 2], ["mark", 2]], "wilson"=>[["wilson", 3]]}
a.zip(b).group_by{ |i,j| i }.map{ |k, v| [k, v.map(&:last)] } # => [["joe", [1, 4]], ["mark", [2, 2]], ["wilson", [3]]]
The star of the show here is group_by
, which collects all elements that match a given criteria, in this case, all array elements matching a given name. Once those are grouped, then it's just a case of cleaning up the resulting array and converting it to a Hash.
Upvotes: 2
Reputation: 8424
If what you're trying to achieve is have multiple values for a (same) key then storing the value as a double array and a bit of modification might work:
class Hash
def add_key_value(key, value)
if self.has_key?(key)
self[key] << value
else
self[key] = [value]
end
end
end
h = Hash.new()
h.add_key_value('joe', [1,2])
p h #=> {"joe"=>[[1, 2]]}
h.add_key_value('joe', [3])
p h #=> {"joe"=>[[1, 2], [3]]}
h.add_key_value('mark', [4,5])
p h #=> {"joe"=>[[1, 2], [3]], "mark"=>[[4, 5]]}
h.add_key_value('mark', [1,2])
p h #=> {"joe"=>[[1, 2], [3]], "mark"=>[[4, 5], [1, 2]]}
Upvotes: 0