Forma preferida de criar uma lista Scala

117

Existem várias maneiras de construir uma lista imutável no Scala (consulte o código de exemplo inventado abaixo). Você pode usar um ListBuffer mutável, criar uma varlista e modificá-la, usar um método recursivo de cauda e provavelmente outros que eu não conheço.

Instintivamente, eu uso o ListBuffer, mas não tenho um bom motivo para fazer isso. Existe um método preferencial ou idiomático para criar uma lista ou há situações que são melhores para um método em vez de outro?

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)
}
queda ágil
fonte

Respostas:

108

ListBufferé uma lista mutável que possui acréscimo de tempo constante e conversão de tempo constante em um List.

List é imutável e tem acréscimo de tempo constante e acréscimo de tempo linear.

Como você constrói sua lista depende do algoritmo para o qual você usará a lista e da ordem em que você obtém os elementos para criá-la.

Por exemplo, se você obtiver os elementos na ordem oposta de quando eles serão usados, você pode apenas usar a Liste fazer prefixos. Se você vai fazer isso com uma função recursiva de cauda foldLeft, ou outra coisa, não é realmente relevante.

Se você obtiver os elementos na mesma ordem em que os usa, ListBufferprovavelmente é uma escolha preferível, se o desempenho for crítico.

Mas, se você não estiver em um caminho crítico e a entrada for baixa o suficiente, você pode sempre reverselistar mais tarde, ou apenas foldRight, ou reversea entrada, que é de tempo linear.

O que você NÃO faz é usar um Liste anexar a ele. Isso proporcionará um desempenho muito pior do que apenas antecipar e reverter no final.

Daniel C. Sobral
fonte
What you DON'T do is use a List and append to itÉ porque uma nova lista foi criada? Visto que usar uma operação prefixar não criará uma nova lista?
Kevin Meredith
2
@KevinMeredith Sim. Anexar é O (n), anexar é O (1).
Daniel C. Sobral
@pgoggijr Isso não é verdade. Primeiro, não há "mudança" em lugar nenhum, porque é imutável. Uma travessia é necessária porque todos os elementos devem ser copiados, apenas para que uma cópia do último elemento possa ser feita apontando para um novo elemento em vez de Nil. Em segundo lugar, não há cópia de nenhum tipo no prefixo: um elemento é criado apontando para a lista existente e é isso.
Daniel C. Sobral
65

E para casos simples:

val list = List(1,2,3) 

:)

Tomasz Nurkiewicz
fonte
10
Não se esqueça do operador contras! 1 :: 2 :: 3 :: Nulo
André Laszlo
22

Uhmm .. isso parece muito complexo para mim. Posso propor

def listTestD = (0 to 3).toList

ou

def listTestE = for (i <- (0 to 3).toList) yield i
Alexander Azarov
fonte
Obrigado pela resposta, mas a questão é o que você faz no caso não trivial. Eu coloquei um comentário no código explicando que eles eram todos equivalentes a 0 a 3 para Listar.
agilefall
Opa, desculpe então! Francamente, eu nunca uso ListBuffer.
Alexander Azarov
5

Você deseja focar na imutabilidade no Scala geralmente eliminando qualquer vars. A legibilidade ainda é importante para o seu próximo, então:

Experimentar:

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)

Você provavelmente nem precisa converter para uma lista na maioria dos casos :)

A seq indexada terá tudo que você precisa:

Ou seja, agora você pode trabalhar nesse IndexedSeq:

scala> list.foldLeft(0)(_+_)
res0: Int = 55
JasonG
fonte
NB Vectoragora é também a Seqimplementação padrão .
Connor Doyle
2

Eu sempre prefiro List e uso "dobrar / reduzir" antes de "para compreensão". No entanto, "para compreensão" é preferível se "dobras" aninhadas forem necessárias. A recursão é o último recurso se eu não conseguir realizar a tarefa usando "dobrar / reduzir / para".

então, para o seu exemplo, farei:

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

antes de eu fazer:

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

Nota: Eu uso "foldRight (: \)" em vez de "foldLeft (/ :)" aqui por causa da ordem de "_" s. Para uma versão que não lança StackOverflowException, use "foldLeft".

Walter Chang
fonte
18
Eu discordo fortemente; sua forma preferida parece apenas ruído de linha.
Matt R
14
Eu vou? Eu aprendi Haskell pela primeira vez em 1999, e tenho me aventurado no Scala por alguns anos. Acho que as dobras são ótimas, mas se a aplicação de uma dobra em qualquer situação exigir a escrita de uma string enigmática de símbolos de pontuação, eu consideraria uma abordagem diferente.
Matt R
11
@Matt R: Eu concordo. Existe exagero, e este é um deles.
ryeguy,
8
@WalterChang Gosto da aparência de todos aqueles emoticons. Espere um minuto, isso é código? : P
David J.
4
É justo chamar ((0 to 3) :\ List[Int]())(_ :: _)emoticode?
David J.
2

Usando List.tabulate, assim,

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)
olmo
fonte
2

Nota: esta resposta foi escrita para uma versão antiga do Scala.

As classes de coleção do Scala serão reprojetadas a partir do Scala 2.8, portanto, esteja preparado para mudar a maneira como você cria listas muito em breve.

Qual é a maneira compatível com versões futuras de criar uma lista? Não tenho ideia, pois ainda não li a documentação do 2.8.

Um documento PDF que descreve as alterações propostas das classes de coleção

André Laszlo
fonte
2
A maioria das mudanças está na maneira como as coisas são implementadas internamente e em coisas avançadas como projeções. O modo como você cria uma lista não é afetado.
Marcus Downing
Ok, é bom saber. Você também será afetado se usar qualquer classe no pacote collection.jcl.
André Laszlo
1

Como um novo desenvolvedor de scala, escrevi um pequeno teste para verificar o tempo de criação da lista com os métodos sugeridos acima. Parece que (for (p <- (0 a x)) produz p) toList a abordagem mais rápida.

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
fonte
0

apenas um exemplo que usa 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
fonte
0

Para criar uma lista de strings, use o seguinte:

val l = List("is", "am", "are", "if")
KayV
fonte
1
Ao responder uma pergunta tão antiga (10 anos), e com tantas respostas existentes (9), é uma boa prática explicar porque sua resposta é diferente de todas as outras. Do jeito que está, parece que você não entendeu a pergunta.
jwvh