Reputation: 21
I am trying to search an array for a substring and if that substring exists, shovel it into a new array. The problem I am having is that it keeps coming back with this error message:
`block in substrings': undefined method `<<' for nil:NilClass
I have verified that the index
in the method is not nil
by printing it. I have also done index == nil
to double check.
What am I missing here?
Thanks in advance for your help!
new_array = []
def substrings(word, array)
new_array = array.each do |index|
if index.include? (word)
p index
p index == nil
new_array << index
end
end
end
dictionary = ["below", "down", "go", "going", "horn", "how", "howdy", "it", "i", "low", "own", "part", "partner", "sit"]
substrings("i", dictionary)
Upvotes: 1
Views: 239
Reputation: 114208
I have verified that the
index
in the method is notnil
by printing it. I have also doneindex == nil
to double check.What am I missing here?
Not index
is nil
, but new_array
. The error message says:
undefined method `<<' for nil:NilClass
and refers to the line new_array << index
. Here, <<
is the method and new_array
is the receiver. And for some reason, new_array
is nil
.
You probably expected new_array
to be []
because you explicitly said new_array = []
. But methods have their own local variable scope. If you define a local variable outside of a method, it won't be available inside, or vice-versa.
Typically when referring to an undefined variable you'd get:
undefined local variable or method `new_array'
but here, the 2nd assignment conceals the actual problem:
new_array = array.each do |index|
^^^^^^^^^^^
When Ruby encounters this line, it immediately creates a local variable new_array
with an initial value of nil
. (local variables are created when the line is parsed, not when the assignment occurs)
To get the expected result, you have to move new_array = []
into the method, get rid of the new_array = array.each { ...}
assignment and return new_array
at the end:
def substrings(word, array)
new_array = []
array.each do |index|
if index.include?(word)
new_array << index
end
end
new_array
end
The variable names are a bit arbitrary, maybe even misleading. Having index.include?(word)
looks like you're comparing a numerical index to a string. I'd use something like this:
def substrings(substring, words)
result = []
words.each do |word|
if word.include?(substring)
result << word
end
end
result
end
Code-wise, you can incorporate the array into the loop via each_with_object
which will also return the array:
def substrings(substring, words)
words.each_with_object([]) do |word, result|
if word.include?(substring)
result << word
end
end
end
However, selecting elements based on a condition is such a common task that Ruby provides a dedicated method select
– you merely have to return true
or false
from the block to indicate whether the element should be selected:
def substrings(substring, words)
words.select do |word|
word.include?(substring)
end
end
Upvotes: 0
Reputation: 22291
I would like to extend a bit the (correct) answer given by @DanneManne: While it is correct that you can access local variables from outer blocks from within an inner block, you can't do it within a def
, and it is unnecessary in your example, because you can initialize new_array
inside the body of your method and return it as result. But in case you ever really need this kind of construct, there indeed is a workaround:
So, this does NOT work:
a=5
def f
puts a; # WRONG. a is not known here
end
and this works different than you seem to expect:
a=5
def f
a=6
end
puts a # prints 5
f
puts a # prints 5 again
But if you define your method like this, it works:
a=5
define_method(:f) do
puts a; # OK, refers to outer variable a
end
By using a block, you create a closure with this, so if you do now a
f;
a=6;
f
5 and 6 is printed, in this order.
Upvotes: 0
Reputation: 21180
You basically combine two different ways of solving this problem. The first is to assign the new_array
the result of looping though the array, but in that case, the new_array variable is not available to use inside the block.
So you could either choose to create the variable first, like this
new_array = []
array.each do |index|
if index.include?(word)
new_array << index
end
end
Alternatively you could use a method called reduce
which takes a more functional programming approach. That could look like this
new_array = array.reduce([]) do |arr, index|
if index.include?(word)
arr << index
else
arr
end
end
What reduce does is that the block argument arr
is always set to the return value of the previous block execution. that can make the syntax a little longer than it has to be, so Ruby also has an alternate approach to reduce, called each_with_object
, that does the same, but by mutating the same variable, instead of requiring a return value. I actually prefer this way and would solve it like this.
new_array = array.each_with_object([]) do |index, arr|
arr << index if index.include?(word)
end
Upvotes: 1