Iterar sobre una secuencia infinita en Ruby
-
28-10-2019 - |
Pregunta
Estoy tratando de resolver el Proyecto Euler Problema #12:
La secuencia de números de triángulo se genera agregando los números naturales. Entonces, el séptimo número de triángulo sería 1 + 2 + 3 + 4 + 5 + 6 + 7 = 28. Los primeros diez términos serían:
1, 3, 6, 10, 15, 21, 28, 36, 45, 55, ...
Enumeremos los factores de los primeros siete números de triángulo:
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
Podemos ver que 28 es el primer número de triángulo en tener más de cinco divisores. ¿Cuál es el valor del primer número de triángulo que tiene más de quinientos divisores?
Aquí está la solución que se me ocurrió usar 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
No debería usar un gran número arbitrario como 9_999_999_999_999_999. Sería mejor si tuviéramos una secuencia de Infinity Math. como algunos lenguajes funcionales. ¿Cómo puedo generar una secuencia infinita perezosa en Ruby?
Solución
En Ruby> = 1.9, puede crear un objeto enumerador que produce cualquier secuencia que desee. Aquí hay uno que produce una secuencia infinita de enteros:
#!/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
O:
sequence.each do |i|
puts i
break if i >= 5
end
Programación Ruby 1.9 (también conocido como "The Pickaxe Book"), 3er. ed., p. 83, tiene un ejemplo de enumerador para números triangulares. Debería ser fácil modificar el enumerador anterior para generar números triangulares. Lo haría aquí, pero eso reproduciría el ejemplo textual, probablemente más de lo que permite el "uso justo".
Otros consejos
Varias respuestas están cerca, pero en realidad no veo a nadie que use rangos infinitos. Ruby los apoya bien.
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
En tu caso
(2..Inf).find {|i| ((2..( i/2 + 1 )).select{|j| i % j == 0}.count+2)==42 }
=> 2880
Su método de fuerza bruta es crudo y, potencialmente, puede tardar mucho en terminar.
El infinito se define en el flotador (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}
Versiones Currrent de Generadores de soporte de Ruby en gran medida:
sequence = 1.step
Esto sería mejor como un bucle simple.
triangle_number = 1
i = 1
while num_divisors < 500
i += 1
triangle_number += i
# ...
end
puts i
Como Amadan mencionó, puede usar cierres:
triangle = lambda { t = 0; n = 1; lambda{ t += n; n += 1; t } }[]
10.times { puts triangle[] }
Realmente no pienses que es mucho más lento que un bucle. También puede guardar el estado en el objeto de clase, pero necesitará más tipificación:
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 }
Adicional:
Para aquellos a quienes les gustan los largos:
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
En Ruby 2.6 esto se vuelve mucho más fácil:
(1..).each {|n| ... }
Construir la excelente respuesta de Wayne y en el espíritu de Ruby de hacer cosas con la menor cantidad de personajes aquí hay una versión ligeramente actualizada:
sequence = Enumerator.new { |yielder| 1.step { |num| yielder.yield num } }
Obviamente, no resuelve el problema original de Euler, pero es bueno para generar una secuencia infinita de enteros. Definitivamente funciona para Ruby> 2.0. ¡Disfrutar!
El día de Navidad de 2018, Ruby presentó el rango sin fin, proporcionando un nuevo enfoque simple para este problema.
Esto se implementa omitiendo el carácter final desde el rango, por ejemplo:
(1..)
(1...)
(10..)
(Time.now..)
O para actualizar usando la solución de Jonas Elfström:
(2..).find { |i| ((2..( i / 2 + 1 )).select { |j| i % j == 0 }.count + 2) == 42 }
¡Espero que esto sea útil para alguien!
Creo que las fibras (agregadas en Ruby 1.9, creo) pueden estar cerca de lo que quieres. Ver aquí Para obtener información o simplemente buscar fibras de rubí.