undefined method `even?' for nil:NilClass using injection - ruby

I'm new to Ruby, and I'm having a hard time figuring out this error.
When I do:
ary = [0,1,1,2,3,5,8,13,21,34,55,89,144]
ary.inject {|mem, i| mem + i if mem.even? && i.even? }
This error comes out:
NoMethodError (undefined method `even?' for nil:NilClass)
I want to count only the pair(even) numbers.

To quickly review what #inject does when you pass it a block: the first iteration it passes the first element of your array (0) in the first block parameter, mem. It 'accumulates' the result from there by always passing the block the result returned by the block last time.
When you look at your block:
ary.inject { |mem, i| mem + i if mem.even? && i.even? }
You are returning mem + i when the condition is met. But, you are implicitly returning nil when the condition isn't met. Thus on the next iteration, mem is nil and you get your error. You probably want a ternary operator instead:
ary.inject { |mem, i| i.even? ? mem + i : mem }

Related

How to solve my Fibonaci recursive method in Ruby

def fibs_rec(n)
return 1 if n == 1 || n == 0
puts fibs_rec(n-1) + fibs_rec(n-2)
end
fibs_rec(5)
It's giving me the error
"undefined method '+' for nil:Nilclass"
I dont know what that means. How is it being turned to nil.
Make the method actually return the generated number:
def fibs_rec(n)
return 1 if n == 1 || n == 0
(fibs_rec(n-1) + fibs_rec(n-2)).tap do |result|
puts result
end
end
.tap lets you yield an object to a block and always return the object itself.
When you do recursion, the recursive function (or method in Ruby's term) must return a value. Ruby's puts method, on the other hand, returns nil.
You must move the puts method out of the recursion so that the fibs_rec method always returns a value.

How to use reduce/inject in Ruby without getting Undefined variable

When using an accumulator, does the accumulator exist only within the reduce block or does it exist within the function?
I have a method that looks like:
def my_useless_function(str)
crazy_letters = ['a','s','d','f','g','h']
str.split.reduce([]) do |new_array, letter|
for a in 0..crazy_letters.length-1
if letter == crazy_letters[a]
new_array << letter
end
end
end
return true if (new_array == new_array.sort)
end
When I execute this code I get the error
"undefined variable new_array in line 11 (the return statement)"
I also tried assigning the new_array value to another variable as an else statement inside my reduce block but that gave me the same results.
Can someone explain to me why this is happening?
The problem is that new_array is created during the call to reduce, and then the reference is lost afterwards. Local variables in Ruby are scoped to the block they are in. The array can be returned from reduce in your case, so you could use it there. However, you need to fix a couple things:
str.split does not break a string into characters in Ruby 2+. You should use str.chars, or str.split('').
The object retained for each new iteration of reduce must be retained by returning it from the block each time. The simplest way to do this is to put new_array as the last expression in your block.
Thus:
def my_useless_function(str)
crazy_letters = ['a','s','d','f','g','h']
crazy_only = str.split('').reduce([]) do |new_array, letter|
for a in 0..crazy_letters.length-1
if letter == crazy_letters[a]
new_array << letter
end
end
new_array
end
return true if (crazy_only == crazy_only.sort)
end
Note that your function is not very efficient, and not very idiomatic. Here's a shorter version of the function that is more idiomatic, but not much more efficient:
def my_useless_function(str)
crazy_letters = %w[a s d f g h]
crazy_only = str.chars.select{ |c| crazy_letters.include?(c) }
crazy_only == crazy_only.sort # evaluates to true or false
end
And here's a version that's more efficient:
def efficient_useless(str)
crazy_only = str.scan(/[asdfgh]/) # use regex to search for the letters you want
crazy_only == crazy_only.sort
end
Block local variables
new_array doesn't exist outside the block of your reduce call. It's a "block local variable".
reduce does return an object, though, and you should use it inside your method.
sum = [1, 2, 3].reduce(0){ |acc, elem| acc + elem }
puts sum
# 6
puts acc
# undefined local variable or method `acc' for main:Object (NameError)
Your code
Here's the least amount of change for your method :
def my_useless_function(str)
crazy_letters = ['a','s','d','f','g','h']
new_array = str.split(//).reduce([]) do |new_array, letter|
for a in 0..crazy_letters.length-1
if letter == crazy_letters[a]
new_array << letter
end
end
new_array
end
return true if (new_array == new_array.sort)
end
Notes:
return isn't needed at the end.
true if ... isn't needed either
for loop should never be used in Ruby
reduce returns the result of the last expression inside the block. It was for in your code.
If you always need to return the same object in reduce, it might be a sign you could use each_with_object.
"test".split is just ["test"]
String and Enumerable have methods that could help you. Using them, you could write a much cleaner and more efficient method, as in #Phrogz answer.

Use `next` with an accumulator argument in a `reduce`

There is a cop: RuboCop::Cop::Lint::NextWithoutAccumulator.
Is anyone able to explain what this cop is for, how is it supposed to improve the code in in what way?
Does it improve readability, efficiency?
github code.
Lets consider sample code from the documentation:
# bad
result = (1..4).reduce(0) do |acc, i|
next if i.odd?
acc + i
end
If you try this in the console, you will get NoMethodError exception for the nil object. This is because next "returns" nil if there is no object specified. You can treat it as return for iterators.
For reduce method it may result in some unexpected behavior as it needs some value returned by the block. If i is odd, then next is evaluated and block gives nil as the result. In the following iterator acc is equal to nil and it can't add integer to it. In our example, first iteration is for i = 1, next sets acc to nil as the result of the block.
In some cases you can get correct value for the enumerable, but in general it is safer to specify value for next inside.
Bad
result = (1..4).reduce(0) do |acc, i|
next if i.odd?
acc + i
end
Good
result = (1..4).reduce(0) do |acc, i|
next acc if i.odd?
acc + i
end
As #smefju pointed out, next by itself implicitly returns nil and will cause a NoMethodError when it is passed in as the parameter of the next execution of the block.

Find the 2nd element of array

I don't understand how this isn't working. The program is supposed to take instance method second in the class Array and return the 2nd object in the array
class Array
def second(*arr)
arr.length <= 1 ? nil : arr[1]
end
end
#Test cases
Test.assert_equals(Array([1, 2, 3]), 2,) #Getting nil
Test.assert_equals(Array([]), nil) #passes
Test.assert_equals(Array([1]), nil) #passes
What am I doing wrong? if I remove class Array and test on second it works fine?
Why use *arr? If you're monkey-patching Array, then use self:
class Array
def second
self.length <= 1 ? nil : self[1]
end
end
p [1,2,3].second #=> 2
p [1].second #=> nil
p [].second #=> nil
In answer to what you're doing wrong, your code as written doesn't need the splat (*) operator (it also doesn't need to be patched into the Array class). While patching into Array and using self allows you to call it like [1,2].second, you could also write it as follows without patching into Array:
def second(arr)
arr.length <= 1 ? nil : arr[1]
end
Which would need to be called like second([1,2]).
To find out more about the splat operator *, try something like this explanation (I confess - the first Google result, but seems ok), but what it's doing in your example is turning your passed-in array into an array of an array - e.g. [1,2,3] becomes [[1,2,3]].

Return 0 when select returns nothing?

Say I have an array of hashes like so:
items = [
{:user=>1, :amount=>484},
{:user=>2, :amount=>0},
{:user=>3, :amount=>8633}
]
To get the amount for a user, I'd so something like this:
items.select{|key| key[:user] == 1 }.first[:amount]
But if there isn't a user with a given ID, then that doesn't work:
> items.select{|key| key[:user] == 8 }.first[:amount]
# NoMethodError: undefined method `[]' for nil:NilClass
So how can I return amount if it finds the item, but return 0 if there's nothing?
First of all, you can use find instead of select since you only want the first match. If you want a one-liner, you can do something like this:
(items.find { |key| key[:user] == 8 } || { :amount => 0 })[:amount]
If you happen to have Rails or ActiveSupport kicking around then you could use try and to_i (while remembering that nil.to_i == 0) like this:
items.find { |k| key[:user] == 1 }.try(:fetch, :amount).to_i
try just calls a method (fetch in this case) but always returns nil if the receiver is nil so nil.try(:fetch, :amount) is nil but some_hash.try(:fetch, :amount) is some_hash.fetch(:amount), it is a handy tool for swallowing up nils without adding a bunch of extra conditionals. AFAIK, the andand gem does similar things without requiring all the ActiveSupport.
You could catch the Exception NoMethodError
begin
#code you want to execute
rescue NoMethodError
puts "0"
end
Three ways:
#1
def my_method(user, items)
items.each {|h|
return h[:amount] if h.key?(:user) && h[:user] == user && h.key?(:amount)}
0
end
my_method(1, items) #=> 484
my_method(5, items) #=> 0
#2
def my_method(user, items)
items.reduce(0) { |v,h|
(h.key?(:user) && h[:user] == user && h.key?(:amount)) ? h[:amount] : v }
end
#3
def my_method(user, items)
hash = items.find { |h| h.key?(:user) && h[:user] == user) }
(hash && hash.key?(:amount)) ? hash[:amount] : 0
end
put rescue 0 on the end
items.select{|key| key[:user] == 8 }.first[:amount] rescue 0
Here's a variant using some built-in Ruby fallback mechanisms
items.find(->{{}}){|e| e[:user] == 8}.fetch(:amount, 0)
First, starting from the end of the line, fetch, which returns the value at a provided hash key. One thing that makes fetch different from using regular hash square brackets is that, if the key isn't there, fetch throws as a key error instead of returning nil. That's a neat trick, but the other useful thing is that it can take an optional second argument, which is the default thing to return if that key isn't found:
test = { a: 1, b:2 }
test[:c] #=> nil
test.fetch(:c) #=> raise KeyError
test.fetch(:c, 0) #=> 0
Handy, but you can't call fetch on nil, which is the default return for find if it finds nothing.
Luckily, find also lets you assign a default return value instead of nil. You can pass an object that responds to call, and that object will specify how to handle a find with no matches.
Here's a short lambda that returns an empty hash: ->{{}} and here it is passed as a first argument to find, before the usual block
items.find(->{{}}){ |key| key[:user] == 8 } #=> {}
All together: items.find(->{{}}){|e| e[:user] == 8}.fetch(:amount, 0) -- now find will always return some sort of hash, which will respond to fetch, and fetch can return a default if a key is missing.

Resources