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.
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:ObjectThat's because of your second problem:
The statement
puts(x,y = 1,2)
is parsed asputs(x, y=1, 2)
; that is, Ruby interprets it as passing three arguments toputs
:x
is the first,y=1
is the second, and2
is the third. Since thex
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
How to Switch to Ruby 1.9.3 Installed Using Homebrew
Rails Console: Reload! Not Reflecting Changes in Model Files? What Could Be Possible Reason
Using Www:Mechanize to Download a File to Disk Without Loading It All in Memory First
Ruby: Easiest Way to Filter Hash Keys
Difference Between $Stdout and Stdout in Ruby
Can't Install Pg Gem on Windows
Difference Between Gemfile and Gemfile.Lock in Ruby on Rails
Rvm 'Not Found' After Successful Usage and a Few Days Later
How to Remove Lines of Data in the Middle of a Text File with Ruby
Invalid Active Developer Path on MAC Os X After Installing Ruby
How to Match Accented Characters With a Regex
Rails Paperclip How to Delete Attachment
How to Stub Things in Minitest
Understanding Ruby and Os I/O Buffering