Итерация по бесконечной последовательности в Ruby - PullRequest
9 голосов
/ 16 июня 2011

Я пытаюсь решить проблему Project Euler # 12:

Последовательность чисел треугольника генерируется путем добавления натурального номера. Итак, номер 7-го треугольника будет 1 + 2 + 3 + 4 + 5 + 6 + 7 = 28. Первые десять слагаемых будут:

1, 3, 6, 10, 15, 21, 28, 36, 45, 55, ...

Перечислим факторы первых семи чисел треугольника:

 1: 1
 3: 1,3
 6: 1,2,3,6
10: 1,2,5,10
15: 1,3,5,15
21: 1,3,7,21
28: 1,2,4,7,14,28

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

Вот решение, которое я придумал, используя Ruby:

triangle_number = 1
(2..9_999_999_999_999_999).each do |i|
  triangle_number += i
  num_divisors = 2 # 1 and the number divide the number always so we don't iterate over the entire sequence
  (2..( i/2 + 1 )).each do |j|
    num_divisors += 1 if i % j == 0
  end
  if num_divisors == 500 then
    puts i
    break
  end
end

Я не должен использовать произвольное огромное число, например 9_999_999_999_999_999. Было бы лучше, если бы у нас была последовательность Math.INFINITY, как у некоторых функциональных языков. Как я могу генерировать ленивую бесконечную последовательность в Ruby?

Ответы [ 10 ]

10 голосов
/ 16 июня 2011

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

Inf = Float::INFINITY # Ruby 1.9
Inf = 1.0/0 # Ruby before 1.9
(1..Inf).include?(2305843009213693951)
# => true
(1..Inf).step(7).take(3).inject(&:+)
# => 24.0

В вашем случае

(2..Inf).find {|i| ((2..( i/2 + 1 )).select{|j| i % j == 0}.count+2)==42 }
=> 2880

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

9 голосов
/ 16 июня 2011

В Ruby> = 1.9 вы можете создать объект Enumerator, который выдает любую последовательность, которая вам нравится. Вот тот, который дает бесконечную последовательность целых чисел:

#!/usr/bin/ruby1.9

sequence = Enumerator.new do |yielder|
  number = 0
  loop do
    number += 1
    yielder.yield number
  end
end

5.times do
  puts sequence.next
end

# => 1
# => 2
# => 3
# => 4
# => 5

Или:

sequence.each do |i|
  puts i
  break if i >= 5
end

Программирование на Ruby 1.9 (он же "Книга кирки"), 3-е. ред., стр. 83, есть пример перечислителя для треугольных чисел. Должно быть легко изменить перечисленный выше перечислитель для генерации треугольных чисел. Я бы сделал это здесь, но это воспроизвело бы дословный пример, вероятно, больше, чем позволяет «добросовестное использование».

7 голосов
/ 16 июня 2011

Бесконечность определяется на Float (Ruby 1.9)

a = Float::INFINITY
puts a #=> Infinity
b = -a
puts a*b #=> -Infinity, just toying

1.upto(a) {|x| break if x >10; puts x}
6 голосов
/ 16 августа 2016

Текущие версии Ruby поддерживают генераторы:

sequence = 1.step
3 голосов
/ 20 декабря 2018

В Ruby 2.6 это становится намного проще:

(1..).each {|n| ... }

Источник: https://bugs.ruby -lang.org / Issues / 12912

3 голосов
/ 16 июня 2011

Как упоминал Амадан, вы можете использовать замыкания:

triangle = lambda { t = 0; n = 1; lambda{ t += n; n += 1; t } }[]
10.times { puts triangle[] }

Не думайте, что это намного медленнее, чем цикл.Вы также можете сохранить состояние в объекте класса, но вам нужно больше набирать:

class Tri
  def initialize
    @t = 0
    @n = 1
  end

  def next
    @t += n
    @n += 1
    @t
  end
end

t = Tri.new
10.times{ puts t.next }

Добавлено:

Для тех, кто любит longjmps:

require "generator"

tri =
  Generator.new do |g|
    t, n = 0, 1
    loop do
      t += n
      n += 1
      g.yield t
    end
  end

puts (0..19).map{ tri.next }.inspect
3 голосов
/ 16 июня 2011

Это было бы лучше всего как простой цикл.

triangle_number = 1
i  = 1
while num_divisors < 500
  i += 1
  triangle_number += i
  # ...
end
puts i
2 голосов
/ 03 января 2019

В Рождество 2018 года Руби представила бесконечный диапазон , предоставив простой новый подход к этой проблеме.

Это реализуется путем пропуска последнего символа из диапазона, например:

(1..)
(1...)
(10..)
(Time.now..)

Или обновить, используя решение Йонаса Эльфстрёма:

(2..).find { |i| ((2..( i / 2 + 1 )).select { |j| i % j == 0 }.count + 2) == 42 }

Надеюсь, это кому-нибудь пригодится!

2 голосов
/ 12 октября 2015

Основываясь на превосходном ответе Уэйна и в духе Ruby делать вещи с наименьшим количеством символов, здесь есть немного обновленная версия:

sequence = Enumerator.new { |yielder| 1.step { |num| yielder.yield num } }

Очевидно, что не решает исходную проблему Эйлера, нохорошо для генерации бесконечной последовательности целых чисел.Определенно работает для Ruby> 2.0.Наслаждайтесь!

1 голос
/ 16 июня 2011

Я считаю, что волокна (добавленные в Ruby 1.9, я считаю) могут быть близки к тому, что вы хотите. См. здесь для получения дополнительной информации или просто поиска рубиновых волокон

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...