Т.Л., д-р : Существуют компромиссы между sp и удобство; вам нужно знать, что ваш прецедент подходит для надлежащего выбора.
Если вы знаете, оба массива имеют одинаковую длину, и вам не нужно беспокоиться, как быстро это самый простой и наиболее каноническая является использование zip
внутри для-понимания:
for ((a,b) <- aList zip bList) { ??? }
Метод zip
создает новый одиночный массив. Чтобы избежать этого накладных расходов, вы можете использовать zipped
на кортеже, который представит элементы в парах к методам, как foreach
и map
:
(aList, bList).zipped.foreach{ (a,b) => ??? }
еще более быстрым индексировать в массивы, особенно если массивы содержат примитивы как Int
, так как общий код, указанный выше, должен помечать их.Существует удобный способ indices
, который вы можете использовать:
for (i <- aList.indices) { ??? }
Наконец, если вам нужно идти так быстро, как вы возможно, вы можете упасть назад к руководству в то время как петли или рекурсии, например, так:
// While loop
var i = 0
while (i < aList.length) {
???
i += 1
}
// Recursion
def loop(i: Int) {
if (i < aList.length) {
???
loop(i+1)
}
}
loop(0)
Если вы вычисляя какую-то ценность, вместо того, это быть побочным эффектом, иногда быстрее с помощью рекурсии, если передать его:
// Recursion with explicit result
def loop(i: Int, acc: Int = 0): Int =
if (i < aList.length) {
val nextAcc = ???
loop(i+1, nextAcc)
}
else acc
Поскольку вы можете оставить определение метода я В любом месте вы можете использовать рекурсию без ограничений. Вы можете добавить аннотацию @annotation.tailrec
, чтобы убедиться, что ее можно скомпилировать до быстрого цикла с прыжками вместо фактической рекурсии, которая ест пространство стека.
Принимая во внимание все эти различные подходы для вычисления скалярного произведения на длину 1024 векторов, мы можем сравнить их с эталонной реализации в Java:
public class DotProd {
public static int dot(int[] a, int[] b) {
int s = 0;
for (int i = 0; i < a.length; i++) s += a[i]*b[i];
return s;
}
}
плюс эквивалентную версию, где мы берем скалярное произведение длин строк (таким образом, мы можем оценить объекты vs. примитивы)
normalized time
-----------------
primitive object method
--------- ------ ---------------------------------
100% 100% Java indexed for loop (reference)
100% 100% Scala while loop
100% 100% Scala recursion (either way)
185% 135% Scala for comprehension on indices
2100% 130% Scala zipped
3700% 800% Scala zip
Это особенно плохо, конечно, с примитивами! (Вы получаете аналогичные огромные скачки во времени, взятые, если вы попытаетесь использовать ArrayList
s из Integer
вместо Array
из int
на Java.) Обратите внимание, что zipped
- довольно разумный выбор, если у вас есть объекты.
Остерегайтесь преждевременной оптимизации, хотя! Есть преимущества в ясности и безопасности для функциональных форм, таких как zip
. Если вы всегда пишете в цикле, потому что вы думаете, что «каждый бит помогает», вы, вероятно, ошибаетесь, потому что требуется больше времени для написания и отладки, и вы можете использовать это время для оптимизации более важной части вашей программы.
Но, если ваши массивы одинаковой длины, это опасно. Вы уверены? Сколько усилий вы предпримете, чтобы быть уверенным? Может быть, вы не должны делать это предположение?
Если вам не нужно, чтобы это было быстро, просто исправьте, тогда вам нужно будет выбрать, что делать, если два массива не имеют одинаковой длины.
Если вы хотите сделать что-то со всеми элементами до длины короче, то zip
еще, что вы используете:
// The second is just shorthand for the first
(aList zip bList).foreach{ case (a,b) => ??? }
for ((a,b) <- (aList zip bList)) { ??? }
// This avoids an intermediate array
(aList, bList).zipped.foreach{ (a,b) => ??? }
Если вместо этого вы хотите площадку короче один со значением по умолчанию вы бы
aList.zipAll(bList, aDefault, bDefault).foreach{ case (a,b) => ??? }
for ((a,b) <- aList.zipAll(bList, aDefault, bDefault)) { ??? }
в любом из этих случаев вы можете использовать yield
с for
или map
вместо foreach
для создания коллекции.
Если вам нужен индекс для расчета или он действительно представляет собой массив, и вам действительно нужно быстро, вам придется делать расчет вручную.Padding недостающих элементов неудобно (я оставляю это в качестве упражнения для читателя), но основная формы будет:
for (i <- 0 until math.min(aList.length, bList.length)) { ??? }
где вы затем использовать i
индексировать в aList
и bList
.
Если вы действительно нужна максимальная скорость вы бы снова использовать (хвост) рекурсию или в то время как петли:
val n = math.min(aList.length, bList.length)
var i = 0
while (i < n) {
???
i += 1
}
def loop(i: Int) {
if (i < aList.length && i < bList.length) {
???
loop(i+1)
}
}
loop(0)
Что случилось с тем, что вы начинаете с? Если проблема в том, что она не компилируется, просто добавьте определения в кортеж: 'for ((aListItem: Int, bListItem: Int) ...' –
Связанный: http://stackoverflow.com/q/17199534/406435 – senia
Scala Way (TM) не использует массивы (которые изменяются), но списки, и они неэффективны для случайного доступа (то есть по индексу). В любом случае, в зависимости от того, что вы используете, правильное решение похоронено в ответе Рекса Керра: '(aList, bList) .zipped.foreach {(a, b) => ???}'. И да, это эффективно, потому что для каждого элемента не нужно создавать кортежи (в отличие от 'zip'). –