2011-04-04 18 views
7

¿Hay una manera más fácil, más limpio para escribir código como este:múltiples iteraciones

(1..10).each do |i| 
    (1..10).each do |j| 
    (1..10).each do |k| 
     (1..10).each do |l| 
     puts "#{i} #{j} #{k} #{l}" 
     end 
    end 
    end 
end 

Lo ideal sería capaz de hacer algo así ...

(1..10).magic(4) { |i, j, k, l| puts "#{i} #{j} #{k} #{l}" } 

o incluso mejor ..

magic(10, 4) { |i, j, k, l| puts "#{i} #{j} #{k} #{l}" } 

Si no hay algo incorporado, ¿cómo podría escribir un método como el último?

+0

lo que la salida se puede esperar? ¿es algo así como '1 2 3 4 \ n 5 6 7 8 \ n 9 10' o' 0 0 0 1 \ n 0 0 0 2 ... '? –

+1

@nash No; el primer código se ejecuta solo. Si ajustaras todo '(1..10)' a '(0..9)', obtendrías ''0 0 0 0 '', '0 0 0 1', ... '9 9 9 8', '9 9 9 9''. – Phrogz

+0

Este es un duplicado de http://stackoverflow.com/questions/5226895/combine-array-of-array-into-all-possible-combinations-forward-only-in-ruby/5227021#5227021, pero me gusta esto las respuestas de la pregunta mejor. –

Respuesta

9

Si estás en Ruby 1.9, usted puede hacer esto:

range = (1..10).to_a 
range.repeated_permutation(4) do |set| 
    puts set.join(" ") 
end 

en Ruby 1.8: solución

range = (1..10).to_a 
range.product(range, range, range).each do |set| 
    puts set.join(" ") 
end 
+0

O incluso 'range.product ([rango] * 3) .each ...' – Phrogz

2

de dmarkow (creo) se materializa los rangos de los cuales, al menos en teoría usa más memoria de la que necesita Aquí está una manera de hacerlo sin materializar las gamas:

def magic(ranges, &block) 
    magic_ = lambda do |ranges, args, pos, block| 
    if pos == ranges.length 
     block.call(*args) 
    else 
     ranges[pos].each do |i| 
     args[pos] = i 
     magic_.call(ranges, args, pos+1, block) 
     end 
    end 
    end 
    magic_.call(ranges, [nil]*ranges.length, 0, block) 
end 

magic([1..10] * 4) do |a,b,c,d| 
    puts [a, b, c, d].inspect 
end 

Dicho esto, el rendimiento es algo complicado y que no sé qué tan eficiente Ruby es con llamadas de función, así que tal vez se pegue a las funciones de la biblioteca es la manera más rápida ir.

Actualización: Tomó la sugerencia de Phrogz y puso magic_ dentro de magic. (Actualización: Tomó la sugerencia de Phrogz nuevamente y con suerte lo hizo bien esta vez con lambda en lugar de def).

actualización: Array#product devuelve un Array, así que estoy asumiendo que se materializarán completamente. No tengo Ruby 1.9.2, pero Mladen Jablanović señaló que Array#repeated_permutation probablemente no materialice todo (a pesar de que el rango inicial se materializa con to_a).

+0

'repeat_permutation' no debe materializar una matriz completa cuando se llama sin bloque, sino un Enumerador en su lugar, que luego puede recorrer usando' each', sin crear gran estructura en la memoria (con suerte). –

+0

Casi escribí este formulario, pero me volví perezoso al pensar en la recursión. :) Tenga en cuenta, sin embargo, que con algo como esto abogo por crear una lambda 'magic_' local dentro de su método' magic' y dejar que se llame recursivamente. Con esto no hay contaminación del espacio de nombres para un método adicional no deseado. +1 para una solución muy general, sin embargo. – Phrogz

+0

Aprecio que haya aceptado mi sugerencia, pero lo que ha hecho no anida las funciones. ¡Define una nueva función exterior 'magic_' cada vez que ejecutas el método' magic'! En cambio, estaba sugiriendo: 'def magic (...); magic_ = lambda {| r, a, p, b | ... magia_[ ... ] }; magia_[ ... ]; fin' – Phrogz

2

Me he tomado la libertad de cambiar el orden de los parámetros magic bajo el supuesto de que la base 10 es más común y opcional:

def magic(digits,base=10) 
    raise "Max magic base of 36" unless base <= 36 
    (base**digits).times do |i| 
    str = "%#{digits}s" % i.to_s(base) 
    parts = str.scan(/./).map{ |n| n.to_i(base)+1 } 
    yield *parts 
    end 
end 

magic(3,2){ |a,b,c| p [a,b,c] } 
#=> [1, 1, 1] 
#=> [1, 1, 2] 
#=> [1, 2, 1] 
#=> [1, 2, 2] 
#=> [2, 1, 1] 
#=> [2, 1, 2] 
#=> [2, 2, 1] 
#=> [2, 2, 2] 

magic(2,16){ |a,b| p [a,b] } 
#=> [1, 1] 
#=> [1, 2] 
#=> [1, 3] 
#=> ... 
#=> [16, 15] 
#=> [16, 16] 

Explicación:

Al traducir el problema original de 1..10 a 0..9 y concatenando los dígitos vemos que la salida solo está contando, con acceso a cada dígito.

0000 
0001 
0002 
... 
0010 
0011 
0012 
... 
9997 
9998 
9999

Así que eso es lo que hace mi código anterior.Cuenta de 0 hasta el número máximo (basado en el número de dígitos y los valores por dígito permitido), y para cada número que:

  1. Convierte el número en la 'base' apropiado:
    i.to_s(base)            # e.g. 9.to_s(8) => "11", 11.to_s(16) => "b"

  2. Usos String#% para rellenar la cadena con el número correcto de caracteres:
    "%#{digits}s" % ...     # e.g. "%4s" % "5" => "   5"

  3. convierte a esta sola cadena en una matriz de cadena de un solo carácter s:
    str.scan(/./)           # e.g. " 31".scan(/./) => [" ","3","1"]
    Tenga en cuenta que en Ruby 1.9 esto se realiza mejor con str.chars

  4. Convierte cada una de estas cadenas de un solo carácter de nuevo en un número:
    n.to_i(base)            # e.g. "b".to_i(16) => 11, " ".to_i(3) => 0

  5. añade 1 a cada de estos números, ya que el deseo era comenzar en 1 en lugar de 0

  6. Splat this new array of numbers as arguments t o el bloque, un número por bloque param:
    yield *parts

+0

Esto es realmente genial. ¿Podría explicar las líneas 'str =' y 'parts ='? Me está costando mucho seguirlos. – Drew

+0

@Drew He actualizado la respuesta con una explicación de cómo funciona. – Phrogz

+0

Ahora entiendo, gracias. :) – Drew

Cuestiones relacionadas