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"))
Johnny Everson
fonte

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))
Kipton Barros
fonte
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))
Dorjee
fonte
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)
  }
Luigi Plinge
fonte
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)
}
Mike
fonte
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)
Hydrosan
fonte