Diviser la liste en plusieurs listes avec un nombre fixe d'éléments


119

Comment diviser une liste d'éléments en listes avec au plus N éléments?

ex: Étant donné une liste de 7 éléments, créez des groupes de 4, laissant éventuellement le dernier groupe avec moins d'éléments.

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

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

Réponses:


213

Je pense que vous cherchez grouped. Il renvoie un itérateur, mais vous pouvez convertir le résultat en liste,

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
Les listes Scala ont quelque chose pour tout.
J Atkin

J'ai une question étrange. Dans le même cas, si je convertis les données en séquence, j'obtiens un objet Stream. Pourquoi donc?
Rakshith

3
@Rakshith Cela ressemble à une question distincte. Scala a un gnome mystérieux qui choisit une structure de données, et il a choisi un Stream pour vous. Si vous voulez une liste, vous devriez demander une liste, mais vous pouvez aussi simplement vous fier au jugement du gnome.
Ion Freeman

12

Il existe un moyen beaucoup plus simple d'effectuer la tâche en utilisant la méthode de glissement. Cela fonctionne de cette façon:

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

Disons que vous souhaitez diviser la liste en listes plus petites de taille 3.

numbers.sliding(3, 3).toList

te donnera

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

9

Ou si vous souhaitez créer le vôtre:

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

Utilisation:

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

edit : après avoir examiné cela 2 ans plus tard, je ne recommanderais pas cette implémentation car sizeest O (n), et donc cette méthode est O (n ^ 2), ce qui expliquerait pourquoi la méthode intégrée devient plus rapide pour les grandes listes, comme indiqué dans les commentaires ci-dessous. Vous pouvez mettre en œuvre efficacement comme suit:

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 même (légèrement) plus efficacement en utilisant 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 nest une alternative à la combinaison xs take netxs drop n
Kipton Barros

1
cela fera exploser la pile, considérons une implémentation récursive
Jed Wesley-Smith

@Kipton, vrai, mais vous devez extraire les résultats dans des valeurs temporaires afin d'ajouter quelques lignes à une méthode. J'ai fait un benchmark rapide et il semble utiliser splitAtau lieu de take/ dropaméliore les performances en moyenne autour de 4%; les deux sont 700-1000% plus rapides que .grouped(n).toList!
Luigi Plinge

@Luigi, Wow. Des pensées sur pourquoi grouped-toListest-ce si lent? Cela ressemble à un bug.
Kipton Barros

@Jed Vous avez raison dans les cas extrêmes, mais votre implémentation dépend de l'utilisation que vous en faites. Pour le cas d'utilisation d'OP (s'il groupedn'existait pas :)), la simplicité est le facteur primordial. Pour la bibliothèque standard, la stabilité et les performances doivent l'emporter sur l'élégance. Mais il y a beaucoup d'exemples à la fois dans Programming in Scala et dans les bibliothèques standard d'appels normaux-récursifs (plutôt que tail-récursifs); c'est une arme standard et importante dans la boîte à outils FP.
Luigi Plinge

4

J'ajoute une version récursive de queue de la méthode de fractionnement car il y avait une discussion de la récursion de queue par rapport à la récursivité. J'ai utilisé l'annotation tailrec pour forcer le compilateur à se plaindre au cas où l'implémentation ne serait pas vraiment recusive. Je crois que la récursivité de la queue se transforme en boucle sous le capot et ne posera donc pas de problèmes, même pour une grande liste, car la pile ne croîtra pas indéfiniment.

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
Cette réponse pourrait être améliorée en ajoutant quelques explications. Étant donné que la réponse acceptée semble être la manière canonique et prévue de le faire, vous devez expliquer pourquoi quelqu'un préférerait cette réponse.
Jeffrey Bosboom

0

Je pense que c'est l'implémentation utilisant splitAt au lieu 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)
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.