Parallel Assignment Operator in Ruby

Parallel Assignment operator in Ruby

The problem with doing the assignment in 2 separate statements is that i2 = i1 + i2 will then use the new value of i1 rather than the previous value required to correctly generate the Fibonacci sequence.

When you use parallel assignment all of the expressions on the right hand side are evaluated first and then assigned to the receiving variables on the left hand side.

Parallel assignment isn't a special operator. Essentially what is on the right hand side acts as an Array where if we list multiple variables on the left hand side then the array is unpacked and assigned into the respective variables.

Here are some more examples:

irb(main):020:0> a = 1, 2 # assign to a single variable
=> [1, 2]
irb(main):021:0> a
=> [1, 2]
irb(main):022:0> a, b = 1, 2 # unpack into separate variables
=> [1, 2]
irb(main):023:0> a
=> 1
irb(main):024:0> b
=> 2
irb(main):025:0> a, b = [1, 2, 3] # 3 is 'lost' as no receiving variable
=> [1, 2, 3]
irb(main):026:0> a
=> 1
irb(main):027:0> b
=> 2
irb(main):028:0> first, *rest = [1, 2, 3] # *rest consumes the remaining elements
=> [1, 2, 3]
irb(main):029:0> first
=> 1
irb(main):030:0> rest
=> [2, 3]

It is a useful feature of ruby, for example it facilitates having methods that return multiple values e.g.

def sum_and_difference(a, b)
a + b, a - b
end

sum, difference = sum_and_difference 5, 3

In Java the closest thing would be to have a method that returned int[] but if we wanted to return a string and a number we'd need to create a little POJO to act as struct for the return value or return Object[] and clutter the code up with casts. See this other question that I answered recently for a more practical example.

why does ruby parallel assignment with array of strings returns string

a, b = ["ho", "hey"] 

a is assigned the first element of the array, which is the string "ho". Nothing weird.

a, b = ["blerg"], ["baz"]

a, b = [["blerg"], ["baz"]]

These two are the same, as you can see by their return values. So a is assigned the first element, which is an array with one element: ["blerg"].

Similarly,

c, d = "foo", "bar"

Is the same as

c, d = ["foo", "bar"]

Is it possible to use parallel assignment for keys in Ruby hash?

Firstly, this does not work as you expect:

sample = {}
sample[:alpha], sample[:beta], sample[:gamma] = 0

This will result in:

sample == { alpha: 0, beta: nil, gamma: nil }

To get the desired result, you could instead use parallel assignment:

sample[:alpha], sample[:beta], sample[:gamma] = 0, 0, 0

Or, loop through the keys to assign each one separately:

[:alpha, :beta, :gamma].each { |key| sample[key] = 0 }

Or, merge the original hash with your new attributes:

sample.merge!(alpha: 0, beta: 0, gamma: 0)

Depending on what you're actually trying to do here, you may wish to consider giving your hash a default value. For example:

sample = Hash.new(0)

puts sample[:alpha] # => 0
sample[:beta] += 1 # Valid since this defaults to 0, not nil

puts sample # => {:beta=>1}

Use underscore as variable in Parallel Assignment

In parallel assignment, we sometimes do two things :

  • ignore one element ( taking care of _)

You can reuse an underscore to represent any element you don’t care about:

a, _, b, _, c = [1, 2, 3, 4, 5]

a # => 1
b # => 3
c # => 5
  • ignore multiple elements ( taking care of * )

To ignore multiple elements, use a single asterisk — I’m going to call it a ‘naked splat’ for no better reason than that it sounds a bit amusing:

a, *, b = [1, 2, 3, 4, 5]

a # => 1
b # => 5

Read this blog post Destructuring assignment in Ruby to know more other related things.

Ruby parallel assignment

You have two problems.

  1. The space between puts and the ( prevents the parenthesized list from being interpreted as an argument list. Once you put a space after a method name, any argument list has to be outside of parentheses, and any parenthesized parts must be a legal expressions. In Ruby, (x,y,z) is not a legal single expression, so you get the above error.

    If you remove the space, you get this:

    > puts(x, y = 1, 2)    
    NameError: undefined local variable or method `x' for main:Object

    That's because of your second problem:

  2. The statement puts(x,y = 1,2) is parsed as puts(x, y=1, 2); that is, Ruby interprets it as passing three arguments to puts: x is the first, y=1 is the second, and 2 is the third. Since the x is not on the left side of an assignment and hasn't been defined yet, you get the above error.

    Use an extra set of parentheses to group the entire assignment together as one argument:

    > puts((x,y=1,2))    
    1
    2

But note that this is passing a single list that contains two items. It doesn't make a difference with puts, but it will for methods that distinguish between lists and individual parameters:

> def foo(a,b) puts "a=#{a},b=#{b}" end
> foo((x,y=1,2))
ArgumentError: wrong number of arguments (1 for 2)

So in that case you need one more piece of punctuation - the splat operator:

> foo(*(x,y=1,2))   
a=1, b=2

Interesting, but of little practical relevance, is the fact that once you've doubled the parentheses, you can put the space back if you want:

> puts ((x, y = 1, 2))    
1
2

But again, that turns them from argument-wrappers into just an extra expression-wrapper; you could put any number of parentheses around that without changing anything. That means that in the foo case, the splat operator has to go outside both sets of parentheses:

> foo (*(x,y=1,2))
SyntaxError: (irb):24: syntax error, unexpected tSTAR
> foo *((x,y=1,2))
a=1, b=2

It's generally considered bad style in Ruby to use the parenthesisless form when the first argument itself includes parentheses, however. Depending on your Ruby version you may get a warning about such statements, but even if you don't, it's better to use the fully-parenthesized version (with no space after the method name).

How to use multiple assignment with a ternary operator?

Here is the correct syntax for multiple assignment using a ternary operator:

foo, bar = baz ? [1, 2] : [3, 4]

The return values for true and false must be wrapped in brackets.

I hope this helps :)

Why does parallel assignment of a single empty array assign multiple nils?

I believe this example will help you understand the problem:

[1] pry(main)> a, b, c = [1,2]
=> [1, 2]
[2] pry(main)> a
=> 1
[3] pry(main)> b
=> 2
[4] pry(main)> c
=> nil

Now back to your problem, you are trying to assign the elements in an empty array to variables, as a result, the three variables all get nil value.

Parallel Assignment and Ranges

With splat operator

city, state, zip = *(0..2)

With cast to array

city, state, zip = (0..2).to_a


Related Topics



Leave a reply



Submit