Конкатенация списка Scala, ::: vs ++

266

Есть ли разница между ::: и ++ для объединения списков в Scala?

scala> List(1,2,3) ++ List(4,5)
res0: List[Int] = List(1, 2, 3, 4, 5)

scala> List(1,2,3) ::: List(4,5)
res1: List[Int] = List(1, 2, 3, 4, 5)

scala> res0 == res1
res2: Boolean = true

Из документации он выглядит как ++ более общий, тогда как ::: является List -специфичным. Предоставлено ли последнее, потому что оно используется на других функциональных языках?

  • 4
    Также ::: является префиксным оператором, как и все методы, начинающиеся с :
  • 3
    Ответы в значительной степени очерчивают способ эволюции scala вокруг списков и единообразия операторов в Scala (или его отсутствия). Немного прискорбно, что у чего-то такого простого есть такой длинный хвост мелочей, чтобы запутать и потратить время любого ученика Scala. Я хотел бы, чтобы это было выровнено в 2.12.
Теги:
list
concatenation

4 ответа

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

Наследство. Список изначально был определен как функционально-языки:

1 :: 2 :: Nil // a list
list1 ::: list2  // concatenation of two lists

list match {
  case head :: tail => "non-empty"
  case Nil          => "empty"
}

Разумеется, Scala разработал другие коллекции специальным образом. Когда вышел 2.8, коллекции были переработаны для максимального повторного использования кода и согласованного API, так что вы можете использовать ++ для объединения любых двух коллекций - и даже итераторов. Однако список должен содержать своих исходных операторов, кроме одного или двух, которые устарели.

  • 14
    Так лучше ли сейчас избегать ::: в пользу ++ ? Также используйте +: вместо :: ?
  • 32
    :: полезно из-за сопоставления с образцом (см. второй пример Дэниела). Вы не можете сделать это с +:
Показать ещё 10 комментариев
61

::: работает только со списками, а ++ может использоваться с любым проходящим. В текущей реализации (2.9.0) ++ возвращается на :::, если аргумент также является List.

  • 3
    Так что очень легко использовать и ::: и ++, работая со списком. Это потенциально может привести к путанице в коде / стиле.
48

Всегда используйте :::. Существует две причины: эффективность и безопасность типов.

Эффективность

x ::: y ::: z быстрее, чем x ++ y ++ z, потому что ::: является правильным ассоциативным. x ::: y ::: z анализируется как x ::: (y ::: z), который алгоритмически быстрее, чем (x ::: y) ::: z (последний требует, чтобы O (| x |) выполнялось больше шагов).

Тип безопасности

С помощью ::: вы можете объединить только два List s. С помощью ++ вы можете добавить любую коллекцию в List, что ужасно:

scala> List(1, 2, 3) ++ "ab"
res0: List[AnyVal] = List(1, 2, 3, a, b)

++ также легко смешивается с +:

scala> List(1, 2, 3) + "ab"
res1: String = List(1, 2, 3)ab
  • 7
    При объединении только 2 списков нет никакой разницы, но в случае 3 или более у вас есть хорошая точка зрения, и я подтвердил это быстрым тестом. Однако, если вы беспокоитесь об эффективности, x ::: y ::: z следует заменить на List(x, y, z).flatten . pastebin.com/gkx7Hpad
  • 0
    это не добавляет безопасности типов ... List(1) ::: List("2") становится List[Any] = List(1, 2) . Если безопасность типов является проблемой (как и должно быть), я бы рекомендовал избегать как ++ и ::: в пользу скаляза |+| или похожие. Если эффективность является главной задачей, то непременно используйте :::
Показать ещё 6 комментариев
16

Другая точка заключается в том, что первое предложение анализируется как:

scala> List(1,2,3).++(List(4,5))
res0: List[Int] = List(1, 2, 3, 4, 5)

В то время как второй пример анализируется как:

scala> List(4,5).:::(List(1,2,3))
res1: List[Int] = List(1, 2, 3, 4, 5)

Итак, если вы используете макросы, вам следует позаботиться.

Кроме того, ++ для двух списков вызывает :::, но с большим количеством служебных данных, потому что запрашивает неявное значение, чтобы иметь построитель из списка в список. Но микробенчмарки не показали ничего полезного в этом смысле, я думаю, что компилятор оптимизирует такие вызовы.

Micro-Benchmarks после разогрева.

scala>def time(a: => Unit): Long = { val t = System.currentTimeMillis; a; System.currentTimeMillis - t}
scala>def average(a: () => Long) = (for(i<-1 to 100) yield a()).sum/100

scala>average (() => time { (List[Int]() /: (1 to 1000)) { case (l, e) => l ++ List(e) } })
res1: Long = 46
scala>average (() => time { (List[Int]() /: (1 to 1000)) { case (l, e) => l ::: List(e ) } })
res2: Long = 46

Как сказал Daniel C. Sobrai, вы можете добавить содержимое любой коллекции в список с помощью ++, тогда как с помощью ::: вы можете объединять списки.

  • 20
    Пожалуйста, опубликуйте свои не слишком упрощенные микробенчмарки, и я буду голосовать за них.

Ещё вопросы

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