Если это не очевидно, значения и типы v1
и v2
различаются: v1
имеет тип List[(Int, Int)]
со значением List((1, 5), (2, 6), (3, 7))
; v2
имеет тип scala.runtime.Tuple2Zipped[Int, List[Int], Int, List[Int]]
и имеет значение (List(1, 2, 3), List(5, 6, 7)).zipped
.
Другими словами, значение v1
было вычислено строго (операция zip
уже выполнена), тогда как v2
было вычислено лениво (или не строго ) - фактически операция zip была сохранена, но еще не выполнена.
Если все, что вы хотите сделать, это вычислить эти два значения (но не использовать их на самом деле), тогда я действительно ожидал бы, что v2
будет вычисляться быстрее, потому что на самом деле он не выполняет большую работу. ; -)
Кроме того, это будет зависеть от того, как вы впоследствии собираетесь использовать эти значения. Tuple2Zipped
будет работать лучше, если вам не нужно обрабатывать каждый кортеж в результирующем списке, так как он не будет тратить время на сжатие элементов списка, которые вам не нужны. Возможно, он может иметь преимущество, если вам нужно применить какую-либо операцию к каждому кортежу, но ему не нужен доступ к ним после обработки, что позволяет выполнить один проход по списку.
Метод List.zip
, вероятно, будет лучшим выбором, если вам нужно выполнить несколько операций над элементами списка, повторяя его несколько раз.
Оба подхода будут работать во всех случаях. (В общем случае я бы предпочел List.zip
хотя бы потому, что Tuple2Zipped
менее известен, и его использование намекает на специальное требование.)
Если производительность действительно вызывает озабоченность, то я рекомендую сравнить два подхода с вашим кодом, используя такой инструмент, как ScalaMeter и точно их различая. Я бы также порекомендовал тестирование использования памяти, а также времени обработки, поскольку у двух подходов разные требования к памяти.
ОБНОВЛЕНИЕ : Ссылка на дополнительный вопрос в комментариях ниже: "Есть ли разница между val m:Map[Int, Int] = (l1 zip l2)(breakOut)
и (l1, l2).zipped.toMap
?
Я повторю это следующим образом:
import scala.collection.breakOut
val l1 = List(1, 2, 3)
val l2 = List(5, 6, 7)
// m1's type has to be explicit, otherwise it is inferred to be
// scala.collection.immutable.IndexedSeq[(Int, Int)].
val m1: Map[Int, Int] = (l1 zip l2)(breakOut)
val m2 = (l1, l2).zipped.toMap
Нет такой вещи, как lazy Map
, так как все элементы на карте должны быть доступны для внутренней структуры карты, что позволяет эффективно извлекать значения при выполнении поиск ключа.
Следовательно, различие между строго оцененным (l1 zip l2)
и лениво оцененным (l1, l2).zipped
исчезает в процессе преобразования в Map
.
Так, что более эффективно? В этом конкретном примере я ожидаю, что оба подхода работают очень схожим образом.
При вычислении m1
операция zip
повторяется через l1
и l2
, проверяя пару элементов головы одновременно. Построитель breakOut
(см. Также ссылку в комментарии ниже) и объявленный тип результата Map[Int, Int]
заставляют операцию zip
построить Map
в качестве своего результата (без breakOut
). zip
приведет к List[(Int, Int)]
).
Подводя итог этого подхода, полученная карта создается с помощью одного одновременного прохода через l1
и l2
.
(Использование breakOut
действительно имеет значение. Если мы сгенерировали карту как (l1 zip l2).toMap
, то мы выполним одну итерацию через l1
и l2
, чтобы создать List[(Int, Int)]
, затем выполните итерацию по этому списку создать результирующее Map
, это явно менее эффективно.
В новом API коллекций Scala 13 breakOut
был удален. Но есть новые альтернативы, которые работают лучше с точки зрения типа. См. этот документ для получения более подробной информации.)
Теперь давайте рассмотрим m2
. В этом случае, как указывалось ранее, (l1, l2).zipped
приводит к отложенному списку кортежей. Тем не менее, на данный момент, ни один итераций еще не было выполнено ни для одного из входных списков. Когда выполняется операция toMap
, каждый кортеж в отложенном списке оценивается при первом обращении и добавляется создаваемая карта.
Подводя итог этому подходу, мы снова получаем, что полученная карта создается с помощью одного одновременного прохода через l1
и l2
.
Итак, в данном конкретном сценарии использования между этими двумя подходами будет очень мало различий.Могут все еще быть незначительные детали реализации, которые влияют на результат, поэтому, если у вас есть огромное количество данных в l1
и l2
, вы все равно можете сравнить их, чтобы найти лучшее решение.Однако я был бы склонен просто выбрать операцию zip
(с breakOut
) и оставить все как есть.