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.