Dividi l'elenco in più elenchi con un numero fisso di elementi


119

Come dividere un elenco di elementi in elenchi con al massimo N elementi?

es: Dato un elenco con 7 elementi, crea gruppi di 4, lasciando l'ultimo gruppo possibilmente con meno elementi.

split(List(1,2,3,4,5,6,"seven"),4)

=> List(List(1,2,3,4), List(5,6,"seven"))

Risposte:


213

Penso che tu stia cercando grouped. Restituisce un iteratore, ma puoi convertire il risultato in un elenco,

scala> List(1,2,3,4,5,6,"seven").grouped(4).toList
res0: List[List[Any]] = List(List(1, 2, 3, 4), List(5, 6, seven))

25
Le liste Scala hanno qualcosa per tutto.
J Atkin,

Ho una domanda strana. Nello stesso caso, se converto i dati in una sequenza, ottengo un oggetto Stream. Perché?
Rakshith

3
@Rakshith Sembra una domanda a parte. Scala ha un misterioso gnomo che sceglie una struttura dati e ha scelto uno Stream per te. Se vuoi una lista, dovresti richiedere una lista, ma puoi anche fidarti del giudizio dello gnomo.
Ion Freeman

12

C'è un modo molto più semplice per eseguire l'attività utilizzando il metodo di scorrimento. Funziona in questo modo:

val numbers = List(1, 2, 3, 4, 5, 6 ,7)

Supponiamo che tu voglia suddividere l'elenco in elenchi più piccoli di dimensione 3.

numbers.sliding(3, 3).toList

ti darà

List(List(1, 2, 3), List(4, 5, 6), List(7))

9

O se vuoi crearne uno tuo:

def split[A](xs: List[A], n: Int): List[List[A]] = {
  if (xs.size <= n) xs :: Nil
  else (xs take n) :: split(xs drop n, n)
}

Uso:

scala> split(List(1,2,3,4,5,6,"seven"), 4)
res15: List[List[Any]] = List(List(1, 2, 3, 4), List(5, 6, seven))

modifica : dopo aver rivisto questo 2 anni dopo, non consiglierei questa implementazione poiché sizeè O (n), e quindi questo metodo è O (n ^ 2), il che spiegherebbe perché il metodo integrato diventa più veloce per elenchi di grandi dimensioni, come indicato nei commenti di seguito. È possibile implementare in modo efficiente come segue:

def split[A](xs: List[A], n: Int): List[List[A]] =
  if (xs.isEmpty) Nil 
  else (xs take n) :: split(xs drop n, n)

o anche (leggermente) più efficiente utilizzando splitAt:

def split[A](xs: List[A], n: Int): List[List[A]] =
  if (xs.isEmpty) Nil 
  else {
    val (ys, zs) = xs.splitAt(n)   
    ys :: split(zs, n)
  }

4
xs splitAt nè un'alternativa alla combinazione xs take nexs drop n
Kipton Barros

1
questo esploderà lo stack, considera un'implementazione ricorsiva
Jed Wesley-Smith

@ Kipton, vero, ma devi estrarre i risultati in vals temporanei in modo che aggiunga un paio di righe a un metodo. Ho fatto un rapido benchmark e sembra che l'utilizzo al splitAtposto di take/ dropmigliori le prestazioni in media intorno al 4%; entrambi sono 700-1000% più veloci di .grouped(n).toList!
Luigi Plinge

@Luigi, Wow. Qualche idea sul perché grouped-toListè così lento? Sembra un insetto.
Kipton Barros

@Jed Hai ragione in casi estremi, ma la tua implementazione dipende da cosa lo usi. Per il caso d'uso di OP (se groupednon esistesse :)), la semplicità è il fattore prioritario. Per la libreria standard, stabilità e prestazioni dovrebbero prevalere sull'eleganza. Ma ci sono molti esempi sia in Programming in Scala che nelle librerie standard di chiamate ricorsive normale (piuttosto che ricorsive in coda); è un'arma standard e importante nella cassetta degli attrezzi FP.
Luigi Plinge

4

Sto aggiungendo una versione ricorsiva della coda del metodo split poiché si è discusso della ricorsione della coda rispetto alla ricorsione. Ho usato l'annotazione tailrec per costringere il compilatore a lamentarsi nel caso in cui l'implementazione non sia effettivamente recusiva. Ritengo che la ricorsione della coda si trasformi in un ciclo nascosto e quindi non causerà problemi anche per un elenco di grandi dimensioni poiché lo stack non crescerà indefinitamente.

import scala.annotation.tailrec


object ListSplitter {

  def split[A](xs: List[A], n: Int): List[List[A]] = {
    @tailrec
    def splitInner[A](res: List[List[A]], lst: List[A], n: Int) : List[List[A]] = {
      if(lst.isEmpty) res
      else {
        val headList: List[A] = lst.take(n)
        val tailList : List[A]= lst.drop(n)
        splitInner(headList :: res, tailList, n)
      }
    }

    splitInner(Nil, xs, n).reverse
  }

}

object ListSplitterTest extends App {
  val res = ListSplitter.split(List(1,2,3,4,5,6,7), 2)
  println(res)
}

1
Questa risposta potrebbe essere migliorata aggiungendo qualche spiegazione. Dato che la risposta accettata sembra essere il modo canonico e inteso per farlo, dovresti spiegare perché qualcuno preferirebbe questa risposta.
Jeffrey Bosboom

0

Penso che questa sia l'implementazione che utilizza splitAt invece di take / drop

def split [X] (n:Int, xs:List[X]) : List[List[X]] =
    if (xs.size <= n) xs :: Nil
    else   (xs.splitAt(n)._1) :: split(n,xs.splitAt(n)._2)
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.