views:

303

answers:

6

I am trying to store values from an array, to a hash (array value is the key, value just 0). Here is my code. Any ideas?

[1, 2, 3, 4].inject({}) {|result, e| result[e] = 0}

This is the error I am getting.

oMethodError: undefined method `[]=' for 0:Fixnum
    from (irb):1
    from (irb):1:in `inject'
    from (irb):1:in `each'
    from (irb):1:in `inject'
    from (irb):1
    from :0
+2  A: 

The return value of result[e] = 0 is 0, not result. You have to do:

[1, 2, 3, 4].inject({}) {|result, e| result[e] = 0; result}

(or use merge instead of []= or use each instead of inject)

sepp2k
+8  A: 

The issue is that result[e] = 0 returns the result of the operation, namely 0, and that is carried to the next iteration where you try to call []= on it. You can get past this by doing the following:

[1, 2, 3, 4].inject({}) {|result, e| result[e] = 0; result }

ezpz
+8  A: 

The "; result" thing works fine, but as a matter of taste, I prefer this way:

[1,2,3,4].inject({}) {|result,e| result.merge!(e=>0)}

If this is in performance-critical code, though, taste has its price. Here's a quick benchmark doing this operation a million times.

In Ruby 1.8.5

merge: 22s
merge!: 14s
; result: 9s

In Ruby 1.9.1

merge: 18s
merge!: 11s
; result: 5s
glenn mcdonald
I suspect the penalty comes from creating all the intermediate `e=>0` dictionaries.
Chuck
A: 

You really ought to use merge! instead of merge in this case. There's no reason to create a new hash on every iteration.

[1,2,3,4].inject({}) {|result,e| result.merge!(e=>0)}
guitarzan
A: 

It'd take two lines, but you could also do

hash = {}
[1,2,3,4].each{|key| hash[key] = 0}
Andrew Grimm
This would be the fastest method, assuming the problem is really as simple as the example, and there's no other reason to want to use inject...
glenn mcdonald
A: 

If your array is a simple array that doesn't have other arrays nested, then I would use the array-dereferencing hash construction method:

Hash[*[1,2,3,4].zip(Array.new(4,0)).flatten]

Or probably more generalized:

If there are nested arrays, you need to only flatten one level deep, and since there isn't a ruby command for flatten_once, you'll just have to do it manually through concatenation. The upside is you can interleave the zeros during concatenation so you no longer have to zip it:

Hash[*[1,2,3,4].inject([]){|s,x| s.concat([x,0])}]

Quickly benching 1000000 iterations of each on my machine in Ruby 1.8.6 gives me:

aforementioned merge! method:       34s
Hash[*...).concat([x,0])}] method:  25s
aforementioned result! method:      22s
Hash[*...).flatten] method:         15s
Bert
Strange, I get different timings than yours. Your concat method is slower than merge!, and your flatten example is slower than the "; result" version. Are you sure about your numbers?
glenn mcdonald
I'm no expert on benchmarking code, so let me know if I did something wrong, but I basically did this:def merg a=Time.now 1000000.times do b=[1,2,3,4].inject({}) {|result,e| result.merge!(e=>0)} end puts "merge: " + (Time.now-a).to_sendand basically replaced the do ... end contents with what I was testing. I did repeat and got pretty much same results as before. Perhaps it has something to do with doing all this inside a VMware/ubuntu instance? I don't know.
Bert
sorry for the unformatted comment there, still a newb at SO posting...
Bert
Maybe differences in OS versions? I'm on OS X. With Ruby 1.8.6 I get: merge 19s, merge! 12s, flatten 11s, concat 10s, ;result 7.7s, each 4.6s. With Ruby 1.9.1 it's merge 16s, flatten 11s, merge! 10s, concat 6.5s, ;result 4.4s, each 3.9s. The fact that I get concat faster than flatten, and you get the reverse, makes me think one of us is screwing something up! But that's enough of this...
glenn mcdonald