ashwin shanker
ashwin shanker

Reputation: 313

Arrays to hashes

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

Answers (5)

Chris Heald
Chris Heald

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

Cary Swoveland
Cary Swoveland

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

Alexander
Alexander

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

the Tin Man
the Tin Man

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

daremkd
daremkd

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

Related Questions