2

I was looking for a way to convert two arrays into a single hash. I found something like this :

a1 = [1,2,3]
a2 = [?A, ?B, ?C]
Hash[*a1.zip(a2).flatten]

I thought that this syntax was a bit weird, because Hash[a1.zip a2] would do exactly the same. But more than that, I don't understand the need for the * operator.

I know that it turns objects into arrays, or something alike (but not in the same way [] does, apparently).

When I execute :

a = a1.zip(a2).flatten
 => [1, "A", 2, "B", 3, "C"]
a = *a1.zip(a).flatten
 => [1, "A", 2, "B", 3, "C"]

Nothing really happens, and for what I know of the * operator, this seems to be the normal behavior.

So, why does

Hash[*a1.zip(a2).flatten]
 => {1=>"A", 2=>"B", 3=>"C"}
Hash[a1.zip(a).flatten]
 => {}

Return different values, given that the parameters seem identical ?

I guess I must be missing something about the * operator.

Thanks.

Gabriel Dehan
  • 1,107
  • 9
  • 15

3 Answers3

4

When the * operator is used with arrays like that it is called the splat operator.

Think of it as an operator that removes the first level of brackets around an array. This is quite useful because you can turn arrays into argument lists:

def stuff(x, y, z)
end

a = [1, 2, 3]
stuff(*a) # x,y,z gets assigned 1,2,3

The same thing works with Hash[]. The [] operator on Hash accepts as arguments:

  1. An argument list of key-value pairs:
    Hash["a", 1, "b", 2] #=> { "a" => 1, "b" => 2 }
  2. An array or array pairs representing key-values:
    Hash[ [["a", 1], ["b", 2]] ] #=> { "a" => 1, "b" => 2 }

Hash[] not does NOT accept a plain flat array as arguments:

Hash[ ["a", 1, "b", 2] ] #=> {}

So with this in mind, plus our understanding what the splat operator does you can now see what is happening:

paired_array = a1.zip(a2)
=> [[1, "A"], [2, "B"], [3, "C"]]

plain_array = a1.zip(a2).flatten
=> [1, "A", 2, "B", 3, "C"]

# Per rule 2 above we know this works
Hash[paired_array]
=> {1=>"A", 2=>"B", 3=>"C"} 

# This won't work
Hash[plain_array]
=> {}

# But if we turn the plain_array into an argument list, 
# then we know per rule 1 above that this will work
Hash[*plain_array]
=> {1=>"A", 2=>"B", 3=>"C"} 

Now then you might be wondering what the hey is happening when you do:

a = *plain_array
=> [1, "A", 2, "B", 3, "C"]

Since we know the splat operator effectively strips the brackets, we get this:

a = 1, "A", 2, "B", 3, "C"

...which funnily enough is valid Ruby code and just creates an array again.

You can read more fun stuff about the splat operator in the rubyspec test case for the splat operator.

Casper
  • 33,403
  • 4
  • 84
  • 79
2

I think there's a mistake in your example, it should be like this:

Hash[a1.zip(a2).flatten] #=> {}
Hash[*a1.zip(a2).flatten] #=> {1=>"A", 2=>"B", 3=>"C"}

The splat operator in the assign mode converts an array to multiple arguments:

duck, cow, pig = *["quack","mooh","oing"] #=> ["quack","mooh","oing"]

Actually it's identical to

duck, cow, pig = ["quack","mooh","oing"] #=> ["quack","mooh","oing"]

But from the documentation you can see that Hash[...] receives multiple arguments, so the splat operator helps to assign each of those multiple arguments.

megas
  • 21,401
  • 12
  • 79
  • 130
1

It's not that mysterious:

a1 = [1,2,3]
a2 = [?A, ?B, ?C]

p Hash[*a1.zip(a2).flatten] #{1=>"A", 2=>"B", 3=>"C"}

The * converts the array to a mere list (of arguments).

But why wasn't this syntax used?

p Hash[a1.zip(a2)]# {1=>"A", 2=>"B", 3=>"C"}

Well, it is new since Ruby 1.9.2. Your example is probably older.

steenslag
  • 79,051
  • 16
  • 138
  • 171