Dividir la lista en varias listas con un número fijo de elementos

119

¿Cómo dividir una lista de elementos en listas con como máximo N elementos?

Ej .: Dada una lista con 7 elementos, cree grupos de 4, dejando el último grupo posiblemente con menos elementos.

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

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

Respuestas:

213

Creo que estás buscando grouped. Devuelve un iterador, pero puede convertir el resultado en una 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
fuente
25
Las listas Scala tienen algo para todo.
J Atkin
Tengo una pregunta extraña. En el mismo caso, si convierto los datos en una secuencia, obtengo un objeto Stream. ¿Porqué es eso?
Rakshith
3
@Rakshith Eso suena como una pregunta aparte. Scala tiene un gnomo misterioso que elige una estructura de datos y eligió un Stream por ti. Si desea una Lista, debe solicitar una Lista, pero también puede confiar en el juicio del gnomo.
Ion Freeman
12

Hay una forma mucho más sencilla de realizar la tarea mediante el método deslizante. Funciona de esta manera:

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

Supongamos que desea dividir la lista en listas más pequeñas de tamaño 3.

numbers.sliding(3, 3).toList

Te regalaré

List(List(1, 2, 3), List(4, 5, 6), List(7))
Dorjee
fuente
9

O si quieres hacer el tuyo propio:

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

Utilizar:

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 : al revisar esto 2 años después, no recomendaría esta implementación ya que sizees O (n), y por lo tanto este método es O (n ^ 2), lo que explicaría por qué el método integrado se vuelve más rápido para listas grandes, como se indica en los comentarios a continuación. Puede implementar de manera eficiente de la siguiente manera:

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 incluso (un poco) más eficientemente 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
fuente
4
xs splitAt nes una alternativa a la combinación xs take nyxs drop n
Kipton Barros
1
esto explotará la pila, considere una implementación recursiva
Jed Wesley-Smith
@Kipton, cierto, pero debe extraer los resultados a valores temporales para que agregue un par de líneas a un método. Hice una evaluación comparativa rápida y parece que el uso en splitAtlugar de take/ dropmejora el rendimiento en promedio alrededor del 4%; ¡Ambos son 700-1000% más rápidos que .grouped(n).toList!
Luigi Plinge
@Luigi, Guau. ¿Alguna idea sobre por qué grouped-toListes tan lento? Eso suena como un error.
Kipton Barros
@Jed Tiene razón en casos extremos, pero su implementación depende de para qué la esté usando. Para el caso de uso de OP (si groupedno existiera :)), la simplicidad es el factor primordial. Para la biblioteca estándar, la estabilidad y el rendimiento deben triunfar sobre la elegancia. Pero hay muchos ejemplos tanto en Programación en Scala como en las bibliotecas estándar de llamadas recursivas normales (en lugar de recursivas al final); es un arma estándar e importante en la caja de herramientas FP.
Luigi Plinge
4

Estoy agregando una versión recursiva de cola del método de división ya que hubo alguna discusión sobre recursión de cola versus recursión. He usado la anotación tailrec para obligar al compilador a quejarse en caso de que la implementación no sea realmente recusiva. Creo que la recursividad de cola se convierte en un bucle bajo el capó y, por lo tanto, no causará problemas incluso para una lista grande, ya que la pila no crecerá 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)
}
Miguel
fuente
1
Esta respuesta podría mejorarse agregando alguna explicación. Dado que la respuesta aceptada parece ser la forma canónica e intencionada de hacer esto, debe explicar por qué alguien preferiría esta respuesta.
Jeffrey Bosboom
0

Creo que esta es la implementación que usa splitAt en lugar 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
fuente