Ruby: перечислитель не может быть приведен к Fixnum; борется с Project Euler # 5

Задача состоит в том, чтобы найти наименьшее целое число foo, для которого:

foo % 1..20 == 0

Моя текущая попытка - грубая сила

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

Это выводит ошибку unexpected keyword end. Но если я не добавлю ключевое слово end в irb, код никогда не запустится, потому что блок не закрыт.

Я сделал пустой тестовый пример, чтобы увидеть, в чем заключается моя ошибка.

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

Это выдает новую ошибку: enumerator can't be coerced to a fixnum. Я предполагаю, что это означает, что вы не можете напрямую выполнять модуль для диапазона и ожидать аккуратного логического результата для всего диапазона. Но я не знаю, куда идти отсюда.

Мои первые попытки отказались от грубой силы в пользу попытки чего-то более эффективного/элегантного/точного и были следующими:

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

дал неправильный ответ. Я не понимаю, почему, но мне также интересно, почему

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

выдает другой ответ.

EDIT: я бы использовал циклы for (я промочил ноги программированием на ActionScript), но они не работают так, как я ожидаю, в ruby.


person ac7v    schedule 18.05.2012    source источник
comment
foo++ не работает в Ruby так, как можно было бы ожидать. Вам нужно написать foo += 1   -  person OzBandit    schedule 18.05.2012
comment
@David: Это моя наименее любимая языковая функция...   -  person sarnold    schedule 18.05.2012
comment
@sarnold Наименее любимый в том смысле, что вы хотели бы, чтобы это было на Ruby, или вам не нравится это на других языках? Это на самом деле не имеет смысла в Ruby (или, возможно, в ОО в целом), поскольку числа не изменяемы, и, таким образом, ++ будет включать скрытое присваивание.   -  person Andrew Marshall    schedule 18.05.2012
comment
@Andrew: Не пытайся успокоить меня аргументацией :) -- я просто очень привык к foo++ на каждом языке, который использую -- и тут Ruby меня кусает.   -  person sarnold    schedule 18.05.2012
comment
@sarnold Нет, я понимаю, я тоже к этому привык и был шокирован, когда начал с Ruby. Но поскольку циклов for в Ruby не существует, как в этих языках, я не слишком скучаю по ++.   -  person Andrew Marshall    schedule 18.05.2012
comment
Я все еще очень новичок в кодировании, в настоящее время пытаюсь изучить java, играя с рубином. Благодаря irb это мой любимый язык для этих упражнений. Тем не менее, я уже влюблен в upto() и downto(), а также в то, как ruby ​​обрабатывает «для каждого», поэтому я не чувствую себя связанным. @OzBandit спасибо за совет.   -  person ac7v    schedule 18.05.2012


Ответы (4)


Если бы это был я, я бы определил функцию для проверки условия:

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

а затем цикл, чтобы попробовать разные значения:

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

(Спасибо Дилану для ускорения += 20.)

Я уверен, что есть умный способ использовать знание foo % 10 == 0, чтобы также подразумевать, что foo % 5 == 0 и foo % 2 == 0, и выполнять проверки только простых чисел между 1 и 20 и, возможно, даже использовать эту информацию для прямого построения числа - но мой код работал достаточно быстро.

person sarnold    schedule 18.05.2012
comment
Вместо этого вы можете просто проверить 11..20. Если 20 сработает, сработает и 10; если 18 сработает, сработает и 9; и т.п. - person Dylan Markow; 18.05.2012
comment
@Dylan: отличный момент, который почти вдвое сократит время выполнения. (Вызовы функций не позволят ему быть ровно половиной.) Хорошо. :) - person sarnold; 18.05.2012
comment
@Dylan, у тебя интригующая оптимизация. У меня есть моя работа, чтобы понять каждый вклад. - person ac7v; 19.05.2012

Ваше первое решение неверно, потому что 1.upto(20) — это перечислитель, то есть, по сути, итератор значений от 1 до 20, и его нельзя использовать как число для модуля или сравнения с другим числом, поскольку оно само не число.

Здесь вам действительно нужны два «цикла»:

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

первый цикл — это until, а затем all? — это еще один своего рода цикл, поскольку он гарантирует, что блок ({ |i| foo % i == 0 }) верен для каждого элемента в диапазоне, в котором он вызывается ((1..20)). Обратите внимание, что я использую однострочный «обратный» синтаксис (который также работает для if, unless, while, …) — приведенное выше эквивалентно:

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

Кроме того, это невероятно неэффективно, Project Euler часто требует немного больше математики, чем программирования, а решение без грубой силы, вероятно, потребует больше математики, но будет намного быстрее.

person Andrew Marshall    schedule 18.05.2012
comment
Как бы я ни хотел сделать решения этих проблем элегантными, на данный момент я могу справиться только с беспорядком. Я придумал другой подход после публикации вопроса, и я пытаюсь привести некоторые фрагменты кода в форму. Это как минимум на шаг выше грубой силы, и я ДУМАЮ, что это сработает, но мне нужна вся помощь, чтобы понять, где я ошибся. Самое интересное в том, как сильно это болит у меня в голове после того, как я потратил на это несколько часов, повышая мою базовую компетентность. Познавательная часть — это чтение решений других людей (лучших) после. Спасибо за помощь на этом конце. - person ac7v; 19.05.2012

Попробуй это:

until 1.upto(20).reject{|i| foo % i == 0 }.empty? do
  foo += 1
end
person PinnyM    schedule 18.05.2012
comment
Поскольку оно должно делиться на 20, вы можете просто начать с foo = 20 и каждый раз прибавлять 20 вместо 1. - person Dylan Markow; 18.05.2012
comment
Хорошая идея, так как я сдался около foo=17740946. :) - person sarnold; 18.05.2012
comment
Я не пытался улучшить ответ для решения проблемы ProjectEuler - я просто предоставлял правильный синтаксис для попытки решения. - person PinnyM; 18.05.2012
comment
Таким образом, метод reject пропускает итерацию по значениям, удовлетворяющим этому условию, и пустая итерация завершается, когда достигается максимальное значение? Поскольку 20 можно разложить на множители, действительно ли вы найдете наименьшее общее кратное? Я пытаюсь найти лучшее решение, чем грубая сила, но оно будет основываться на первом устранении общих факторов между повторяемыми значениями, за исключением значения, которое имеет наибольшее количество копий рассматриваемого фактора. Отсюда умножайте. По крайней мере, я попробую это решение. Хорошая практика, даже если она окажется неправильной и чрезмерно сложной, но, возможно, по крайней мере более эффективной, чем грубая сила. Спасибо! - person ac7v; 19.05.2012

Я знаю, что это не прямой вопрос ОП, но этого намного проще добиться, просто:

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

Это настолько просто, что кажется несправедливым решать ее таким образом, но именно поэтому Ruby является моим языком выбора для Project Euler.

См. также этот вопрос

person Camilo Martinez    schedule 16.05.2013