ruby parallel assignment, step question
1: It's called parallel assignment. Ruby cares to create temporal variables and not override your variables with incorrect values. So this example:
cur, nxt = nxt, cur + nxt
is the same as:
tmp = cur + nxt
cur = nxt
nxt = tmp
bur more compact, without place to make stupid mistake and so on.
2: There are 2 step
methods in ruby core library. First is for Numeric
class (every numbers), so you could write:
5.step(100, 2) {}
and it starts at 5 and takes every second number from it, stops when reaches 100.
Second step
in Ruby is for Range
:
(5..100).step(2) {}
and it takes range (which has start and end) and iterates through it taking every second element. It is different, because you could pass it not necessarily numeric range and it will take every nth element from it.
Take a look at http://ruby-doc.org/core-1.8.7/index.html
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).
Parallel assignment in ruby working differently for two equivalent code fragments
They're different because in the first case you're pre-calculating the indices, while in the second you're calculating them dynamically, and the array changes between the first assignment and the second.
Technically both RHS evaluations are made before the LHS assignments are made, but both LHS assignments can't be made simultaneously, so in this case you're effectively seeing that
A, B = C, D
is equivalent to
A = C
B = D
So the first thing you do is...
ary[0...ary.index("B")] = ary[(ary.index("A") + 1)..-1]
in both cases. So ary is now
["B", "8", "5", "4", "6", "5", "6", "9", "7", "A"]
Now originally you'd calculated indx1 and indx2 as 0...4
and 14..-1
respectively, but now if you recalculated the indx1 and indx2 values you'd have:
indx1 = 0...ary.index("B") #=> 0...0
indx2 = (ary.index("A") + 1)..-1 #= 10..-1
In other words,
ary[indx2] = ary[indx1]
is no longer equivalent to
ary[(ary.index("A") + 1)..-1] = ary[0...ary.index("B")]
That is,
ary = ["B", "8", "5", "4", "6", "5", "6", "9", "7", "A"]
ary[(ary.index("A") + 1)..-1] = ary[0...ary.index("B")]
gives you
ary #=> ["B", "8", "5", "4", "6", "5", "6", "9", "7", "A"]
while
ary = ["B", "8", "5", "4", "6", "5", "6", "9", "7", "A"]
ary[indx2] = ary[indx1]
gives you
ary #=> ["B", "8", "5", "4", "6", "5", "6", "9", "7", "A", nil, nil, nil, nil, "1", "2", "5", "6"]
How do I swap array elements using parallel assignment?
To make things simple, let deck be just ['A', 'B']. Here is step-by-step evaluation:
deck = ['A', 'B']
deck[deck.index("A")], deck[deck.index("B")] = deck[deck.index("B")], deck[deck.index("A")] # deck == ['A', 'B']
deck[deck.index("A")], deck[deck.index("B")] = deck[1], deck[0] # deck == ['A', 'B']
deck[deck.index("A")], deck[deck.index("B")] = 'B', 'A' # deck == ['A', 'B']
deck[0], deck[deck.index("B")] = 'B', 'A' # deck == ['A', 'B']
# Applying first assignment.
..., deck[deck.index("B")] = ..., 'A' # deck == ['B', 'B']
# NOTE: deck.index("B") is 0 now, not 1!
..., deck[0] = ..., 'A' # deck == ['B', 'B']
# Applying second assignment.
... # deck == ['A', 'B']
So what your code actually does is just assinging twise to the same element of array.
In order to fix this issue, just save deck.index() values to temporary arrays:deck = []
(deck << (1..52).to_a << 'A' << 'B').flatten!
p deck
index_a, index_b = deck.index("A"), deck.index("B")
deck[index_a], deck[index_b] = deck[index_b], deck[index_a]
p deck
How does Ruby pass array's element to variables directly?
"page/action".split("/")
creates an array in Ruby.
Then controller_name, action = "page/action".split("/")
it causes the parallel assignment, by splatting the array, created by String#split
method.
Below are all the intermediate steps handled/taken by Ruby itself for you :
controller_name, action = "page/action".split("/")
↓
controller_name, action = ['page','action'] # <- intermediate
↓
controller_name, action = *['page','action'] # <- intermediate
↓
controller_name, action = 'page','action' # final assignment happened now.
Read the Parallel Assignment
You can collapse and expand arrays using Ruby's parallel assignment operator. If the last lvalue is preceded by an asterisk, all the remaining rvalues will be collected and assigned to that lvalue as an array. Similarly, if the last rvalue is an array, you can prefix it with an asterisk, which effectively expands it into its constituent values in place. (This is not necessary if the rvalue is the only thing on the right-hand side---the array will be expanded automatically.)
Assignment inside a case statement
case 'type'
needs to be
case type
You're providing a constant string 'type'
to your case, not the variable type
. None of your when
's will ever be matched, because none of them equal the string 'type'
.
You could also clean this up quite a bit by removing all identical assignments from each branch. Everything evaluates to a value in Ruby, so you the case statement will return the value from whichever branch is selected.
def message_notice(type, notice)
message_header = case type
when 'danger' then 'Oops, something wrong happened'
when 'success' then 'Done'
when 'warning' then 'Hey, be careful'
when 'info' then 'News, news'
end
"<strong>#{message_header}!</strong> #{notice}"
end
You might even want to take it a step further and simply store the type/message as a simple mapping:
def message_notice(type, notice)
messages = {
'danger' => 'Oops, something wrong happened',
'success' => 'Done',
'warning' => 'Hey, be careful',
'info' => 'News, news'
}
"<strong>#{messages[type]}!</strong> #{notice}"
end
How to assign multiple attributes to ActiveRecord::Relation array in single step?
When you use Post.update_all
this is special, because it runs an UPDATE ALL
SQL statement in the database. Therefore there's no need to iterate over the collection in memory.
Updating all records in ruby memory, on the other hand, cannot do this; you do need to iterate over them:
posts.each { |post| post.assign_attributes(state: 'pending') }
However, this shouldn't be an issue. I can't think of any scenario where you'd want to update a collection of objects in memory, without also wanting to iterate over them at some point.
Related Topics
Ror: Execute SQL in Controller
Extract Text Between Two Tags Using Regex in Ruby
Create Multiple Records with Fields_For - Rails
Pass Array Hidden Field in Nested Model Rails
If Statement in Ruby Using Regex
Are Spies an Appropriate Approach to See If Resque Methods Are Being Fired
Ruby/Watir/Rasta: Pass the Value from the Excel/Rasta to an Array in Ruby
How to Write the JSON Schema If JSON Has Multiple Data Set
Ruby Ternary Operator and Method Call
How to Replace a Pattern in Ruby Array
Continuous Integration for Ruby on Rails
Module and Class with the Same Name in Rails Project
Undefined Method 'Click' for Nil:Nilclass (Mechanize)
Why Rails Instance Method Can Be Used as Class Method in Rspec