Ruby и красивый код: Рефаторинг поиска палиндромов
августа 27, 2011 | Published in Ruby, Основы | 4 Comments
В уютном бложеке одного из комментаторов нашел один пост-решение задачи, который меня заинтересовал ().
Задача состоит вот в чем:
Необходимо найти числа-палиндромы (которые при чтении слева и справа выглядят одинаково). Автор (к сожалению имени не знаю) предлагает следующее решение:
#Создаем массив чисел numbers = [] (100).step(999,1) do |first| (100).step(999,1) do |second| numbers.push first * second end end #находим палиндром polinoms = [] numbers.map do |subnumber| polinoms << subnumber if subnumber.to_s == subnumber.to_s.reverse end #максимальный палиндром puts polinoms.max
Совет: Оборачивайте код в методы.
А теперь критика.
Мы создаем массив из 899 * 899 = 808201 элемента, на моем Dell D500 перемножение чисел заняло примерно полминуты — слишком много. Да и зачем нам необходимо хранить все эти числа? Нам необходимы только палиндромы! По условию мы должны использовать 3-х значные числа, точнее автор имел ввиду то, что мы перемножаем трехзначные числа для получения чисел для реальной выборки палиндромов, а это 5-ти — 6-ти значные числа. Принимаем эти правила.
Итак, условия таковы: найти палиндромы в пределах всех 5-ти и 6-ти значных чисел.
def palindrome?(num) num.to_s == num.to_s.reverse end def get_palindromes(start, finish) palindromes = [] (start..finish).each do |num| palindromes << num if palindrome?(num) end return palindromes end get_palindromes(100*100, 999*999) #[10001,10101,10201,...995599,996699,997799]
Результат вываливается практически мгновенно, код несколько длиннее, но читабельнее. Это не все, вы видите, что здесь мы работаем с диапазонами, по ‘этому можно расширить клас диапазонов — Range:
class Range def palindrome?(num) num.to_s == num.to_s.reverse end def get_palindromes palindromes = [] self.each do |num| palindromes << num if palindrome?(num) end return palindromes end end (100 .. 500).get_palindromes #=> [101,111,121,131,...474,484,494]
А можно даже так:
module Palindrome def palindrome?(num) num.to_s == num.to_s.reverse end def get_palindromes palindromes = [] self.each do |num| palindromes << num if palindrome?(num) end return palindromes end end class Range include Palindrome end class Array include Palindrome end (100..200).get_palindromes #=> [101,111,121,...171,181,191] [100,101,200,1245421].get_palindromes #=> [101,1245421]
Пределов совершенству нет даже в совсем простых задачах.
UPD: А вот и доказательство приведенного выше тезиса, код от brainopia:
class Numeric def palindrome? to_s == to_s.reverse end end module Palindrome def palindromes select &:palindrome? end end #теперь достаточно подмешать Palindrome в массив или диапазон и можно легко выбрать все числа - палиндромы.
августа 30, 2011 at 14:58 (#)
Во-первых, get_palindromes можно упростить до:
Во-вторых, если уж начали открывать классы, то palindrome? должен быть либо приватным методом, либо быть определен на Numeric и тогда это будет выглядеть следующим образом
августа 31, 2011 at 11:22 (#)
brainopia, спасибо, совсем забыл о select.
мая 4, 2012 at 13:15 (#)
Уж не судите строго,ни чего умнее этого придумать не смог)))
марта 27, 2013 at 10:14 (#)
Зачем все усложнять?