I was writing some code like following:
[1,2,3,4,5].inject([]) do |res, a|
res << a*a and next if a == 2
res << a
end
It gives following error:
NoMethodError: undefined method `<<' for nil:NilClass
As by next it makes res
variable as nil
, How to work around this problem?
I tried various ways but couldn't get next to work with ruby, I know this snippet I have provided can be done without next(a == 4 ? res << a*a : res << a
), but in my actual use-case I have some complex logic and can't be done that simply.
Ugh. Don't use a trailing conditional for this. Instead it is easily done using a standard if
/else
:
[1,2,3,4,5].inject([]) do |res, a|
if a == 2
res << a*a
else
res << a
end
end
# => [1, 4, 3, 4, 5]
Any time you feel like you're coding yourself into a corner, don't look for a way out, instead, back up and look at what you're trying to accomplish to see if there is a more straightforward way there.
I'd probably tweak the code a bit more though, for readability. Long term maintenance relies on quickly understanding what is going on, and code that is convoluted or not obvious can take its toll later:
[1,2,3,4,5].inject([]) do |res, a|
if a == 2
res << a*a
else
res << a
end
res # return it for clarity in what the block is returning
end
# => [1, 4, 3, 4, 5]
inject
is similar to each_with_object
, only it relies on the accumulator being returned at the end of the block, which is why I would add the res
at the end of the block for clarity, due to the if
block. Switching to each_with_object
removes that reliance on the return value of the block, allowing the following code to be more logically clear:
[1,2,3,4,5].each_with_object([]) do |a, ary|
if a == 2
ary << a*a
else
ary << a
end
end
# => [1, 4, 3, 4, 5]
Of course, at that point, the whole thing can be reduced further, and could take advantage of the ternary version using:
[1,2,3,4,5].each_with_object([]) do |a, ary|
a2 = (a == 2) ? a * a : a
ary << a2
end
# => [1, 4, 3, 4, 5]
Which of the above two are more readable is somewhat up to the person coding it and the person responsible for maintaining it. I'd lean toward the non-ternary version because it's more easily extended/expanded and doesn't have the line noise of the ternary ?:
chain.
Since it was asked in the comments, map
reduces some noise, and is how we should transform an array:
[1,2,3,4,5].map { |a|
(a == 2) ? a * a : a
}
That's untested but it looks correct.
Replace the
res << a*a and next if a == 2
with
next res << a*a if a == 2
Now, it will work.
Example :-
#!/usr/bin/env ruby
ar = [1,2,3,4,5].inject([]) do |res, a|
next res << a*a if a == 2
res << a
end
p ar
# >> [1, 4, 3, 4, 5]
Read the documentation of next
next
can take a value, which will be the value returned for the current iteration of the block....
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With