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

как мы можем итерацию эффективно, и без использования vars?

вы могли бы сделать это

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

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

12 71

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)

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