Ruby: enumerator can't be coerced to Fixnum; struggling with Project Euler #5

1k views Asked by At

The challenge is to find the smallest integer foo for which:

foo % 1..20 == 0

My current attempt is brute force

until foo % 1.upto(20) == 0 do
    foo++
end

This outputs the error unexpected keyword end. But if I don't put the end keyword into irb the code never runs, because the block isn't closed.

I made an empty test case to see where my error lays

until foo % 1.upto(20) == 0 do
end

This throws a new error: enumerator can't be coerced to a fixnum. I imagine this means you can't directly perform modulus upon a range and expect a neat boolean result for the whole range. But I don't know where to go from here.

My first attempts forewent brute force in favor of an attempt at something more efficient/elegant/to-the-point and were as follows:

foo = 1
1.upto(20) {|bar| foo *= bar unless foo % i == 0}

gave the wrong answer. I don't understand why, but I'm also interested in why

foo = 1
20.downto(1) {|bar| foo *= bar unless foo % i == 0}

outputs a different answer.

EDIT: I would have used for loops (I got my feet wet with programming in ActionScript) but they do not work how I expect in ruby.

4

There are 4 answers

3
sarnold On BEST ANSWER

If this were me, I'd define a function to test the condition:

def mod_test(num)
  test = (1..20).map {|i| num % i == 0}
  test.all? # all are true
end

and then a loop to try different values:

foo = 20
until mod_test(foo) do
  foo += 20
end

(Thanks to Dylan for the += 20 speedup.)

I'm sure that there's a clever way to use the knowledge of foo % 10 == 0 to also imply that foo % 5 == 0 and foo % 2 == 0, and perform only tests on prime numbers between 1 and 20, and probably even use that information to construct the number directly -- but my code ran quickly enough.

4
PinnyM On

Try this:

until 1.upto(20).reject{|i| foo % i == 0 }.empty? do
  foo += 1
end
1
Andrew Marshall On

Your first solution is wrong because 1.upto(20) is an enumerator, that is, essentially an iterator over the values 1 to 20, and it cannot be used as a number for modulo or comparison to another number, since it itself isn't a number.

You really need two "loops" here:

foo = 1
foo += 1 until (1..20).all? { |i| foo % i == 0 }

the first loop is the until, and then the all? is another loop of sorts, in that it ensures that the block ({ |i| foo % i == 0 }) is true for each element in the range it is called on ((1..20)). Note that I'm using the one-line "backwards" syntax (which also works for if, unless, while, …)—the above is equivalent to:

foo = 1
until (1..20).all? { |i| foo % i == 0 } do
  foo += 1
end
# foo => 232792560

Also, this is incredibly inefficient, Project Euler often involves a bit more math than programming, and a non-brute-force solution will likely involve more math but be far faster.

0
Camilo Martinez On

I know it's not directly the OP question, but this is way easier to achieve with just:

puts (1..20).reduce(:lcm)

It's so simple that it seems like isn't fair to solve it this way, but that's precisely why Ruby is my language of choice for Project Euler.

See also this question