-1

I have a hash, whose values are an array of size 1:

hash = {:start => [1]}

I want to unpack the arrays as in:

hash.each_pair{ |key, value| hash[key] = value[0] } # => {:start=>1}

and I thought the *-operator as in the following would work, but it does not give the expected result:

hash.each_pair{ |key, value| hash[key] = *value } # => {:start=>[1]}

Why does *value return [1] and not 1?

4

3 回答 3

4

Because the []= method applied to hash takes only one argument in addition to the key (which is put inside the [] part), and a splatted/expanded array, which is in general a sequence of values (which coincidentally happens to be a single element in this particular case) cannot be directly accepted as the argument as is splatted. So it is accepted by the argument of []= as an array after all.

In other words, an argument (of the []= method) must be an object, but splatted elements (such as :foo, :bar, :baz) are not an object. The only way to interpret them as an object is to put them back into an array (such as [:foo, :bar, :baz]).

Using the splat operator, you can do it like this:

hash.each_pair{|key, value| hash.[]= key, *value}
于 2015-09-15T11:15:41.163 回答
4

sawa and Ninigi already pointed out why the assignment doesn't work as expected. Here's my attempt.

Ruby's assignment features work regardless of whether you're assigning to a variable, a constant or by implicitly invoking an assignment method like Hash#[]= with the assignment operator. For the sake of simplicity, I'm using a variable in the following examples.

Using the splat operator in an assignment does unpack the array, i.e.

a = *[1, 2, 3]

is evaluated as:

a = 1, 2, 3

But Ruby also allows you to implicitly create arrays during assignment by listing multiple values. Therefore, the above is in turn equivalent to:

a = [1, 2, 3]

That's why *[1] results in [1] - it's unpacked, just to be converted back to an array.

Elements can be assigned separately using multiple assignment:

a, b = [1, 2, 3]
a #=> 1
b #=> 2

or just:

a, = [1, 2, 3]
a #=> 1

You could use this in your code (note the comma after hash[key]):

hash = {:start => [1]}
hash.each_pair { |key, values| hash[key], = values }
#=> {:start=>1}

But there's another and more elegant way: you can unpack the array by putting parentheses around the array argument:

hash = {:start => [1]}
hash.each_pair { |key, (value)| hash[key] = value }
#=> {:start=>1}

The parentheses will decompose the array, assigning the first array element to value.

于 2015-09-15T14:34:01.103 回答
3

Because Ruby is acting unexpectedly smart here.

True, the splash operator will "fold" and "unfold" an array, but the catch in your code is what you do with that fanned value.

Take this code into account:

array = ['a', 'b']
some_var = *array
array # => ['a', 'b']

As you can see the splat operator seemingly does nothing to your array, while this:

some_var, some_other_var = *array
some_var # => "a"
somet_other_var # => "b"

Will do what you'd expect it does.

It seems ruby just "figures" if you splat an array into a single variable, that you want the array, not the values.

EDIT: As sawa pointed out in the comments, hash[key] = is not identical to variable =. []= is an instance Method of Hash, with it's own C-Code under the hood, which COULD (in theory) lead to different behaviour in some instances. I don't know of any example, but that does not mean there is none. But for the sake of simplicity, we can asume that the regular variable assignment behaves exactly identical to hash[key] =.

于 2015-09-15T11:18:45.157 回答