2011-10-01 2 views
12

Sono confuso sul comportamento del metodo take nel tratto Iterator. Sembra che non consumi oggetti. Ecco un esempio:consuma elementi da una scala Iterator

scala> Iterator(1,2,3) 
res0: Iterator[Int] = non-empty iterator 

scala> res0 take 2 toArray 
res1: Array[Int] = Array(1, 2) 

scala> res0.next 
res2: Int = 1 

Apparentemente Fase 2 consuma due voci, ma al punto 3 del Iterator è ancora al primo elemento. Osservando l'implementazione, non riesco a vedere alcun tipo di copia o buffering, solo un nuovo Iterator che delega a quello sottostante. Come potrebbe essere possibile? Come posso davvero consumare gli articoli n?

risposta

10

L'iteratore in questione è definito in IndexedSeqLike#Elements (source). A ticket was recently filed sul comportamento incoerente di take in diverse implementazioni iteratore.

Per consumare realmente N elementi, chiamare Iterator#next N volte.

Si potrebbe prendere in considerazione l'utilizzo di Stream, che è un pigro (come Iterator), ma è anche immutabile (a differenza di Iterator).

scala> val s = Stream(1, 2, 3) 
s: scala.collection.immutable.Stream[Int] = Stream(1, ?) 

scala> s.take(2).toList 
res43: List[Int] = List(1, 2) 

scala> s.take(2).toList 
res44: List[Int] = List(1, 2) 

scala> s.drop(2).toList 
res45: List[Int] = List(3) 

scala> {val (s1, s2) = s.splitAt(2); (s1.toList, s2.toList)} 
res46: (List[Int], List[Int]) = (List(1, 2),List(3)) 
+0

Grazie retronym, vedo il punto. Ciò che mi ha fatto scartare "Stream" era la mancanza di qualcosa come un "puntatore corrente" (a proposito, forse a causa dell'influenza di Java, qualcosa che ci si potrebbe aspettare in una cosa chiamata così). Ora vedo che potrei ottenere un "puntatore corrente" con una sequenza di 'drop' e guardare' head'. Ci riproverò. – jglatre

1

si vuole consumare gli oggetti, li drop. Si noti che la maggior parte dei metodi richiamati su un Iterator renderà tale Iterator inutile per un ulteriore utilizzo - inutile nel senso che il comportamento non è definito e soggetto a modifiche.

+0

'drop' è in realtà soggetto alla stessa incoerenza di' take'. '{val i = Iterator (1, 2, 3); i.drop (1); i.next} 'restituisce' 1', non '2'. – retronym

+1

@retronym Dopo aver chiamato 'drop' su' i', 'i' non può essere più utilizzato in modo affidabile. Invece, devi usare cosa restituito "drop". –

+0

Buon punto. '{val i = Iterator (1, 2, 3); i.drop (1) .next} 'restituisce 2. Tutto tranne' next() 'e' hasNext() '(potenzialmente) invalida l'iteratore originale; sebbene questi metodi possano restituire un * nuovo *, valido, iteratore. Attendo con ansia il vostro contributo alla documentazione per chiarire questo aspetto. – retronym

2

Grazie ragazzi.

Questa è la mia soluzione di consumare mazzi di oggetti da un Iterator:

implicit def consumable(i: Iterator[_]) = new { 
    def next(n: Int) = { 
     (for (_ <- 1 to n) yield i.next()).iterator 
    } 
    def skip(n: Int) { 
     (1 to n).foreach(_ => i.next()) 
    } 
    } 

commenti saranno i benvenuti.

+0

Per coerenza con il tipo restituito, la definizione deve essere: implicable def consumable [T] (i: Iterator [T]) altrimenti restituisce Iterator [Any] – Miquel