Предпочтительный способ создания списка Scala

109

Существует несколько способов создания неизменяемого списка в Scala (см. приведенный ниже примерный код). Вы можете использовать изменяемый ListBuffer, создать список var и изменить его, использовать метод tail recursive, и, возможно, другие, которые я не знать.

Инстинктивно я использую 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)
}
Теги:

9 ответов

107
Лучший ответ

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

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

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

Например, если вы получите элементы в обратном порядке, когда они будут использоваться, вы можете просто использовать List и делать preends. Будете ли вы делать это с помощью хвостовой рекурсивной функции, foldLeft или что-то еще не актуально.

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

Но, если вы не находитесь на критическом пути, а вход достаточно низок, вы всегда можете reverse список позже или просто foldRight или reverse входной сигнал, который является линейным.

Что вы НЕ выполняете, используйте List и добавляете к нему. Это даст вам гораздо худшую производительность, чем просто добавление и реверсирование в конце.

  • 0
    What you DON'T do is use a List and append to it Это потому, что создается новый список ? Принимая во внимание, что использование операции prepend не создаст новый список?
  • 2
    @KevinMeredith Да. Добавить это O (n), prepend это O (1).
Показать ещё 1 комментарий
64

И для простых случаев:

val list = List(1,2,3) 

:)

  • 10
    Не забудьте оператор минусов! 1 :: 2 :: 3 :: Ноль
  • 3
    Или я должен сказать «оператор»?
21

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

def listTestD = (0 to 3).toList

или

def listTestE = for (i <- (0 to 3).toList) yield i
  • 0
    Спасибо за ответ, но вопрос в том, что вы делаете в нетривиальном случае. Я добавил комментарий в код, пояснив, что все они эквивалентны списку от 0 до 3.
  • 0
    Ой, извините тогда! Честно говоря, я никогда не использую ListBuffer.
5

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

Try:

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
  • 0
    NB Vector теперь также является реализацией Seq по умолчанию.
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
    Большинство изменений заключаются в том, как вещи реализованы внутри, и в продвинутых вещах, таких как проекции. Как вы создаете список, это не влияет.
  • 0
    Хорошо, это приятно знать. Это также повлияет на использование любого класса в пакете collection.jcl.
2

Я всегда предпочитаю List и я использую "fold/reduce" перед "для понимания". Однако "для понимания" предпочтительнее, если требуются вложенные "складки". Рекурсия - последнее средство, если я не могу выполнить задачу, используя "fold/reduce/for".

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

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

до этого:

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

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

  • 18
    Я категорически не согласен; Ваша предпочтительная форма просто выглядит как шум линии.
  • 1
    Что ж, все, что я могу сказать, если вы достаточно долго будете придерживаться Scala и функционального программирования, вы научитесь любить его.
Показать ещё 7 комментариев
1

В качестве нового разработчика scala я написал небольшой тест, чтобы проверить время создания списка с предложенными выше методами. Он выглядит как (для (p < - (от 0 до x)) дает 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)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню