Forum: Ruby About array to hash conversion

Posted by Holden Holden (holdenc)
on 2008-01-09 22:31
Hi,

I want to convert an array (with pairs [key,value]) to a hash. After
googling a bit I got:

1) Hash[*array.flatten]

But this is not safe as Array#flatten is recursive.

2) array.inject({}) { |m, e| m[e[0]] = e[1]; m }

Too ugly to consider.

3) Hash[*array.flatten(1)]

That's my own attempt. It only works for Ruby > 1.9.

But I am still wondering why...

- Array objects have no "to_h" method?
- Hasy[*array] don't accept the [key,value] pairs (as Hash#to_a calls
return)?

thanks,
holden
Posted by Jeremy Weiskotten (jeremy_weiskotten)
on 2008-01-10 00:59
You could open up the Array class and add a to_hash method that uses the 
ugly inject idiom to make it more convenient.
Posted by Jeremy Weiskotten (jeremy_weiskotten)
on 2008-01-10 01:00
class Array
  def to_hash
    inject({}) { |m, e| m[e[0]] = e[1]; m }
  end
end
Posted by yermej (Guest)
on 2008-01-10 01:05
(Received via mailing list)
On Jan 9, 3:31 pm, Holden Holden <pyar...@gmail.com> wrote:
>
> return)?
These would only work in one specific case. What would this do?

[1, 2, 3].to_h
Posted by Wally T Terrible (Guest)
on 2008-01-10 01:11
(Received via mailing list)
I just did this myself. Because I knew how the data was stored in the
array, I was able to control how I put it into the hash. What I did was
this:
    myArray=['255','10','258','08','154','34'] #(etc)
    myHash={}
    c=0
    while c<=myArray.size
        myHash.store(myArray[c],myArray[c+1])
    c+=2
    end

I don't suggest that this absolutely the best way to do it, and it may
not work if you don't know your data structure in the array but it was a
quick and easy way for me to solve my problem.
Posted by Daniel Sheppard (Guest)
on 2008-01-10 06:19
(Received via mailing list)
> I want to convert an array (with pairs [key,value]) to a hash. After
> googling a bit I got:
> 
> 1) Hash[*array.flatten]
> 
> But this is not safe as Array#flatten is recursive.
> 
> 2) array.inject({}) { |m, e| m[e[0]] = e[1]; m }
> 
> Too ugly to consider.

array.inject({}) {|h,(k,v)| h[k]=v; h}

is still ugly, but perhaps you may consider it.

Also, arrays of arrays can be used directly in a hash-like manner:

array = [[1,2],[3,4]]
array.assoc(3)
=> [3,4]

which may be useful, depending on what you're doing (it won't have hash
performance, but it may do)

Dan.
Posted by Robert Klemme (Guest)
on 2008-01-10 09:13
(Received via mailing list)
2008/1/9, Holden Holden <pyarnau@gmail.com>:
> I want to convert an array (with pairs [key,value]) to a hash. After
> googling a bit I got:
>
> 1) Hash[*array.flatten]
>
> But this is not safe as Array#flatten is recursive.
>
> 2) array.inject({}) { |m, e| m[e[0]] = e[1]; m }
>
> Too ugly to consider.

You can do this:

array.inject({}) {|ha, (k, v)| ha[k] = v; ha}

Is this nicer according to your standards?

Kind regards

robert
Posted by Holden Holden (holdenc)
on 2008-01-10 10:35
yermej wrote:
> On Jan 9, 3:31�pm, Holden Holden <pyar...@gmail.com> wrote:
>>
>> return)?
> These would only work in one specific case. What would this do?
> 
> [1, 2, 3].to_h

That's why I prefer [key, value] pairs, to avoid any ambiguity.

However, a theorical [1,2,3].to_h should give the same error than 
Hash[*array] gives:

irb(main):002:0> Hash[*[1,2,3]]
ArgumentError: odd number of arguments for Hash
Posted by Robert Klemme (Guest)
on 2008-01-10 10:43
(Received via mailing list)
2008/1/10, Holden Holden <pyarnau@gmail.com>:
> However, a theorical [1,2,3].to_h should give the same error than
> Hash[*array] gives:
>
> irb(main):002:0> Hash[*[1,2,3]]
> ArgumentError: odd number of arguments for Hash

There is however one issue with a general Array to Hash conversion
routine: there are different ways to convert a Hash and to me it's not
clear which one should be the default.  Options I can see so far:

1. if array contains pairs use them as key and value for the Hash

2. if array is flat use consecutive entries for key and value (fails
with odd number of entries)

3. use indexes as keys and entries as values, e.g.

ar.to_enum(:each_with_index).inject({}) {|h, (v,k)| h[k]=v;h}

The last one has not been mentioned so far but there might actually be
uses for this (i.e. when one wants to replace an Array with a Hash).
Dunno how often this might occur though.

There are probably other valid default conversion candidates, too.

Kind regards

robert
Posted by Holden Holden (holdenc)
on 2008-01-10 10:53
Robert Klemme wrote:

> You can do this:
> 
> array.inject({}) {|ha, (k, v)| ha[k] = v; ha}
> 
> Is this nicer according to your standards?

Thanks. Yes, it's clearer to separate key/value on the args. I must 
clarify that by "too ugly to consider" I meant "I imagine there is a 
nicer way to do it".

Inject solutions are fine, short and clear, but I am still puzzled why 
Hash[*array] and Hash#to_a are not specular (maybe this is a question 
for a ruby-dev forum, though)

regards
Posted by Stefan Rusterholz (apeiros)
on 2008-01-10 17:54
Holden Holden wrote:
> yermej wrote:
>> On Jan 9, 3:31�pm, Holden Holden <pyar...@gmail.com> wrote:
>>>
>>> return)?
>> These would only work in one specific case. What would this do?
>> 
>> [1, 2, 3].to_h
> 
> That's why I prefer [key, value] pairs, to avoid any ambiguity.
> 
> However, a theorical [1,2,3].to_h should give the same error than 
> Hash[*array] gives:
> 
> irb(main):002:0> Hash[*[1,2,3]]
> ArgumentError: odd number of arguments for Hash

I disagree. Array#to_hash should work so that hash.to_a.to_hash == hash, 
in other words it should create a hash only if it contains key value 
tuples.

> array.inject({}) {|ha, (k, v)| ha[k] = v; ha}

There's absolutely no advantage using inject like this.
h={}; array.each { |k,v| h[k]=v }
Is less code and faster.

> ar.to_enum(:each_with_index).inject({}) {|h, (v,k)| h[k]=v;h}
Same here
h={}; array.each_with_index { |k,v| h[k]=v }

Personally I use the following code in my lib:
# create a hash from an array with keys and an array with values
# you can set default or provide a block just as with Hash::new
def Hash.zip(keys, values, default=nil, &block)
  hash = block_given? ? Hash.new(&block) : Hash.new(default)
  keys.zip(values) { |k,v| hash[k]=v }
  hash
end

class Array
# create a hash from an array of [key,value] tuples
# you can set default or provide a block just as with Hash::new
# Note: if you use [key, value1, value2, value#], hash[key] will
# be [value1, value2, value#]
  def to_hash(default=nil, &block)
    hash = block_given? ? Hash.new(&block) : Hash.new(default)
    each { |(key, *value)| hash[key]=*value }
    hash
  end
end

Regards
Stefan
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.