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

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.


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


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

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

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".


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)

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


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

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)

0

Para criar uma lista de strings, use o seguinte:

val l = List("is", "am", "are", "if")

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.