Méthode préférée pour créer une liste Scala

117

Il existe plusieurs façons de construire une liste immuable dans Scala (voir l'exemple de code artificiel ci-dessous). Vous pouvez utiliser un ListBuffer mutable, créer unvar liste et la modifier, utiliser une méthode récursive de queue , et probablement d'autres que je ne connais pas.

Instinctivement, j'utilise le ListBuffer, mais je n'ai pas de bonne raison de le faire. Existe-t-il une méthode préférée ou idiomatique pour créer une liste, ou y a-t-il des situations qui conviennent le mieux à une méthode plutôt qu'à une autre?

import scala.collection.mutable.ListBuffer

// THESE are all the same as: 0 to 3 toList.
def listTestA() ={
    var list:List[Int] = Nil

    for(i <- 0 to 3) 
        list = list ::: List(i)
    list
}


def listTestB() ={
    val list = new ListBuffer[Int]()

    for (i <- 0 to 3) 
        list += i
    list.toList
}


def listTestC() ={
    def _add(l:List[Int], i:Int):List[Int] = i match {
        case 3 => l ::: List(3)
        case _ => _add(l ::: List(i), i +1)
    }
    _add(Nil, 0)
}
agilefall
la source

Réponses:

108

ListBufferest une liste mutable qui a un ajout à temps constant et une conversion à temps constant en un fichier List.

List est immuable et a un préfixe en temps constant et un ajout en temps linéaire.

La façon dont vous construisez votre liste dépend de l'algorithme pour lequel vous utiliserez la liste et de l'ordre dans lequel vous obtenez les éléments pour la créer.

Par exemple, si vous obtenez les éléments dans l'ordre inverse de leur utilisation, vous pouvez simplement utiliser un Listet faire des préfixes. Que vous le fassiez avec une fonction récursive de queue foldLeft, ou autre chose n'est pas vraiment pertinent.

Si vous obtenez les éléments dans le même ordre que vous les utilisez, alors a ListBufferest probablement un choix préférable, si les performances sont essentielles.

Mais, si vous n'êtes pas sur un chemin critique et que l'entrée est suffisamment faible, vous pouvez toujours reversela liste plus tard, ou simplement foldRight, ou reversel'entrée, qui est en temps linéaire.

Ce que vous NE FAITES PAS , c'est utiliser un Listet y ajouter. Cela vous donnera des performances bien pires que le simple début et l'inversion à la fin.

Daniel C. Sobral
la source
What you DON'T do is use a List and append to itEst-ce parce qu'une nouvelle liste est créée? Alors que l'utilisation d'une opération de préfixe ne créera pas une nouvelle liste?
Kevin Meredith
2
@KevinMeredith Oui. Ajouter est O (n), préfixer est O (1).
Daniel C. Sobral
@pgoggijr Ce n'est pas vrai. Premièrement, il n'y a aucun «changement» nulle part, car il est immuable. Une traversée est requise car tous les éléments doivent être copiés, afin qu'une copie du dernier élément puisse être faite en pointant vers un nouvel élément au lieu de Nil. Deuxièmement, il n'y a aucune copie d'aucune sorte en préfixe: un élément est créé en pointant vers la liste existante, et c'est tout.
Daniel C.Sobral
65

Et pour les cas simples:

val list = List(1,2,3) 

:)

Tomasz Nurkiewicz
la source
10
N'oubliez pas l'opérateur contre! 1 :: 2 :: 3 :: Nil
André Laszlo
22

Euhmm .. cela me semble trop complexe. Puis-je proposer

def listTestD = (0 to 3).toList

ou

def listTestE = for (i <- (0 to 3).toList) yield i
Alexandre Azarov
la source
Merci pour la réponse, mais la question est de savoir que faites-vous dans le cas non trivial. J'ai mis un commentaire dans le code expliquant qu'ils étaient tous équivalents à 0 à 3 toList.
agilefall
Oups, désolé alors! Franchement, je n'utilise jamais ListBuffer.
Alexander Azarov
5

Vous voulez vous concentrer sur l'immuabilité dans Scala généralement en éliminant toutes les variables. La lisibilité est toujours importante pour votre prochain, donc:

Essayer:

scala> val list = for(i <- 1 to 10) yield i
list: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

Vous n'avez probablement même pas besoin de convertir en liste dans la plupart des cas :)

Le seq indexé aura tout ce dont vous avez besoin:

Autrement dit, vous pouvez maintenant travailler sur cet IndexedSeq:

scala> list.foldLeft(0)(_+_)
res0: Int = 55
JasonG
la source
NB Vectorest désormais également l' Seqimplémentation par défaut .
Connor Doyle
2

Je préfère toujours List et j'utilise "plier / réduire" avant "pour la compréhension". Cependant, «pour la compréhension» est préférable si des «plis» imbriqués sont nécessaires. La récursivité est le dernier recours si je ne peux pas accomplir la tâche en utilisant "plier / réduire / pour".

donc pour votre exemple, je vais faire:

((0 to 3) :\ List[Int]())(_ :: _)

avant moi:

(for (x <- 0 to 3) yield x).toList

Remarque: j'utilise "foldRight (: \)" au lieu de "foldLeft (/ :)" ici à cause de l'ordre des "_". Pour une version qui ne lève pas StackOverflowException, utilisez plutôt "foldLeft".

Walter Chang
la source
18
Je suis fortement en désaccord; votre forme préférée ressemble simplement au bruit de ligne.
Matt R
14
Vais-je? J'ai appris Haskell pour la première fois en 1999 et j'essaye de jouer à Scala depuis quelques années. Je pense que les plis sont excellents, mais si l'application d'un pli dans une situation donnée nécessite l'écriture d'une chaîne cryptique de symboles de ponctuation, j'envisagerais une approche différente.
Matt R
11
@Matt R: Je suis d'accord. Il y a une telle chose comme exagérer, et c'est l'un d'entre eux.
ryeguy le
8
@WalterChang J'aime le look de toutes ces émoticônes. Attendez une minute, c'est ce code? : P
David J.
4
Est-il juste d'appeler ((0 to 3) :\ List[Int]())(_ :: _)émoticode?
David J.
2

En utilisant List.tabulate, comme ça,

List.tabulate(3)( x => 2*x )
res: List(0, 2, 4)

List.tabulate(3)( _ => Math.random )
res: List(0.935455779102479, 0.6004888906328091, 0.3425278797788426)

List.tabulate(3)( _ => (Math.random*10).toInt )
res: List(8, 0, 7)
orme
la source
2

Remarque: Cette réponse est écrite pour une ancienne version de Scala.

Les classes de la collection Scala vont être repensées à partir de Scala 2.8, alors soyez prêt à changer très bientôt la façon dont vous créez des listes.

Quelle est la méthode compatible avec la transmission avant de créer une liste? Je n'en ai aucune idée car je n'ai pas encore lu la documentation 2.8.

Un document PDF décrivant les modifications proposées des classes de collection

André Laszlo
la source
2
La plupart des changements concernent la manière dont les choses sont implémentées en interne et des éléments avancés comme les projections. La façon dont vous créez une liste n'est pas affectée.
Marcus Downing
Ok, c'est bon à savoir. Vous serez également affecté si vous utilisez une classe dans le package collection.jcl.
André Laszlo
1

En tant que nouveau développeur scala, j'ai écrit un petit test pour vérifier le temps de création de la liste avec les méthodes suggérées ci-dessus. Cela ressemble à (for (p <- (0 to x)) yield p) toList l'approche la plus rapide.

import java.util.Date
object Listbm {

  final val listSize = 1048576
  final val iterationCounts = 5
  def getCurrentTime: BigInt = (new Date) getTime

  def createList[T] ( f : Int => T )( size : Int ): T = f ( size )

  // returns function time execution
  def experiment[T] ( f : Int => T ) ( iterations: Int ) ( size :Int ) : Int  = {

    val start_time = getCurrentTime
    for ( p <- 0 to iterations )  createList ( f ) ( size )
    return (getCurrentTime - start_time) toInt

  }

  def printResult ( f:  => Int ) : Unit = println ( "execution time " + f  )

  def main( args : Array[String] ) {


    args(0) match {

      case "for" =>  printResult ( experiment ( x => (for ( p <- ( 0 to x ) ) yield p) toList  ) ( iterationCounts ) ( listSize ) )
      case "range"  =>  printResult ( experiment ( x => ( 0 to x ) toList ) ( iterationCounts ) ( listSize ) )
      case "::" => printResult ( experiment ( x => ((0 to x) :\ List[Int]())(_ :: _) ) ( iterationCounts ) ( listSize ) )
      case _ => println ( "please use: for, range or ::\n")
    }
  }
}
Yuli Reiri
la source
0

juste un exemple qui utilise collection.breakOut

scala> val a : List[Int] = (for( x <- 1 to 10 ) yield x * 3)(collection.breakOut)
a: List[Int] = List(3, 6, 9, 12, 15, 18, 21, 24, 27, 30)

scala> val b : List[Int] = (1 to 10).map(_ * 3)(collection.breakOut)
b: List[Int] = List(3, 6, 9, 12, 15, 18, 21, 24, 27, 30)
Arne
la source
0

Pour créer une liste de chaînes, utilisez ce qui suit:

val l = List("is", "am", "are", "if")
KayV
la source
1
Lorsque vous répondez à une question aussi ancienne (10 ans), et avec autant de réponses existantes (9), c'est une bonne pratique d'expliquer pourquoi votre réponse est différente de toutes les autres. Dans l'état actuel des choses, il semble que vous n'ayez pas compris la question.
jwvh