Scalaのインデックスを使用した効率的な反復


83

Scalaにはforインデックス付きの古いJavaスタイルのループがないため、

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

しかし、リストは2回トラバースされます-あまり効率的ではありません。


これらはすべて良い反応です。Javaの「for」ループに欠けているのは、複数の初期化子を持つ機能と、単なるインクリメント/デクリメント以上のものを使用して「反復」する機能です。これは、JavaがScalaよりも簡潔になる可能性がある1つの例です。
スナッピー2011

...単なるインクリメント/デクリメント以上のものを使用して「反復」... scalaでは、stepで反復するか、ループヘッダーの「if」条件で反復することができます。またはあなたは何か他のものを探していますか?
om-nom-nom 2011

1
/ * Java * / for(int i = 0、j = 0; i + j <100; i + = j * 2、j + = i + 2){...} Scalaでこれを1行で行うにはどうすればよいですか?
スナッピー2011

3
@snappy:私の意見では、Scalaへの最も自然な翻訳はwhileループでしょう。私が思い出したように、数年前にScalaがJavaのfor(;;)ループを継承すべきかどうかについての議論があり、その利点は追加された複雑さを正当化するのに十分ではないと判断されました。
キプトンバロス2011

回答:


130

2回トラバースするよりもはるかに悪いことに、ペアの中間配列が作成されます。を使用できますview。あなたが行うとcollection.view、あなたは反復中、ゆったりと演技として後続の呼び出しと考えることができます。適切に完全に実現されたコレクションを取り戻したい場合forceは、最後に電話します。ここでは、それは役に立たず、コストがかかります。したがって、コードを次のように変更します

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

6
良いアイデアです。トラバーサルは1つだけですが、新しいコレクションが適切に作成されない場合でも、nペアも作成されます。
スナッピー2011

2
まったく正しい。JVMがそれらの作成を最適化するかもしれないという漠然とした希望があるかもしれませんが、私はそれを当てにしません。その場合、インデックスの反復に基づかないソリューションは見当たりません。
Didier Dupont 2011

1
@snappyこれは答えとして選ばれるべきでした!他のほとんどの回答で提案されているインデックスによる要素へのアクセスは、Scalaの機能的性質に違反し、リンクリスト(ListScalaで最も使用されているコレクションなど)でひどく実行されます。こちらapply操作をご覧ください。リンクリストのようなコレクションでは、インデックスによる要素へのすべてのアクセスにより、リストがトラバースされます。
ニキータボルコフ2012年

まったく異なるアプローチがここに示されています:stackoverflow.com/questions/6821194/…–
Neil

なぜこれが効率的ですか?それは新しい配列オブジェクトを作成していて、追加の関数( `view ')を使用しているので、これが開発者にとってもマシンにとっても効率的である理由を理解するのは難しいと思います。
マタンスター2016年

70

Scalaにforループの構文があると言われています:

for (i <- 0 until xs.length) ...

または単に

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

しかし、あなたは効率も求めました。これは、Scalaのことが判明したfor構文が実際のような高次のメソッドの構文糖であるmapforeach例えば、いくつかのケースでは、これらのループは非効率的であることができ、このようになど、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ループと同等のバイトコードに変換します。


インデックスの方法/関数について言及するための+1は、オフバイワンのプログラミングエラーのセット全体を事実上排除するため、非常に好ましいと思います。
chaotic3quilibrium 2011

1
場合ことに留意しなければならないxs(例えば、広く使用されるようなリンクリストの任意の種類のものであるList)、のような指標によってその要素にアクセスするとxs(i)、リニアになりますので、for (i <- xs.indices) println(i + " : " + xs(i))さえより悪い方法を実行しますfor((x, i) <- xs.zipWithIndex) println(i + " : " + x)それだけよりもはるかにになりますよう、ボンネットの下で2つのトラバーサル。したがって、ビューの使用を提案する@didierdの回答は、最も一般的で最も慣用的なIMOとして受け入れられる必要があります。
ニキータボルコフ2012年

1
最大の効率が必要な場合(たとえば、数値計算で)、リンクリストをトラバースするよりも配列にインデックスを付ける方が高速です。リンクリストのノードは個別にヒープに割り当てられ、異なるメモリ位置をジャンプすることはCPUキャッシュではうまく機能しません。aviewを使用すると、この高レベルの抽象化でさえ、ヒープとGCにさらに圧力がかかります。私の経験では、数値コードでヒープの割り当てを回避することで得られるパフォーマンスには、多くの場合10倍の係数があります。
キプトンバロス2012

20

もう1つの方法:

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

5
私はあなたがインデックスのメソッド/関数を指摘するのが本当に好きです。複雑さを軽減し、すべてのソフトウェアエンジニアリングで最も一般的なプログラミングエラー/バグである「オフバイワン」エラーのセット全体を事実上排除します。
chaotic3quilibrium 2011

14

実際、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
xs(i)リストに載ると複雑さがO(n ^ 2)に
上がる

@Vadzimそれは本当ですが、Javaの場合も同様です。LinkedListを使用してインデックスでforループを使用しました
francoisr

1
配列のxs(i)の場合、上記のコードはO(n)ですよね?Scalaの配列はほぼ一定の時間のランダムアクセスを提供するので?
dhfromkorea 2016年

2
@dhfromkoreaはい、配列では高速である必要があります(実際にはO(n))
om-nom-nom 2016年

6

これはどう?

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

出力:

0: One
1: Two
2: Three

4

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

4

実際、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
    }
  }

これは、コレクションのビューを作成するよりも少し効率的です。


3

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

これは、標準ライブラリに追加するのが間違いなく理にかなっていることです。
スナッピー2011

1
通常のforeach / map APIに準拠したい場合は、とにかくタプルで立ち往生しているので、よくわかりません。
アレックスクルーズ

3

反復する他のいくつかの方法:

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) 

キャリーパートは、iとjで何かを行うためのほんの一例です。Intである必要はありません。

より単純なものの場合、通常のforループに近い:

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) 

3

私は次のアプローチを持っています

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

   }
}

2

実装からインスピレーションを簡単かつ効率的な方法、transformSeqLike.scala

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

0

提案されたソリューションは、コレクションを明示的に反復するか、コレクションを関数に詰め込むという事実に悩まされています。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)

このようにして、効果的な関数をラップすることを犠牲にして、通常のTraversable関数を使用できます。楽しい!

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.