2015-02-05 4 views
6

Как вы перебираете два массива одинакового размера, получая один и тот же индекс на каждой итерации Scala Way ™?Scala - Итерация над двумя массивами

 for ((aListItem, bListItem) <- (aList, bList)) { 
     // do something with items 
     } 

Путь Java применяется к Scala:

 for(i <- 0 until aList.length) { 
      aList(i) 
      bList(i) 
     } 

Пусть оба списка имеют одинаковый размер.

+0

Что случилось с тем, что вы начинаете с? Если проблема в том, что она не компилируется, просто добавьте определения в кортеж: 'for ((aListItem: Int, bListItem: Int) ...' –

+1

Связанный: http://stackoverflow.com/q/17199534/406435 – senia

+0

Scala Way (TM) не использует массивы (которые изменяются), но списки, и они неэффективны для случайного доступа (то есть по индексу). В любом случае, в зависимости от того, что вы используете, правильное решение похоронено в ответе Рекса Керра: '(aList, bList) .zipped.foreach {(a, b) => ???}'. И да, это эффективно, потому что для каждого элемента не нужно создавать кортежи (в отличие от 'zip'). –

ответ

11

Т.Л., д-р : Существуют компромиссы между 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) 
+0

Отличная работа, спасибо. – BAR

2

Что-то вроде:

for ((aListItem, bListItem) <- (aList zip bList)) { 
    // do something with items 
} 

Или с map как:

(aList zip bList).map{ case (alistItem, blistItem) => // do something } 

Обновлено:

Для итерацию без создания промежуточных, вы можете попробовать:

for (i <- 0 until xs.length) ... //xs(i) & ys(i) to access element 

или просто

for (i <- xs.indices) ... 
+0

Я думаю, что при добавленной zip-процедуре будет ухудшаться производительность по сравнению с Java-способом. – BAR

+0

Должен увидеть источник, чтобы посмотреть, будет ли zip обертываться или конструирует новый массив. – BAR

+0

@BAR Это может помочь с точки зрения учета производительности, но опять же это зависит от варианта использования и оптимизации компилятора: http://stackoverflow.com/questions/2794823/is-scala-functional-programming-slower-than- традиционное кодирование –

1
for { 
    i <- 0 until Math.min(list1.size, list2.size) 
} yield list1(i) + list2(i) 

Или что-то подобное тому, которое проверяет границы и т.д.

+0

Он будет проверять только границы списка1. Он все равно может взорваться, если списки не синхронизируются по размеру. –

+0

Это будет _всего_ выйти за пределы, потому что вы использовали 'to', что включено. –

+0

Я определился в ответе, что необходимо проверить флажки, но я обновил его для вас. –

1

Я хотел бы сделать что-то вроде этого:

aList.indices foreach { i => 
    val (aListItem, bListItem) = (aList(i), bList(i)) 
    // do something with items 
} 
+0

Мне это очень нравится. Краткая с синтаксисом и функциональностью Scala. – BAR

+1

Это вызовет исключение, если 'bList' короче, чем' aList'. –

+0

@RexKerr Оба списка будут одинакового размера. Обновление сообщения для уточнения. – BAR

Смежные вопросы