Кращий спосіб створення списку Scala


117

Існує кілька способів побудови незмінного списку в Scala (див. Надуманий приклад коду нижче). Ви можете використовувати змінний ListBuffer, створити varсписок та змінити його, використовувати хвостовий рекурсивний метод та, ймовірно, інші, про які я не знаю.

Інстинктивно я використовую ListBuffer, але у мене немає вагомих причин для цього. Чи є кращий або ідіоматичний метод створення списку, чи існують ситуації, які найкраще підходять для одного методу над іншим?

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

Відповіді:


108

ListBuffer- це список, що змінюється, який має додавання постійного часу та конверсію постійного часу в a List.

List є незмінним і має додаток до постійного часу та додавання лінійного часу.

Спосіб побудови списку залежить від алгоритму, для якого ви будете використовувати список, і порядку, в якому ви отримуєте елементи для його створення.

Наприклад, якщо ви отримуєте елементи в протилежному порядку, коли вони будуть використані, ви можете просто використовувати a Listі робити препенди. Незалежно від того, чи будете ви це робити з хвостово-рекурсивною функцією foldLeft, чи щось інше не дуже важливо.

Якщо ви отримуєте елементи в тому ж порядку, коли ви їх використовуєте, то ListBuffer, швидше за все, це кращий вибір, якщо продуктивність є критичною.

Але якщо ви не знаходитесь на критичному шляху, а вхід недостатньо низький, ви завжди можете reverseперелічити пізніше, або просто foldRight, або reverseвхід, який є лінійним часом.

Те, що ви НЕ РОБИТИ , - це використовувати Listта додавати до нього. Це дасть вам набагато гірші показники, ніж просто передбачення та перевернення в кінці.


What you DON'T do is use a List and append to itЦе тому, що створюється новий список ? В той час, як використання операції попередньої подачі не створить новий список?
Кевін Мередіт

2
@KevinMeredith Так. Додаток - O (n), додаток - O (1).
Даніель К. Собрал

@pgoggijr Це неправда. По-перше, ніде "зміни" ніде немає, тому що це незмінне. Обхід необхідний, тому що всі елементи повинні бути скопійовані, так що копія останнього елемента може бути зроблена, що вказує на новий елемент замість Nil. По-друге, жодної копії на препенді немає: створюється елемент, що вказує на існуючий список, і все.
Даніель К. Собрал


22

Гмм .. це здаються мені занадто складними. Можна запропонувати

def listTestD = (0 to 3).toList

або

def listTestE = for (i <- (0 to 3).toList) yield i

Дякую за відповідь, але питання в тому, що ви робите в нетривіальному випадку. Я додаю коментар до коду, пояснюючи, що всі вони еквівалентні 0 до 3 списку.
agilefall

На жаль, вибачте тоді! Чесно кажучи, я ніколи не використовую ListBuffer.
Олександр Азаров

5

Ви хочете зосередити увагу на незмінності Scala, усуваючи будь-які вари. Читання все ще важливо для вашого ближнього чоловіка, тому:

Спробуйте:

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)

Мабуть, вам навіть не потрібно перетворюватись на список у більшості випадків :)

Індексований seq матиме все необхідне:

Тобто тепер ви можете працювати над цим IndexedSeq:

scala> list.foldLeft(0)(_+_)
res0: Int = 55

NB Vectorзараз також є Seqреалізацією за замовчуванням .
Коннор Дойл

2

Я завжди віддаю перевагу списку, і для розуміння використовую "скласти / зменшити" перед ". Однак "для розуміння" бажано, якщо потрібні вкладені "складки". Рекурсія є крайнім заходом, якщо я не можу виконати завдання, використовуючи «скласти / зменшити / для».

тому для вашого прикладу я зроблю:

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

перед тим, як зробити:

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

Примітка: я використовую тут "foldRight (: \)" замість "foldLeft (/ :)" через порядок "_" s. Для версії, яка не кидає StackOverflowException, використовуйте натомість "foldLeft".


18
Я категорично не згоден; улюблена форма просто виглядає як шум лінії.
Метт Р

14
Буду? Я вперше дізнався Хаскелла в 1999 році і пару років балувався в Скалі. Я думаю, що складки є чудовими, але якщо застосування складки в будь-якій ситуації вимагає написання криптованого рядка розділових символів, я б розглядав інший підхід.
Метт Р

11
@Matt R: Я згоден. Є таке поняття, як перестаратися, і це одна з них.
ryeguy

8
@WalterChang Мені подобається вигляд усіх цих смайликів. Почекайте хвилинку, це код? : P
Девід Дж.

4
Чи справедливо називати ((0 to 3) :\ List[Int]())(_ :: _)емотикод?
Девід Дж.

2

Використовуючи List.tabulate, як це,

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

Примітка. Ця відповідь написана для старої версії Scala.

Класи колекції Scala будуть перероблені, як і в Scala 2.8, тому будьте готові змінити спосіб створення списків дуже скоро.

Який прямий сумісний спосіб створення списку? Поняття не маю, оскільки ще не прочитав 2,8 документа.

Документ у форматі PDF, що описує запропоновані зміни класів колекції


2
Більшість змін пов'язані з тим, як все реалізовується всередині, а також у вдосконалених речах, таких як прогнози. На те, як створити список, це не впливає.
Маркус Даунінг

Гаразд, це добре знати. Ви також постраждаєте від використання будь-якого класу в пакеті collection.jcl.
Андре Ласло

1

Як новий розробник scala, я написав невеликий тест, щоб перевірити час створення списку з запропонованими вище методами. Схоже, що (для (p <- (0 до x)) вихід p) для переліку найшвидшого підходу.

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

лише приклад, який використовує 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

Щоб створити список рядків, використовуйте наступне:

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

1
Коли ви відповідаєте на запитання цієї старої (10 років) та з такою кількістю існуючих відповідей (9), то добре пояснити, чому ваша відповідь відрізняється від усіх інших. Як це здається, ви не зрозуміли питання.
jwvh
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.