Эффективная итерация с индексом в Scala
так как Scala не имеет старого стиля Java for
петли с индексом,
// does not work
val xs = Array("first", "second", "third")
for (i=0; i<xs.length; i++) {
println("String #" + i + " is " + xs(i))
}
как мы можем итерацию эффективно, и без использования var
s?
вы могли бы сделать это
val xs = Array("first", "second", "third")
val indexed = xs zipWithIndex
for (x <- indexed) println("String #" + x._2 + " is " + x._1)
но список проходит дважды-не очень эффективно.
12 ответов:
гораздо хуже, чем при пересечении в два раза, он создает промежуточный массив пар. Вы можете использовать
view
. Когда вы делаетеcollection.view
, вы можете думать о последующих вызовах как о действии лениво, во время итерации. Если вы хотите вернуть правильную полностью реализованную коллекцию, вы звонитеforce
В конце. Вот это было бы бесполезно и дорого. Поэтому измените свой код наfor((x,i) <- xs.view.zipWithIndex) println("String #" + i + " is " + x)
было упомянуто, что Scala тут есть синтаксис
for
петли:for (i <- 0 until xs.length) ...
или просто
for (i <- xs.indices) ...
тем не менее, вы также просили эффективности. Оказывается, что Scala
for
синтаксис-это на самом деле синтаксический сахар для методов более высокого порядка, таких какmap
,foreach
и т. д. Таким образом, в некоторых случаях эти циклы могут быть неэффективными, например как оптимизировать для-понимания и петли в Scala?(благо новость заключается в том, что команда Scala работает над улучшением этого. Вот проблема в трекере ошибок:https://issues.scala-lang.org/browse/SI-4633)
для максимальной эффективности, можно использовать
while
петли или, если вы настаиваете на удалении использованияvar
, хвостовая рекурсия:import scala.annotation.tailrec @tailrec def printArray(i: Int, xs: Array[String]) { if (i < xs.length) { println("String #" + i + " is " + xs(i)) printArray(i+1, xs) } } printArray(0, Array("first", "second", "third"))
отметим, что дополнительно
@tailrec
аннотация полезна для обеспечения того, что метод фактически является хвостовым рекурсивным. Компилятор Scala переводит хвост-рекурсивный вызывает байтовый код, эквивалентный циклам while.
еще один способ:
scala> val xs = Array("first", "second", "third") xs: Array[java.lang.String] = Array(first, second, third) scala> for (i <- xs.indices) | println(i + ": " + xs(i)) 0: first 1: second 2: third
на самом деле, scala имеет старые циклы Java-стиля с индексом:
scala> val xs = Array("first","second","third") xs: Array[java.lang.String] = Array(first, second, third) scala> for (i <- 0 until xs.length) | println("String # " + i + " is "+ xs(i)) String # 0 is first String # 1 is second String # 2 is third
здесь
0 until xs.length
или0.until(xs.length)
- этоRichInt
метод, который возвращаетRange
подходит для лупинга.кроме того, вы можете попробовать петлю с
to
:scala> for (i <- 0 to xs.length-1) | println("String # " + i + " is "+ xs(i)) String # 0 is first String # 1 is second String # 2 is third
Как насчет этого?
val a = Array("One", "Two", "Three") a.foldLeft(0) ((i, x) => {println(i + ": " + x); i + 1;} )
выход:
0: One 1: Two 2: Three
в stdlib нет ничего, что сделает это за вас, не создавая мусор кортежа, но это не слишком сложно написать свой собственный. К сожалению, я никогда не удосуживался выяснить, как сделать правильный CanBuildFrom implicit raindance, чтобы сделать такие вещи общими в типе коллекции, к которой они применяются, но если это возможно, я уверен, что кто-то просветит нас. :)
def foreachWithIndex[A](as: Traversable[A])(f: (Int,A) => Unit) { var i = 0 for (a <- as) { f(i, a) i += 1 } } def mapWithIndex[A,B](in: List[A])(f: (Int,A) => B): List[B] = { def mapWithIndex0(in: List[A], gotSoFar: List[B], i: Int): List[B] = { in match { case Nil => gotSoFar.reverse case one :: more => mapWithIndex0(more, f(i, one) :: gotSoFar, i+1) } } mapWithIndex0(in, Nil, 0) } // Tests.... @Test def testForeachWithIndex() { var out = List[Int]() ScalaUtils.foreachWithIndex(List(1,2,3,4)) { (i, num) => out :+= i * num } assertEquals(List(0,2,6,12),out) } @Test def testMapWithIndex() { val out = ScalaUtils.mapWithIndex(List(4,3,2,1)) { (i, num) => i * num } assertEquals(List(0,3,4,3),out) }
еще несколько способов итерации:
scala> xs.foreach (println) first second third
foreach, и аналогичная, карта, которая будет возвращать что-то (результаты функции, которая является, для println, единицей, поэтому список единиц)
scala> val lens = for (x <- xs) yield (x.length) lens: Array[Int] = Array(5, 6, 5)
работа с элементами, а не Индекс
scala> ("" /: xs) (_ + _) res21: java.lang.String = firstsecondthird
складывание
for(int i=0, j=0; i+j<100; i+=j*2, j+=i+2) {...}
можно сделать с рекурсией:
def ijIter (i: Int = 0, j: Int = 0, carry: Int = 0) : Int = if (i + j >= 100) carry else ijIter (i+2*j, j+i+2, carry / 3 + 2 * i - 4 * j + 10)
carry-part-это просто какой-то пример, чтобы сделать что-то с i и j. это не обязательно должно быть Int.
для более простых вещей, ближе к обычным for-loops:
scala> (1 until 4) res43: scala.collection.immutable.Range with scala.collection.immutable.Range.ByOne = Range(1, 2, 3) scala> (0 to 8 by 2) res44: scala.collection.immutable.Range = Range(0, 2, 4, 6, 8) scala> (26 to 13 by -3) res45: scala.collection.immutable.Range = Range(26, 23, 20, 17, 14)
или без того:
List (1, 3, 2, 5, 9, 7).foreach (print)
в самом деле, называя
zipWithIndex
на коллекции будет проходить его, а также создать новую коллекцию для пар. Чтобы избежать этого, вы можете просто позвонитьzipWithIndex
на итераторе для коллекции. Это просто вернет новый итератор, который отслеживает индекс во время итерации, поэтому без создания дополнительной коллекции или дополнительного обхода.это как
scala.collection.Iterator.zipWithIndex
В настоящее время реализуется в 2.10.3:def zipWithIndex: Iterator[(A, Int)] = new AbstractIterator[(A, Int)] { var idx = 0 def hasNext = self.hasNext def next = { val ret = (self.next, idx) idx += 1 ret } }
это должно быть даже немного более эффективным, чем создание представления коллекции.
простой и эффективный способ, вдохновленный реализацией
transform
in SeqLike.скалаvar i = 0 xs foreach { el => println("String #" + i + " is " + xs(i)) i += 1 }
цикл в scala довольно прост. Создание любого массива на ваш выбор, например.
val myArray = new Array[String](3) myArray(0)="0"; myArray(1)="1"; myArray(2)="2";
виды петель
for(data <- myArray)println(data) for (i <- 0 until myArray.size) println(i + ": " + myArray(i))
У меня есть следующие подходы
object HelloV2 { def main(args: Array[String]) { //Efficient iteration with index in Scala //Approach #1 var msg = ""; for (i <- args.indices) { msg+=(args(i)); } var msg1=""; //Approach #2 for (i <- 0 until args.length) { msg1 += (args(i)); } //Approach #3 var msg3="" args.foreach{ arg => msg3 += (arg) } println("msg= " + msg); println("msg1= " + msg1); println("msg3= " + msg3); } }
предлагаемые решения страдают от того, что они либо явно перебирают коллекцию, либо заполняют коллекцию в функцию. Более естественно придерживаться обычных идиом Scala и помещать индекс внутри обычных методов map - или foreach. Это можно сделать с помощью запоминания. Полученный код может выглядеть как
myIterable map (doIndexed(someFunction))
вот способ достижения этой цели. Рассмотрим следующую программу:
object TraversableUtil { class IndexMemoizingFunction[A, B](f: (Int, A) => B) extends Function1[A, B] { private var index = 0 override def apply(a: A): B = { val ret = f(index, a) index += 1 ret } } def doIndexed[A, B](f: (Int, A) => B): A => B = { new IndexMemoizingFunction(f) } }
это уже все, что вам нужно. Вы можете применить это, например, следующим образом:
import TraversableUtil._ List('a','b','c').map(doIndexed((i, char) => char + i))
что приводит к списку
List(97, 99, 101)
таким образом, вы можете использовать обычные проходимые-функций за счет упаковки эффективная функция. Наслаждайтесь!