I just began with Ruby yesterday. I saw some basics online and started to do some beginner coding challenges and one asks for finding prime numbers. I try not using the prime feature already in Ruby. I think I almost have it, but I don't understand why the error shows itself.
"18:in ': undefined method %' for nil:NilClass (NoMethodError)"
I tried to tweak around it for a few hours now, and can't seem to find solutions to this online... I use Ruby 3.0.0, here's the code I managed to build until now:
nums = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
prime_nums = []
i = 0
#Loop on the array to check each element
while i <= nums.length
check = 0
divider = nums[i]
#While to prevent DivisionByZero error
while divider != 0
if nums[i] % divider == 0 #/!\ ERROR HERE
check += 1
end
divider -= 1
end
#Only division by 1 and itself will increment $check
if check == 2
prime_nums.append(nums[i])
end
i += 1
end
puts prime_nums
In your example it means that nums[i] returns nil, so the case is that i is greater than 19.
You can change while i <= nums.length to while i < nums.length because arrays are indexed from 0 in Ruby.
undefined method %' for nil:NilClass means you're trying to use % on nil. nil is what you get when you, for example, try to get too much out of an Array.
If we put p "#{i}: #{nums[i]}" in the loop we'll see that i goes to 20. Arrays start counting at 0, so if you have 20 elements in an array the highest index is 19. num[20] is nil.
You can fix this with while i < nums.length to stop at 19.
However, one rarely writes while loops in Ruby. Instead, you iterate using methods. each iterates through each element of an Array. And you can count down with downto.
nums.each do |num|
check = 0
num.downto(1) do |divider|
if num % divider == 0
check += 1
end
end
#Only division by 1 and itself will increment $check
if check == 2
prime_nums.append(num)
end
end
Now there's no chance of being off-by-one.
The answer from @Schwern has great advice on using iterating methods in Ruby. We can use them to make your algorithm much more efficient.
def is_prime?(num)
2.upto Math::sqrt(num) do |divisor|
return false if num % divisor == 0
end
true
end
Rather than iterate down from num to 1 and keep track of divisors, we can start from 2 and work up to the square root of num. If we find an even divisor, we know it's not prime and can immediately return false, saving ourselves the effort of iterating over anything further.
If we reach the end of the loop, we know num must be prime and return true.
Then let's say we want to print primes from 1 to 500, we can do:
1.upto 500 do |num|
puts num if is_prime? num
end