Эффективная итерация с индексом в 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?

Вы можете сделать это

val xs = Array("first", "second", "third")
val indexed = xs zipWithIndex
for (x <- indexed) println("String #" + x._2 + " is " + x._1)

но список проходит дважды - не очень эффективно.

Ответ 1

Намного хуже, чем пересечение дважды, он создает промежуточный массив пар. Вы можете использовать view. Когда вы выполняете collection.view, вы можете думать о последующих вызовах как лениво, во время итерации. Если вы хотите вернуть полноценную полностью реализованную коллекцию, вы вызываете force в конце. Здесь это было бы бесполезно и дорого. Поэтому измените свой код на

for((x,i) <- xs.view.zipWithIndex) println("String #" + i + " is " + x)

Ответ 2

Было упомянуто, что 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.

Ответ 3

Еще один способ:

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

Ответ 4

Собственно, 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

Ответ 5

Как насчет этого?

val a = Array("One", "Two", "Three")
a.foldLeft(0) ((i, x) => {println(i + ": " + x); i + 1;} )

Вывод:

0: One
1: Two
2: Three

Ответ 6

Нет ничего в stdlib, который сделает это для вас, не создавая мусор, но его не так сложно написать. К сожалению, я никогда не потрудился выяснить, как сделать надлежащее CanBuildFrom неявное насилие, чтобы сделать такие вещи родовыми в типе коллекции, к которой они применяются, но если это возможно, я уверен, что кто-то просветит нас.:)

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)
}

Ответ 7

Еще несколько способов повторения:

scala>  xs.foreach (println) 
first
second
third

foreach и аналогичная карта, которая вернет что-то (результаты функции, которая для println, Unit, так что список единиц)

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) 

Несущая часть - это просто пример, чтобы сделать что-то с я и j. Это не должно быть Int.

для более простых вещей, ближе к обычным для петель:

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) 

Ответ 8

Действительно, вызов 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
    }
  }

Это должно быть даже немного более эффективным, чем создание представления в коллекции.

Ответ 9

Простой и эффективный способ, основанный на реализации transform в SeqLike.scala

    var i = 0
    xs foreach { el =>
      println("String #" + i + " is " + xs(i))
      i += 1
    }

Ответ 10

Цикл в 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))

Ответ 11

У меня есть следующие подходы

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);

   }
}

Ответ 12

Предлагаемые решения связаны с тем, что они либо явно перебирают коллекцию, либо собирают ее в функцию. Естественно придерживаться обычных идиом Scala и помещать индекс в обычные методы map- или foreach. Это можно сделать с помощью memoizing. Полученный код может выглядеть как

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)

Таким образом, вы можете использовать обычные функции Traversable за счет переноса вашей эффективной функции. Наслаждайтесь!