Предпочтительный способ создания списка 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- это изменяемый список, который имеет добавление с постоянным временем и преобразование с постоянным временем в List.

List является неизменным и имеет добавление с постоянным временем и добавление с линейным временем.

То, как вы создаете свой список, зависит от алгоритма, для которого вы будете использовать список, и порядка, в котором вы получаете элементы для его создания.

Например, если вы получаете элементы в порядке, обратном порядку их использования, тогда вы можете просто использовать Listи do prepends. Делаете ли вы это с помощью хвостовой рекурсивной функции 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. Во-вторых, в prepend нет никакой копии: создается элемент, указывающий на существующий список, и все.
Дэниел С. Собрал,


22

Умм ... мне это кажется слишком сложным. Могу я предложить

def listTestD = (0 to 3).toList

или

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

Спасибо за ответ, но вопрос в том, что вы делаете в нетривиальном случае. Я добавил комментарий в код, объясняя, что все они эквивалентны от 0 до 3 toList.
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)

В большинстве случаев вам, вероятно, даже не нужно преобразовывать в список :)

В индексированной последовательности будет все, что вам нужно:

То есть теперь вы можете работать с этим IndexedSeq:

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

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

2

Я всегда предпочитаю List и использую «свернуть / уменьшить» перед «для понимания». Однако «для понимания» предпочтительнее, если требуются вложенные «складки». Рекурсия - это последнее средство, если я не могу выполнить задачу с помощью «свернуть / уменьшить / для».

так что для вашего примера я сделаю:

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

прежде чем я это сделаю:

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

Примечание. Я использую «foldRight (: \)» вместо «foldLeft (/ :)» из-за порядка «_». Для версии, которая не генерирует исключение StackOverflowException, используйте вместо этого «foldLeft».


18
Я категорически не согласен; ваша предпочтительная форма выглядит как линейный шум.
Matt R,

14
Я буду? Я впервые изучил Haskell в 1999 году и уже пару лет баловался Scala. Я думаю, что складки - это здорово, но если для применения складок в любой ситуации требуется написание загадочной строки знаков препинания, я бы рассмотрел другой подход.
Matt R,

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)) yield p) toList самый быстрый подход.

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.