Более похожее на рубин решение этой проблемы?

Я изучаю Ruby и практикую его, решая задачи из Project Euler.

Это мое решение проблемы 12.

# Project Euler problem: 12
# What is the value of the first triangle number to have over five hundred divisors?

require 'prime'

triangle_number = ->(num){ (num *(num + 1)) / 2 }

factor_count = ->(num) do
  prime_fac = Prime.prime_division(num)
  exponents = prime_fac.collect { |item| item.last + 1 }
  fac_count = exponents.inject(:*)
end

n = 2
loop do
  tn = triangle_number.(n)
  if factor_count.(tn) >= 500
    puts tn
    break
  end
  n += 1
end

Какие улучшения можно внести в этот фрагмент кода?


person RaouL    schedule 11.06.2010    source источник
comment
что такое ->? Это какой-то экшен 1.9, о котором я ничего не знаю?   -  person theIV    schedule 11.06.2010
comment
это новый синтаксис лямбда вместо d = lambda {|x| x+1} в ruby ​​1.9 вы можете написать d = -›(x){ x+1 }   -  person RaouL    schedule 11.06.2010
comment
Кстати: если у вас есть ровно один параметр, вы можете не указывать круглые скобки: ->num{ (num *(num + 1)) / 2 }   -  person Jörg W Mittag    schedule 11.06.2010


Ответы (3)


Вместо того, чтобы решать проблему за один раз, просмотр отдельных частей проблемы может помочь вам немного лучше понять Ruby.

Первая часть — выяснить, каким будет число треугольника. Поскольку здесь используется последовательность натуральных чисел, вы можете представить это с помощью диапазона в ruby. Вот пример:

(1..10).to_a => [1,2,3,4,5,6,7,8,9,10]

Массив в ruby ​​считается перечисляемым, а ruby ​​предоставляет множество способов перечисления данных. Используя это понятие, вы можете перебирать этот массив с помощью метода each и передавать блок, который суммирует числа.

sum = 0
(1..10).each do |x|
  sum += x
end

sum => 55

Это также можно сделать с помощью другого перечисляемого метода, известного как inject, который будет передавать то, что было возвращено из предыдущего элемента, в текущий элемент. Используя это, вы можете получить сумму в одной строке. В этом примере я использую 1.upto(10), который функционально будет работать так же, как (1..10).

1.upto(10).inject(0) {|sum, x| sum + x} => 55

Проходя через это, первый раз, когда это вызывается, сумма = 0, х = 1, поэтому (сумма + х) = 1. Затем он передает это следующему элементу, и поэтому сумма = 1, х = 2, (сумма + х ) = 3. Следующая сумма = 3, x = 3, (сумма + x) = 6. сумма = 6, x = 4, (сумма + x) = 10. И т. д. и т. д.

Это только первый шаг этой проблемы. Если вы хотите выучить язык таким образом, вам следует подходить к каждой части проблемы и изучать то, что уместно изучать для этой части, а не решать всю проблему целиком.

РЕФАКТОРИРОВАННОЕ РЕШЕНИЕ (хотя и совсем неэффективное)

def factors(n)
  (1..n).select{|x| n % x == 0}
end

def triangle(n)
  (n * (n + 1)) / 2
end

n = 2

until factors(triangle(n)).size >= 500
  puts n
  n += 1
end

puts triangle(n) 
person lambdabutz    schedule 11.06.2010
comment
Прочитав ваш код еще раз, становится ясно, что вы уже знаете большинство этих методов, извините, что не прочитали его достаточно тщательно. В случае с рефакторингом вы немного перегружены процедурами, но, как сказал ryanjm.mp, это вопрос стиля, хотя я не думаю, что вы найдете много рубиновых людей, согласных с вашим стилем. Некоторые вещи, такие как сохранение лямбды треугольника_числа в переменной, не нужны, если вы используете ее только один раз. factor_count также имел бы больше смысла как метод, чем лямбда, с точки зрения удобочитаемости и даже раскрытия его позже. Удачи. - person lambdabutz; 11.06.2010
comment
я слишком увлекся лямбда-выражениями, так как для меня это в новинку :) честно говоря, я никогда не программировал ни на одном языке, поддерживающем парадигму функционального программирования, спасибо за совет - person RaouL; 11.06.2010

Как заявляли другие, рубисты будут использовать методы или блоки гораздо чаще, чем лямбда-выражения.

Enumerable в Ruby — это очень мощный миксин, поэтому я считаю, что здесь стоит создать перечисляемое, аналогично Prime. Так:

require 'prime'
class Triangular
  class << self
    include Enumerable
    def each
      sum = 0
      1.upto(Float::INFINITY) do |i|
        yield sum += i
      end
    end
  end
end

Это очень универсально. Просто проверяю, работает ли:

Triangular.first(4) # => [1, 3, 7, 10]

Хороший. Теперь вы можете использовать его для решения вашей проблемы:

def factor_count(num)
  prime_fac = Prime.prime_division(num)
  exponents = prime_fac.collect { |item| item.last + 1 }
  exponents.inject(1, :*)
end

Triangular.find{|t| factor_count(t) >= 500}  # => 76576500

Примечания:

  • Float::INFINITY появился в версии 1.9.2. Либо используйте 1.0/0, require 'backports', либо loop, если используете более раннюю версию.
  • each можно улучшить, сначала проверив, что блок пройден; вы часто будете видеть такие вещи, как:

      def each
        return to_enum __method__ unless block_given?
        # ...
    
person Marc-André Lafortune    schedule 11.06.2010

Похоже, вы перешли от написания Ocaml или другого функционального языка. В Ruby вы бы хотели использовать больше def для определения своих методов. Руби о том, чтобы оставаться чистым. Но это может быть и личным предпочтением.

И вместо loop do вы могли бы while (faction_count(traingle_number(n)) < 500) do, но для некоторых это может быть слишком много для одной строки.

person RyanJM    schedule 11.06.2010