Preferowany sposób tworzenia listy Scala

117

Istnieje kilka sposobów tworzenia niezmiennej listy w Scali (zobacz poniższy przykładowy kod). Możesz użyć mutowalnego ListBuffera, stworzyć varlistę i zmodyfikować ją, użyć metody rekurencyjnej tail i prawdopodobnie innych, o których nie wiem.

Instynktownie używam ListBuffer, ale nie mam ku temu dobrego powodu. Czy istnieje preferowana lub idiomatyczna metoda tworzenia listy, czy też są sytuacje, w których jedna metoda jest najlepsza?

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
źródło

Odpowiedzi:

108

ListBufferto zmienna lista, która ma stałe dołączanie i stałą konwersję do pliku List.

List jest niezmienna i ma poprzedzanie w czasie stałym i dołączanie w czasie liniowym.

Sposób konstruowania listy zależy od algorytmu, dla którego lista zostanie użyta, oraz kolejności, w jakiej otrzymujesz elementy do jej utworzenia.

Na przykład, jeśli otrzymasz elementy w odwrotnej kolejności niż wtedy, gdy mają być użyte, możesz po prostu użyć a Listi zrobić przedrostki. To, czy zrobisz to z funkcją rekurencyjną ogonową foldLeft, czy coś innego, nie jest tak naprawdę istotne.

Jeśli otrzymujesz elementy w tej samej kolejności, w jakiej ich używasz, ListBuffernajprawdopodobniej preferowanym wyborem jest, jeśli wydajność ma kluczowe znaczenie.

Ale jeśli nie znajdujesz się na ścieżce krytycznej, a dane wejściowe są wystarczająco niskie, zawsze możesz reversewyświetlić listę później, lub po prostu foldRight, lub reversewejście, które jest liniowe.

Czego NIE robić to użyć Listi dołączyć do niego. Daje to znacznie gorszą wydajność niż zwykłe dodawanie i cofanie na końcu.

Daniel C. Sobral
źródło
What you DON'T do is use a List and append to itCzy to dlatego, że tworzona jest nowa lista ? Zważywszy, że użycie operacji dołączania na początku nie spowoduje utworzenia nowej listy?
Kevin Meredith
2
@KevinMeredith Yes. Dołącz to O (n), poprzedzone to O (1).
Daniel C. Sobral
@pgoggijr To nieprawda. Po pierwsze, nigdzie nie ma „zmiany”, ponieważ jest niezmienna. Przejście jest wymagane, ponieważ wszystkie elementy muszą zostać skopiowane, tak więc kopia ostatniego elementu może zostać utworzona, wskazując na nowy element zamiast Nil. Po drugie, nie ma żadnej kopii na początku: tworzony jest element wskazujący na istniejącą listę i to wszystko.
Daniel C. Sobral
65

A w prostych przypadkach:

val list = List(1,2,3) 

:)

Tomasz Nurkiewicz
źródło
10
Nie zapomnij o operatorze wad! 1 :: 2 :: 3 :: Nil
André Laszlo
22

Uhmm… to wydaje mi się zbyt skomplikowane. Mogę zaproponować

def listTestD = (0 to 3).toList

lub

def listTestE = for (i <- (0 to 3).toList) yield i
Aleksandra Azarowa
źródło
Dzięki za odpowiedź, ale pytanie, co robisz w nietrywialnym przypadku. W kodzie umieściłem komentarz wyjaśniający, że wszystkie są równoważne 0 do 3 toList.
agilefall
Ups, przepraszam! Szczerze mówiąc, nigdy nie używam ListBuffer.
Alexander Azarov
5

Chcesz skupić się na niezmienności w Scali, eliminując wszelkie zmienne. Czytelność jest nadal ważna dla twojego bliźniego, więc:

Próbować:

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)

Prawdopodobnie w większości przypadków nie musisz nawet konwertować na listę :)

Zindeksowana sekwencja będzie zawierała wszystko, czego potrzebujesz:

Oznacza to, że możesz teraz pracować nad tym IndexedSeq:

scala> list.foldLeft(0)(_+_)
res0: Int = 55
JasonG
źródło
NB Vectorjest teraz również domyślną Seqimplementacją.
Connor Doyle
2

Zawsze wolę Listę i używam „zwiń / zmniejsz” przed „dla zrozumienia”. Jednak „do zrozumienia” jest preferowane, jeśli wymagane są zagnieżdżone „fałdy”. Rekursja jest ostatnią deską ratunku, jeśli nie mogę wykonać zadania za pomocą "fold / redukuj / dla".

więc dla twojego przykładu zrobię:

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

zanim to zrobię:

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

Uwaga: używam tutaj „foldRight (: \)” zamiast „foldLeft (/ :)” ze względu na kolejność „_”. W przypadku wersji, która nie zgłasza wyjątku StackOverflowException, użyj zamiast tego „foldLeft”.

Walter Chang
źródło
18
Zdecydowanie się nie zgadzam; Twoja preferowana forma wygląda jak szum linii.
Matt R
14
Będę? Po raz pierwszy nauczyłem się Haskella w 1999 r., A Scala paruję od kilku lat. Myślę, że fałdy są świetne, ale jeśli zastosowanie zagięcia w danej sytuacji wymaga napisania tajemniczego ciągu znaków interpunkcyjnych, rozważę inne podejście.
Matt R
11
@Matt R: Zgadzam się. Istnieje coś takiego jak przesada, a to jest jedna z nich.
ryeguy
8
@WalterChang Podoba mi się wygląd tych wszystkich emotikonów. Chwileczkę, czy to kod? : P
David J.,
4
Czy można nazywać ((0 to 3) :\ List[Int]())(_ :: _)emoticode?
David J.,
2

Używając w List.tabulateten sposób

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)
wiąz
źródło
2

Uwaga: ta odpowiedź jest napisana dla starej wersji Scali.

Klasy kolekcji Scala zostaną przeprojektowane od wersji Scala 2.8, więc przygotuj się na zmianę sposobu tworzenia list już wkrótce.

Jaki jest zgodny z przyszłością sposób tworzenia listy? Nie mam pojęcia, ponieważ nie przeczytałem jeszcze dokumentów 2.8.

Dokument PDF opisujący proponowane zmiany klas kolekcji

André Laszlo
źródło
2
Większość zmian dotyczy sposobu, w jaki rzeczy są wdrażane wewnętrznie, oraz zaawansowanych rzeczy, takich jak prognozy. Nie ma to wpływu na sposób tworzenia listy.
Marcus Downing
Dobrze, dobrze wiedzieć. Będzie to również miało wpływ, jeśli użyjesz dowolnej klasy z pakietu collection.jcl.
André Laszlo
1

Jako nowy programista scala napisałem mały test, aby sprawdzić czas tworzenia listy za pomocą sugerowanych powyżej metod. Wygląda na to, że (dla (p <- (0 do x)) wydajność p) toList jest najszybszym podejściem.

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
źródło
0

tylko przykład wykorzystujący 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
źródło
0

Aby utworzyć listę ciągów, użyj:

val l = List("is", "am", "are", "if")
KayV
źródło
1
Odpowiadając na tak stare (10-letnie) pytanie i mając tak wiele istniejących odpowiedzi (9), dobrą praktyką jest wyjaśnienie, dlaczego Twoja odpowiedź różni się od wszystkich pozostałych. Wygląda na to, że nie zrozumiałeś pytania.
jwvh