Ruby showel operator vs (?:) conditional - ruby-on-rails

I have the following code:
#ids = []
x = 'a'
#ids << x == 'a' ? [1,2] : [3,4]
#ids
I expect that in next line the #ids value should be
#ids = [1,2], but I obtain #ids = ['a']
Why ?

Operations are executed in the order of their precedence.
The operations in your relevant line of code are executed in that order:
<<
==
?, :
See the full list at Ruby's operation precedence.
Here, parenthesis indicate what actually happens in your example:
(((#ids << x) == 'a') ? [1,2] : [3,4])
^^^----1----^ ^ ^
||---------2--------| |
|------------------3-----------------|
To get the result you expected, write
#ids << (x == 'a' ? [1,2] : [3,4])
or
#ids.push(x == 'a' ? [1,2] : [3,4])
I hope you find this helpful.

Related

Ruby << syntax method raises an error, but .push method not when using yield. Why?

I am trying to figure out why the case C is not working. As you can see when I use 'yield' and '<<' sugar syntax it raises an error, but if I use the method's name 'acc.push' it works. In the other hand, if I use the 'result' variable to get yield result and then add to acc array using << syntax, it works. I just would like to understand why it does not work in the case C. Thanks.
Case A - Working fine
def my_map(my_arr)
c = 0 # the counter
acc = [] # new array
until c == my_arr.length
acc.push(yield my_arr[c])
c += 1
end
acc
end
p my_map( [1,2,3,4] ) { |each| each * 10 }
Case B - Working fine
def my_map(my_arr)
c = 0 # the counter
acc = [] # new array
until c == my_arr.length
result = yield my_arr[c]
acc << result
c += 1
end
acc
end
p my_map( [1,2,3,4] ) { |each| each * 10 }
Case C - Error: syntax error, unexpected local variable or method, expecting `end' acc << yield my_arr[c]
def my_map(my_arr)
c = 0 # the counter
acc = [] # new array
until c == my_arr.length
acc << yield my_arr[c]
c += 1
end
acc
end
p my_map( [1,2,3,4] ) { |each| each * 10 }
While you expect that Ruby interprets acc << yield my_arr[c] as
acc.<<(yield(my_arr[c]))
Ruby actually understands it like this
acc.<<(yield)(my_arr[c])
Which doesn't make much sense. It can be fixed by using parentheses as others already mentioned:
acc << yield(my_arr[c])
You're hitting a case of operator precedence, which Ruby can't resolve for itself.
You can fix it by using parentheses to provide enough clarity for Ruby to work out how to parse the offending line:
acc << yield(my_arr[c])
Just use parenthesis for the yield method call.
acc << yield(my_arr[c])
My guess is that the << tries to add the yield directly to the array which results in the syntax error.

Ruby compare 2 arrays of integer

Let's say i have 2 arrays with the same length filled with integers
a = [1,2,3,4]
b = [1,3,2,5]
And i want to compare these arrays and get an output in new (c) array so it would look like this
c = ["+","-","-"," "]
An empty space indicates that there is not a current digit in a first array
A - indicates a number match: one of the numbers in the second array is the same as one of the numbers in the first array but in a different position
Currently i have this comparison method and need to improve it
a.each_with_index.map {|x, i| b[i] == x ? '+' : '-'}
Something like this would work: (assuming unique elements)
a.zip(b).map do |i, j|
if i == j
'+'
elsif b.include?(i)
'-'
else
' '
end
end
#=> ["+", "-", "-", " "]
zip combines the arrays in an element-wise manner and then maps the pairs as follows:
'+' if they are identical
'-' if the element from a is included in b
' ' otherwise
Note that the result still reflects the element order i.e. the position of '+' exactly tells you which elements are identical. You might want to sort / shuffle the result.
appearance = a.each_with_index.to_h
b.map.with_index { |v, i|
appearance[v].nil? ? " " : (v == a[i] ? '+' : '-')
}

How to use multiple assignment with a ternary operator?

this works:
foo, bar = 1, 2
foo == 1 #true
bar == 2 #true
this also works:
baz = true
foo = baz ? 1 : 2
foo == 1 #true
this does not work:
foo, bar = baz ? 1, 2 : 3, 4
# SyntaxError: (irb):4: syntax error, unexpected ',', expecting ':'
# foo, bar = baz ? 1, 2 : 3, 4
# ^
How should this be formatted so that it works?
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 :)
foo, bar = baz ? 1, 2 : 3, 4 <= this DOES NOT work... why?
Here's why:
If you look at parse.y (the grammar of Ruby), the ternary conditional structure arg1 ? arg2 : arg3 needs arg (an argument) for its arguments:
arg : lhs '=' arg_rhs
| # ...
| arg '?' arg opt_nl ':' arg
| # ...
and
arg_rhs : arg
# ...
As seen above, the assignment lhs = rhs is also an arg. But multiple assignment mlhs1, mlhs2 = mrhs1, mrhs2 is a statement:
stmt : # ...
| mlhs '=' mrhs_arg
| # ...
| expr
;
And while an argument can be used as an expression
expr : # ...
| arg
;
and an expression can be used as a statement as seen above, the reverse is not true: a statement is not always a valid expression, and an expression is not always a valid argument.
Furthermore, when you have [1, 2], that's an array, which is a valid arg, which is also a valid arg_rhs, which can go in the right side of arg : lhs '=' arg_rhs. 1, 2 is not a valid arg, but it is a valid mrhs_arg (multiple right-side built up of arguments, which either has multiple comma-separated values like foo, bar = 1, 2, foo, bar = *[1, 2] or even foo, bar = 1, *[2], or destructures an array value like foo, bar = [1, 2]):
mrhs_arg : mrhs
| arg_value
;
mrhs : args ',' arg_value
# ...
| args ',' tSTAR arg_value
# ...
| tSTAR arg_value
# ...
;
so it fits right in into the stmt : mlhs '=' mrhs_arg rule.
This last rule is also the reason why your solution foo, bar = baz ? [1, 2] : [3, 4] works: baz ? [1, 2] : [3, 4] is an arg, which is also arg_value, and can be used in the mrhs_arg : arg_value rule. But the rule with explicit bare commas that allows foo, bar = 1, 2 (mrhs : args ',' arg_value) cannot be used with a conditional, because it explicitly requires at least two comma-separated arguments - which is not a possible result from a conditional.
tl;dr: Because multiple assignment is parsed differently than simple assignment.

"undefined method 'zero' for Nil:Class" when #sum the Array without Nils

The issue happens when the variable, that the array was built from, was a nil initially.
y = (1..2).map do
v = nil
v = 1
v
end
p y # => [1, 1]
p y.class # => Array(Int32)
p y.sum # => 2
When v stops being nil on a condition, that is potentially computational and not solvable while compiling:
z = (1..2).map do
v = nil
v = 1 if true
v
end
p z # [1, 1]
p z.class # => Array(Nil | Int32)
The array gets more complex type, that isn't compatible with current sum implementation, so p z.sum causes compile time error:
undefined method 'zero' for Nil:Class (compile-time type is (Nil | Int32):Class)
def sum(initial = T.zero)
^~~~
How am I supposed to fight this properly?
Or maybe it waits for some better implementation of stdlib sum method or anything else?
UPD: inject gives the same:
p z.inject{ |i, j| i + j }
undefined method '+' for Nil (compile-time type is (Nil | Int32))
You can use Iterator#compact_map to select non-nil values. The compiler will be able to infer a Array(Int32) in that case.
http://play.crystal-lang.org/#/r/e85
z = (1..2).map do
v = nil
v = 1 if true
v
end
pp typeof(z) # => Array(Nil | Int32)
pp z # => z = [1, 1]
y = z.compact_map(&.itself)
pp typeof(y) # => Array(Int32)
pp y # => y = [1, 1]
Also, notice that typeof(Expr) and Expr.class might lead to different results. The first is the compile time type and the later is the runtime type.
An alternative solution to what Brian says is to use sum with a block:
http://play.crystal-lang.org/#/r/ein
z = (1..2).map do
v = nil
v = 1 if true
v
end
puts z.sum { |x| x || 0 } #=> 2

How to insert a new hash into existing hash in ruby

I have two hashes:
p = {"name"=>"TRICHI", "subdistrict"=>{"WANDIWASH"=>"1234"}}
q = {"name"=>"VELLORE", "subdistrict"=>{"WANDIWASH"=>"4183"}}
I need to make this as
r = [{"name"=>"VELLORE", "subdistrict"=>{"WANDIWASH"=>"4183"}},
{"name"=>"TRICHI", "subdistrict"=>{"WANDIWASH"=>"1234"}}]
I guess you want this:
r = [] << p << q
# or r = [p, q]
# either way you'll get:
# [ {"name"=>"VELLORE", "subdistrict"=>{"WANDIWASH"=>"4183"}},
# {"name"=>"TRICHI", "subdistrict"=>{"WANDIWASH"=>"1234"}} ]
This way you will have an array with 2 hashes.
As Tim pointed, r doesn't seem to be a Hash, maybe you meant an Array, in which case you can do
r = [p,q]
or
r = []
r << p
r << q
.. keep going for any other entry you want to push into r

Resources