Divida a lista em várias listas com um número fixo de elementos


119

Como dividir uma lista de elementos em listas com no máximo N itens?

ex: Dada uma lista com 7 elementos, crie grupos de 4, deixando o último grupo possivelmente com menos elementos.

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

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

Respostas:


213

Acho que você está procurando grouped. Ele retorna um iterador, mas você pode converter o resultado em uma lista,

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
As listas do Scala têm algo para tudo.
— J Atkin

Eu tenho uma pergunta estranha. Para o mesmo caso, se eu converter os dados em uma sequência, obtenho um Stream Object. Por que é que?
— Rakshith

3
@Rakshith Isso soa como uma pergunta separada. Scala tem um gnomo misterioso que escolhe uma estrutura de dados e um fluxo para você. Se você quiser uma lista, deve solicitar uma lista, mas também pode simplesmente confiar no julgamento do gnomo.
— Ion Freeman

12

Existe uma maneira muito mais fácil de fazer a tarefa usando o método deslizante. Funciona assim:

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

Digamos que você queira dividir a lista em listas menores de tamanho 3.

numbers.sliding(3, 3).toList

Darei à você

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

9

Ou se você quiser fazer o seu próprio:

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)
}

Usar:

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))

editar : ao revisar isso 2 anos depois, eu não recomendaria esta implementação, pois sizeé O (n) e, portanto, este método é O (n ^ 2), o que explicaria por que o método integrado se torna mais rápido para listas grandes, conforme observado nos comentários abaixo. Você pode implementar de forma eficiente da seguinte maneira:

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

ou mesmo (ligeiramente) mais eficiente usando 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é uma alternativa à combinação xs take nexs drop n
— Kipton Barros

1
isso explodirá a pilha, considere uma implementação recursiva
— Jed Wesley-Smith,

@Kipton, verdadeiro, mas você precisa extrair os resultados para vals temporários, então ele adiciona algumas linhas a um método. Fiz um benchmark rápido e parece que usa ao splitAtinvés de take/ dropmelhora o desempenho em média em torno de 4%; ambos são 700-1000% mais rápidos do que .grouped(n).toList!
— Luigi Plinge

@Luigi, uau. Alguma ideia de por que grouped-toListé tão lento? Isso parece um bug.
— Kipton Barros

@Jed Você está certo em casos extremos, mas sua implementação depende do que você está usando. Para o caso de uso do OP (se groupednão existisse :)), a simplicidade é o fator predominante. Para a biblioteca padrão, estabilidade e desempenho devem superar a elegância. Mas há muitos exemplos em Programação em Scala e nas bibliotecas padrão de chamadas normal-recursivas (em vez de recursivas de cauda); é uma arma padrão e importante na caixa de ferramentas FP.
— Luigi Plinge

4

Estou adicionando uma versão recursiva de cauda do método de divisão, uma vez que houve alguma discussão sobre recursão de cauda versus recursão. Usei a anotação tailrec para forçar o compilador a reclamar no caso de a implementação não ser de fato não-conformista. Acredito que a recursão de cauda se transforme em um loop por baixo do capô e, portanto, não causará problemas mesmo para uma lista grande, pois a pilha não crescerá indefinidamente.

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
Esta resposta pode ser melhorada adicionando alguma explicação. Dado que a resposta aceita parece ser a forma canônica pretendida de fazer isso, você deve explicar por que alguém prefere essa resposta.
— Jeffrey Bosboom

0

Acho que esta é a implementação usando splitAt em vez de 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)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.