1

I have an array of about 860 items, I would like to turn it into a hash, the array structure is (Key1, value 1, value2, value 3, Key2, value 1, value 2, value 3........... etc etc) except in real life it looks like (Peter, 150, 39, 345, John, 123, 450, 402, Mary, 145, 345, 506............). Original these are four arrays which I have transposed together, so I could reach the same end goal by starting from 4 independent arrays.

I would like

Hash {Peter=> [150, 39, 345], John=>[123,450,402], Mary => [145,345,506] etc etc

I feel there should be a nice neat way to do this, but it eludes me, possibly because I don't know enough ruby.

Artjom B.
  • 61,146
  • 24
  • 125
  • 222
D133p53
  • 59
  • 7
  • If the value arrays are all the same length couldn't you just iterate and add each item to the appropriate hash entry? You don't really state what the original arrays are, but it seems like it'd be easier to just skip the array concatenation. – Dave Newton Apr 08 '17 at 03:25

5 Answers5

4

One more way:

l = ['Peter', 150, 39, 345, 'John', 123, 450, 402, 'Mary', 145, 345, 506]

Hash[*l.each_slice(4).flat_map { |a, *b| [a, b] }]
#=> {"Peter"=>[150, 39, 345], 
#    "John" =>[123, 450, 402], 
#    "Mary" =>[145, 345, 506]}

If you already have arrays, solution will be even more simple:

arrays = [array_1, array_2, array_3]
Hash[*arrays.flat_map {|a, *b| [a, b]} ]
#=> {"Peter"=>[150, 39, 345], "John"=>[123, 450, 402], "Mary"=>[145, 345, 506]}
Ilya
  • 13,337
  • 5
  • 37
  • 53
3
arr = ['Peter', 150, 39, 345, 'John', 123, 450, 'Mary', 145, 345, 506, 222]

arr.slice_before {|a| String === a }.
    each_with_object({}) {|(f,*rest), h| h[f] = rest}
  #=> {"Peter"=>[150, 39, 345], "John"=>[123, 450], "Mary"=>[145, 345, 506, 222]} 

Enumerable#slice_before made its debut in Ruby v2.3. One could alternatively use Enumerable#slice_when, which was new in v2.2.

Here's are a couple more ways that work with earlier versions of Ruby.

arr.chunk { |e| String === e }.
    each_slice(2).
    with_object({}) { |((_,(k)),(_,v)), h| h[k] = v }

and

a = []
enum = arr.to_enum
loop do
  o = enum.next
  (String === o) ? a << [o,[]] : a.last.last << o
end
Hash[a]
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
1

If you know that the data is always in sets of 4:

l = ['Peter', 150, 39, 345, 'John', 123, 450, 402, 'Mary', 145, 345, 506]
h = {}
l.each_slice(4).each do |chunk|
    h[chunk[0]] = chunk[1..chunk.length]
end

Alternately, if you know the keys are always strings:

l = ['Peter', 150, 39, 345, 'John', 123, 450, 402, 'Mary', 145, 345, 506]
h = {}
current_key = ""
l.each do |item|
    if item.is_a? String
        current_key = item
        h[item] = []
    else 
        h[current_key] << item 
    end 
end

Basically, you need to work out how to slice the array appropriately and then loop through.

Also if you've already got them as separate arrays you could easily just:

array_1 = ['Peter', 150, 39, 345]
array_2 = ['John', 123, 450, 402]
array_3 = ['Mary', 145, 345, 506]
h = {}    

[array_1,array_2, array_3].each do |a|
    h[a[0]] = a[1:a.length]
end

# One line version:
[array_1,array_2, array_3].map{|a| h[a[0]] = a[1..a.length]}

Which I'd argue is the cleanest method.

Darkstarone
  • 4,590
  • 8
  • 37
  • 74
1

Original these are four arrays which I have transposed together, so I could reach the same end goal by starting from 4 independent arrays.

I assume you have something like this to start with:

a = ["Peter", "John", "Mary"]
b = [150, 123, 145]
c = [39, 450, 345]
d = [345, 402, 506]

You can combine the "value" arrays via zip:

values = b.zip(c, d)
#=> [[150, 39, 345], [123, 450, 402], [145, 345, 506]]

And add the "key" array via another zip:

a.zip(values)
#=> [["Peter", [150, 39, 345]], ["John", [123, 450, 402]], ["Mary", [145, 345, 506]]]

This is already the correct structure, so we can just call to_h:

a.zip(values).to_h
#=> {"Peter"=>[150, 39, 345], "John"=>[123, 450, 402], "Mary"=>[145, 345, 506]}

Of course you don't need the intermediate variable, a.zip(b.zip(c, d)).to_h returns the same result.

Stefan
  • 109,145
  • 14
  • 143
  • 218
0

Try this

l = ['Peter', 150, 39, 345, 'John', 123, 450, 402, 'Mary', 145, 345, 506]

l.each_slice(4).to_a.inject({}){|acc, temp| acc[temp.first]=temp.last(3); acc}

#=>{"Peter"=>[150, 39, 345], "John"=>[123, 450, 402], "Mary"=>[145, 345, 506]}
Rahul Patel
  • 1,386
  • 1
  • 14
  • 16