Concatenazione elenco Scala, ::: vs ++


362

C'è qualche differenza tra :::e ++per concatenare elenchi in Scala?

scala> List(1,2,3) ++ List(4,5)
res0: List[Int] = List(1, 2, 3, 4, 5)

scala> List(1,2,3) ::: List(4,5)
res1: List[Int] = List(1, 2, 3, 4, 5)

scala> res0 == res1
res2: Boolean = true

Dalla documentazione sembra che ++sia più generale mentre :::è Listspecifico. Quest'ultimo è fornito perché utilizzato in altri linguaggi funzionali?


4
Inoltre :::è un operatore di prefisso come tutti i metodi che iniziano con:
Ben Jackson,

3
Le risposte delineano praticamente il modo in cui Scala si è evoluta attorno alle liste e all'uniformità dell'operatore in Scala (o alla mancanza di quest'ultima). È un po 'sfortunato che qualcosa di così semplice abbia una coda così lunga di minuzie da confondere e perdere il tempo di ogni studente di Scala. Vorrei che fosse livellato in 2.12.
matanster

Risposte:


321

Legacy. L'elenco è stato originariamente definito per essere funzionale alle lingue:

1 :: 2 :: Nil // a list
list1 ::: list2  // concatenation of two lists

list match {
  case head :: tail => "non-empty"
  case Nil          => "empty"
}

Naturalmente, Scala ha evoluto altre collezioni, in modo ad hoc. Quando 2.8 è uscito, le collezioni sono stati ridisegnati per il massimo riutilizzo del codice e API coerente, in modo che è possibile utilizzare ++per concatenare ogni due collezioni - e anche iteratori. List, tuttavia, deve mantenere i suoi operatori originali, a parte uno o due che sono stati deprecati.


19
Quindi è buona prassi evitare :::a favore di ++adesso? Usa anche +:invece di ::?
Luigi Plinge,

37
::è utile a causa del pattern matching (vedi il secondo esempio di Daniel). Non puoi farlo con+:
paradigmatico

1
@Luigi Se stai usando Listinvece di Seq, potresti anche usare Listmetodi idiomatici . D'altra parte, renderà più difficile il passaggio a un altro tipo, se mai lo desideri.
Daniel C. Sobral,

2
Trovo positivo che uno abbia sia operazioni idiomatiche di Elenco (come ::e :::) sia operazioni più generali comuni ad altre raccolte. Non lascerei perdere nessuna operazione dalla lingua.
Giorgio,

21
@paradigmatic Scala 2.10 ha :+e +:estrattori di oggetti.
0__

97

Usa sempre :::. Ci sono due ragioni: efficienza e sicurezza del tipo.

Efficienza

x ::: y ::: zè più veloce di x ++ y ++ z, perché :::è giusto associativo. x ::: y ::: zviene analizzato come x ::: (y ::: z), che è algoritmicamente più veloce di (x ::: y) ::: z(quest'ultimo richiede O (| x |) più passaggi).

Digitare sicurezza

Con :::te puoi concatenare solo due Lists. Con ++te puoi aggiungere qualsiasi raccolta a List, il che è terribile:

scala> List(1, 2, 3) ++ "ab"
res0: List[AnyVal] = List(1, 2, 3, a, b)

++è anche facile da confondere con +:

scala> List(1, 2, 3) + "ab"
res1: String = List(1, 2, 3)ab

9
Quando si concatenano solo 2 elenchi, non c'è alcuna differenza, ma nel caso di 3 o più, hai un buon punto e l'ho confermato con un rapido benchmark. Tuttavia, se sei preoccupato per l'efficienza, x ::: y ::: zdovrebbe essere sostituito con List(x, y, z).flatten. pastebin.com/gkx7Hpad
Luigi Plinge,

3
Spiegare perché la concatenazione associativa sinistra richiede più passaggi O (x). Pensavo che entrambi funzionassero per O (1).
Pacman,

6
Le liste di @pacman sono collegate singolarmente, per aggiungere una lista a un'altra devi fare una copia della prima lista che ha la seconda allegata alla fine. La concatenazione è quindi O (n) rispetto al numero di elementi nel primo elenco. La lunghezza del secondo elenco non influisce sul tempo di esecuzione, quindi è meglio aggiungere un elenco lungo a uno corto anziché aggiungere un elenco breve a uno lungo.
puhlen,

1
Le liste di @pacman Scala sono immutabili . Ecco perché non possiamo semplicemente sostituire l'ultimo collegamento durante la concatenazione. Dobbiamo creare un nuovo elenco da zero.
Zheka Kozlov,

4
@pacman La complessità è sempre lineare per la lunghezza di xe y( znon viene mai ripetuta in ogni caso, quindi non ha alcun effetto sul tempo di esecuzione, ecco perché è meglio aggiungere un lungo elenco a un breve, piuttosto che viceversa) ma la complessità asintotica non racconta l'intera storia. x ::: (y ::: z)itera ye accoda z, quindi itera xe accoda il risultato di y ::: z. xe ysono entrambi ripetuti una volta. (x ::: y) ::: zitera xe aggiunge y, quindi itera il risultato x ::: ye lo aggiunge z. yviene ancora ripetuto una volta, ma xin questo caso viene ripetuto due volte.
puhlen,

84

:::funziona solo con elenchi, mentre ++può essere utilizzato con qualsiasi attraversabile. Nell'attuale implementazione (2.9.0), ++ricade :::se l'argomento è anche a List.


4
Quindi è molto facile usare sia ::: che ++ lavorando con la lista. Che potenzialmente può mettere un casino in codice / stile.
SES

24

Un altro punto è che la prima frase viene analizzata come:

scala> List(1,2,3).++(List(4,5))
res0: List[Int] = List(1, 2, 3, 4, 5)

Considerando che il secondo esempio è analizzato come:

scala> List(4,5).:::(List(1,2,3))
res1: List[Int] = List(1, 2, 3, 4, 5)

Quindi se stai usando le macro, dovresti prenderti cura.

Inoltre, ++per due elenchi si chiama :::ma con un overhead maggiore perché richiede un valore implicito per avere un builder da Elenco a Elenco. Ma i microbenchmark non si sono rivelati utili in tal senso, suppongo che il compilatore ottimizzi tali chiamate.

Micro-benchmark dopo il riscaldamento.

scala>def time(a: => Unit): Long = { val t = System.currentTimeMillis; a; System.currentTimeMillis - t}
scala>def average(a: () => Long) = (for(i<-1 to 100) yield a()).sum/100

scala>average (() => time { (List[Int]() /: (1 to 1000)) { case (l, e) => l ++ List(e) } })
res1: Long = 46
scala>average (() => time { (List[Int]() /: (1 to 1000)) { case (l, e) => l ::: List(e ) } })
res2: Long = 46

Come ha detto Daniel C. Sobrai, puoi aggiungere il contenuto di qualsiasi raccolta a un elenco usando ++, mentre con :::te puoi solo concatenare elenchi.


20
Per favore, pubblica i tuoi microbenchmark non troppo semplicistici e li voterò.
Mikaël Mayer,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.