Ruby Parallel Assignment, Step Question

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.

  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).

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



Leave a reply



Submit