2014-05-21 11 views
5

Faccio fatica a capire come funziona il metodo Enumerator.new. Supponendo esempio dalla documentazione:Come funziona Enumerator.new con il blocco passato?

fib = Enumerator.new do |y| 
    a = b = 1 
    loop do 
    y << a 
    a, b = b, a + b 
    end 
end 

p fib.take(10) # => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] 

dov'è la condizione di interruzione del ciclo, come fa a sapere quante volte ciclo dovrebbe per scorrere (in quanto non ha alcuna condizione di interruzione esplicita e si presenta come loop infinito)?

risposta

4

Enumerator utilizza Fibers internamente. Il tuo esempio è equivalente a:

require 'fiber' 

fiber = Fiber.new do 
    a = b = 1 
    loop do 
    Fiber.yield a 
    a, b = b, a + b 
    end 
end 

10.times.map { fiber.resume } 
#=> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] 
+0

Molto interessante! –

4

Modifica Penso di capire la tua domanda ora, terrò ancora la mia risposta originale qui sotto.

y << a è uno pseudonimo per y.yield(a), che è fondamentalmente un sleep con valore di ritorno. Ogni volta che viene richiesto un valore dall'enumeratore con next, l'esecuzione viene continuata fino a quando non viene restituito un altro valore.


Enumerators non hanno bisogno di elencare un numero finito di elementi, in modo che sono infinito. Ad esempio, fib.to_a non si interromperà mai, perché tenta di creare un array con un numero infinito di elementi.

Come tali, gli enumeratori sono grandi come una rappresentazione di serie infinite come i numeri naturali o, nel tuo caso, i numeri di Fibonacci. L'utente dell'enumeratore può decidere quanti valori ha bisogno, quindi nell'esempio take(10) determina la condizione di interruzione se lo si desidera.

Lo stato di interruzione si trova nell'implementazione di Enumerator#take. A scopo dimostrativo, siamo in grado di rendere la nostra applicazione chiamata my_take:

class Enumerator 
    def my_take(n) 
    result = [] 
    n.times do 
     result << self.next 
    end 
    result 
    end 
end 

dove si poteva, naturalmente, "mentalmente sostituire" il ciclo n.times con il C stile classico for (i=0; i<n; i++). C'è la tua condizione di rottura. self.next è il metodo per ottenere il valore successivo del enumeratore, che è possibile utilizzare anche al di fuori della classe:

fib.next 
#=> 1 
fib.next 
#=> 1 
fib.next 
#=> 2 
fib.next 
#=> 3 

Detto questo, si può ovviamente costruire un enumeratore che enumera un numero finito di valori, come ad esempio i numeri naturali in un determinato intervallo, ma non è questo il caso. Quindi, l'Enumeratore genererà un errore StopIteration quando si tenta di chiamare next, ma tutti i valori sono già stati enumerati. In tal caso, hai due condizioni di pausa, per così dire; quello che si rompe prima vincerà. take in realtà lo gestisce salvando dall'errore, quindi il codice seguente è un po 'più vicino all'implementazione reale (tuttavia, take è in realtà implementato in C).

class Enumerator 
    def my_take(n) 
    result = [] 
    n.times do 
     result << self.next 
    end 
    result 
    rescue StopIteration 
    # enumerator stopped early 
    result 
    end 
end 
+0

Fresco, ma dove è una dichiarazione che rompe il ciclo? '<<'? –

+0

@LeszekAndrukanis vedere la mia modifica. L'enumeratore è davvero infinito, non vi è alcuna condizione di rottura nell'enumeratore. Spetta al consumatore dei valori decidere quanti valori vuole! –

+0

@ p11y Penso che l'OP voglia sapere come Ruby riesce a fermare il ciclo infinito dopo ogni iterazione – Stefan

Problemi correlati