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's 있습니까?

당신은 이것을 할 수 있습니다

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

그러나 목록은 두 번 순회됩니다-그리 효율적이지 않습니다.


모두 좋은 반응입니다. Java 'for'루프에서 내가 놓친 것은 여러 이니셜 라이저를 가질 수있는 기능과 단순히 증가 / 감소 이상을 사용하여 "반복"하는 기능입니다. 이것은 Java가 Scala보다 간결 할 수있는 하나의 인스턴스입니다.
snappy

... 증가 / 감소 이상의 것을 사용하는 "반복"... 스칼라에서는 단계로 반복하거나 루프 헤더의 "if"조건으로 반복 할 수 있습니다. 아니면 다른 것을 찾고 있습니까?
om-nom-nom 2011

1
/ * Java * / for (int i = 0, j = 0; i + j <100; i + = j * 2, j + = i + 2) {...} Scala에서 어떻게이 작업을 한 줄로 할 수 있습니까?
snappy

3
@snappy : 제 생각에 스칼라로의 가장 자연스러운 번역은 while루프 일 것입니다. 내가 회상했듯이 몇 년 전 Scala가 Java의 for(;;)루프를 상속해야하는지에 대한 논쟁이 있었으며 추가 된 복잡성을 정당화하기에 이점이 충분하지 않다고 결정했습니다.
Kipton Barros 2011

답변:


130

두 번 순회하는 것보다 훨씬 더 나쁜 것은 쌍의 중간 배열을 생성한다는 것입니다. 사용할 수 있습니다 view. 을 수행하면 collection.view후속 호출이 반복 중에 느리게 작동한다고 생각할 수 있습니다. 완전히 실현 된 적절한 컬렉션을 되 찾으 force려면 마지막에 전화 하십시오. 여기에서는 쓸모없고 비용이 많이 듭니다. 따라서 코드를

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

6
좋은 생각, 단 하나의 순회이지만 새로운 컬렉션을 적절하게 생성하지 않더라도 n 쌍을 생성합니다.
snappy

2
맞습니다. JVM이 이러한 생성을 최적화 할 수 있다는 막연한 희망이 있을지 모르지만 나는 그것을 믿지 않을 것입니다. 나는 인덱스에 대한 반복을 기반으로하지 않는 솔루션을 보지 못했습니다.
Didier Dupont

1
@snappy 이것은 답으로 선택되어야합니다! 대부분의 다른 답변에서 제안 된 인덱스로 요소에 액세스하는 것은 Scala의 기능적 특성을 위반하고 연결된 목록 (예 List: Scala에서 가장 많이 사용되는 컬렉션) 에서뿐만 아니라 끔찍하게 수행 됩니다. 여기 에서 apply작업 확인 하십시오 . 연결된 목록과 같은 컬렉션에서 인덱스로 요소에 액세스 할 때마다 목록이 순회됩니다.
Nikita Volkov

여기에는 상당히 다른 접근 방식이 나와 있습니다. stackoverflow.com/questions/6821194/…
Neil

이것이 왜 효율적입니까? 새로운 배열 객체를 생성하고 추가 기능 ( 'view')을 사용하므로 현명하게 관용적이라고 느끼는 것 외에 이것이 개발자 나 기계에게 왜 효율적인지 알기가 어렵습니다.
matanster

70

Scala 에는for 루프 구문 이 있다고 언급되었습니다 .

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

또는 간단히

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

그러나 효율성도 요구했습니다. 그것은 스칼라 밝혀 for구문은 실제로 같은 고차 방법에 대한 문법 설탕이다 map, foreach등 등과 같은, 어떤 경우에는 이러한 루프가 비효율적 일 수있다, 예를 들어 어떻게 최적화에 스칼라 및 루프 - 지능형 하시나요?

(좋은 소식은 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 컴파일러는 tail-recursive 호출을 while 루프에 해당하는 바이트 코드로 변환합니다.


인덱스 방법 / 기능을 언급하는 데 +1은 거의 모든 개별 프로그래밍 오류를 제거하기 때문에 선호되는 방식입니다.
chaotic3quilibrium 2011-07-27

1
이 경우 것을 주목해야한다 xs(널리 사용되는 등의 연결 목록의 종류이다 List)와 같은 인덱스 요소를 액세스하는 xs(i)선형되며 따라서는 for (i <- xs.indices) println(i + " : " + xs(i))악화에도 이상의 방법을 수행 for((x, i) <- xs.zipWithIndex) println(i + " : " + x)훨씬 더 단 이상에서 발생하는 바와 같이, 후드 아래에서 두 번의 횡단. 따라서 뷰 사용을 제안하는 @didierd의 대답은 가장 일반적인 것과 가장 관용적 인 IMO로 받아 들여 져야합니다.
Nikita Volkov

1
최대 효율성이 필요한 경우 (예 : 수치 계산) 연결 목록을 순회하는 것보다 배열을 인덱싱하는 것이 더 빠릅니다. 연결 목록의 노드는 개별적으로 힙이 할당되며 다른 메모리 위치를 건너 뛰는 것은 CPU 캐시에서 잘 작동하지 않습니다. a view를 사용하는 경우이 높은 수준의 추상화는 힙과 GC에 더 많은 압력을가합니다. 내 경험상 종종 숫자 코드에서 힙 할당을 피함으로써 얻을 수있는 성능 요소가 10 배입니다.
Kipton Barros

20

한 가지 더 방법 :

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

14

실제로 스칼라에는 인덱스가있는 오래된 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)A는 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)목록에 (N ^ 2) O 복잡성 제기
바드

@Vadzim 사실입니다.하지만 Java에서도 LinkedList를 사용하여 인덱스에 for 루프를 사용했습니다.
francoisr

1
배열의 xs (i)의 경우 위 코드는 O (n)입니다. 스칼라의 배열이 거의 일정한 시간에 랜덤 액세스를 제공하기 때문에?
dhfromkorea

2
@dhfromkorea 예, 어레이에 대해 빠르다 (실제로 O (n))
om-nom-nom

6

이건 어때?

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

산출:

0: One
1: Two
2: Three

4

스칼라에서 반복하는 것은 매우 간단합니다. 예를 들어 원하는 배열을 만듭니다.

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

이것은 확실히 표준 라이브러리에 추가되는 것이 합리적입니다.
snappy

1
일반적인 foreach / map API를 따르고 싶다면 어쨌든 튜플이 붙어 있기 때문입니다.
Alex Cruise

3

반복하는 몇 가지 다른 방법 :

scala>  xs.foreach (println) 
first
second
third

foreach 및 이와 유사한 map은 무언가를 반환합니다 (기능의 결과, 즉 println, Unit, 따라서 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) 

carry-part는 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

의 구현에서 영감을 간단하고 효율적인 방법 transform으로 SeqLike.scala

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

0

제안 된 솔루션은 컬렉션을 명시 적으로 반복하거나 컬렉션을 함수에 넣는다는 사실로 인해 어려움을 겪습니다. 스칼라의 일반적인 관용구를 고수하고 인덱스를 일반적인 맵 또는 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.