Lặp lại hiệu quả với chỉ mục trong Scala


83

Vì Scala không có các forvòng lặp kiểu Java cũ với chỉ mục,

// does not work
val xs = Array("first", "second", "third")
for (i=0; i<xs.length; i++) {
  println("String #" + i + " is " + xs(i))
}

Làm thế nào chúng ta có thể lặp lại một cách hiệu quả mà không cần sử dụng var's?

Bạn có thể làm điều này

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

nhưng danh sách được duyệt hai lần - không hiệu quả lắm.


Đây là tất cả các phản hồi tốt. Điều tôi thiếu từ các vòng lặp Java 'for' là khả năng có nhiều bộ khởi tạo và khả năng "lặp đi lặp lại" bằng cách sử dụng nhiều hơn là chỉ tăng / giảm. Đây là một ví dụ mà Java có thể ngắn gọn hơn Scala.
snappy

... "lặp đi lặp lại" sử dụng nhiều hơn là chỉ tăng / giảm ... Trong tỷ lệ, có thể lặp với bước hoặc lặp với điều kiện "nếu" trong tiêu đề vòng lặp. Hay bạn đang tìm kiếm thứ gì khác?
om-nom-nom

1
/ * Java * / for (int i = 0, j = 0; i + j <100; i + = j * 2, j + = i + 2) {...} Làm cách nào bạn có thể thực hiện điều này trong 1 dòng trong Scala?
snappy

3
@snappy: Theo tôi, bản dịch tự nhiên nhất sang Scala sẽ là một whilevòng lặp. Như tôi nhớ lại, đã có một cuộc tranh luận cách đây vài năm về việc liệu Scala có nên kế thừa for(;;)vòng lặp của Java hay không , và người ta quyết định rằng lợi ích không đủ để biện minh cho sự phức tạp tăng thêm.
Kipton Barros

Câu trả lời:


130

Tệ hơn nhiều so với việc duyệt hai lần, nó tạo ra một mảng trung gian của các cặp. Bạn có thể sử dụng view. Khi bạn làm vậy collection.view, bạn có thể nghĩ về các cuộc gọi tiếp theo như hành động một cách lười biếng trong quá trình lặp lại. Nếu bạn muốn lấy lại một bộ sưu tập đã được thực hiện đầy đủ thích hợp, bạn gọi điện forceở cuối. Ở đây điều đó sẽ vô ích và tốn kém. Vì vậy, hãy thay đổi mã của bạn thành

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

6
Ý tưởng hay, chỉ một lần đi ngang, nhưng nó cũng tạo ra n cặp, ngay cả khi nó không tạo ra một bộ sưu tập mới thích hợp.
snappy

2
Khá đúng. Có thể có một hy vọng mơ hồ rằng JVM có thể tối ưu hóa những sáng tạo đó, nhưng tôi sẽ không tin vào điều đó. Tôi không thấy một giải pháp nào sẽ không dựa trên việc lặp lại các chỉ mục sau đó.
Didier Dupont

1
@snappy Câu này lẽ ra phải được chọn làm câu trả lời! Việc truy cập các phần tử theo chỉ mục, được đề xuất trong hầu hết các câu trả lời khác, vi phạm bản chất chức năng của Scala và hoạt động kém hiệu quả trên các danh sách được liên kết (như List, bộ sưu tập được sử dụng nhiều nhất trong Scala) - và không chỉ trên chúng. Kiểm tra applyhoạt động ở đây . Trong một bộ sưu tập giống như danh sách được liên kết, mọi quyền truy cập vào một phần tử theo chỉ mục sẽ dẫn đến việc truyền qua danh sách.
Nikita Volkov

khá một cách tiếp cận khác nhau được đưa ra ở đây: stackoverflow.com/questions/6821194/...
Neil

Tại sao điều này lại hiệu quả? nó đang tạo một đối tượng mảng mới và sử dụng một chức năng bổ sung (`` view '), vì vậy tôi cảm thấy khó hiểu tại sao điều này lại hiệu quả đối với nhà phát triển cũng như máy tính, ngoài việc cảm thấy thông minh.
matanster

70

Nó đã được đề cập rằng Scala không có cú pháp cho forvòng:

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

hoặc đơn giản

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

Tuy nhiên, bạn cũng yêu cầu hiệu quả. Nó chỉ ra rằng forcú pháp Scala thực sự là một đường cú pháp cho các phương thức bậc cao hơn, chẳng hạn như map, foreachv.v. Như vậy, trong một số trường hợp, các vòng lặp này có thể không hiệu quả, ví dụ: Làm thế nào để tối ưu hóa các phần và vòng lặp trong Scala?

(Tin tốt là nhóm Scala đang làm việc để cải thiện điều này. Đây là sự cố trong trình theo dõi lỗi: https://issues.scala-lang.org/browse/SI-4633 )

Để đạt hiệu quả cao nhất, người ta có thể sử dụng một whilevòng lặp hoặc, nếu bạn nhấn mạnh vào việc loại bỏ việc sử dụng var, đệ quy đuôi:

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

Lưu ý rằng chú thích tùy chọn @tailrec rất hữu ích để đảm bảo rằng phương thức thực sự là đệ quy đuôi. Trình biên dịch Scala dịch các cuộc gọi đệ quy đuôi thành mã byte tương đương với các vòng lặp while.


+1 để đề cập đến phương thức / hàm chỉ số vì tôi thấy nó thích hợp hơn do nó hầu như loại bỏ toàn bộ tập hợp các lỗi lập trình riêng lẻ.
hỗn loạn3 trạng thái cân bằng,

1
Ở đây phải lưu ý rằng nếu xsthuộc bất kỳ loại danh sách liên kết nào (chẳng hạn như được sử dụng rộng rãi List), việc truy cập các phần tử của nó theo chỉ mục như xs(i)sẽ là tuyến tính và do đó, for (i <- xs.indices) println(i + " : " + xs(i))nó sẽ hoạt động kém hơn thậm chí for((x, i) <- xs.zipWithIndex) println(i + " : " + x), vì nó sẽ dẫn đến nhiều hơn là chỉ hai đường đi ngang dưới mui xe. Do đó, câu trả lời của @didierd đề xuất sử dụng quan điểm nên được chấp nhận là câu trả lời chung nhất và câu thành ngữ nhất, IMO.
Nikita Volkov,

1
Nếu cần hiệu suất tối đa (ví dụ: trong tính toán số), lập chỉ mục mảng sẽ nhanh hơn so với duyệt qua danh sách liên kết. Các nút của một danh sách được liên kết được phân bổ heap riêng biệt và việc nhảy qua các vị trí bộ nhớ khác nhau không hoạt động tốt với bộ đệm CPU. Nếu a viewđược sử dụng, mức trừu tượng thậm chí cao này sẽ gây áp lực nhiều hơn lên heap và GC. Theo kinh nghiệm của tôi, thường có hệ số 10 trong hiệu suất để đạt được bằng cách tránh phân bổ đống trong mã số.
Kipton Barros

20

Một cách nữa:

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
Tôi thực sự thích bạn chỉ ra phương pháp / chức năng chỉ số. Nó làm giảm độ phức tạp và hầu như loại bỏ toàn bộ tập hợp các lỗi "từng lỗi một" vốn là lỗi / lỗi lập trình phổ biến nhất trong tất cả các kỹ thuật phần mềm.
hỗn loạn 3 trạng thái cân

14

Trên thực tế, scala có các vòng lặp kiểu Java cũ với chỉ mục:

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

Where 0 until xs.lengthhoặc 0.until(xs.length)là một RichIntphương thức trả về Rangephù hợp với lặp.

Ngoài ra, bạn có thể thử vòng lặp với 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)trên danh sách làm tăng độ phức tạp O (n ^ 2)
Vadzim

@Vadzim Đó là sự thật, nhưng điều đó cũng sẽ là trường hợp trong Java trong bạn sử dụng một vòng lặp for trên các chỉ số với một LinkedList
francoisr

1
Trong trường hợp xs (i) trên Mảng, mã trên là O (n), phải không? Vì Mảng trong tỷ lệ cung cấp quyền truy cập ngẫu nhiên theo thời gian gần như không đổi?
dhfromkorea

2
@dhfromkorea có, cần được nhanh chóng cho Mảng (thực sự O (n))
om-nôm-nom

6

Còn cái này thì sao?

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

Đầu ra:

0: One
1: Two
2: Three

4

Vòng lặp trong scala khá đơn giản. Tạo bất kỳ mảng nào bạn chọn cho người yêu cũ.

val myArray = new Array[String](3)
myArray(0)="0";
myArray(1)="1";
myArray(2)="2";

Các loại vòng lặp,

for(data <- myArray)println(data)

for (i <- 0 until myArray.size)
println(i + ": " + myArray(i))

4

Thật vậy, việc gọi zipWithIndexmột bộ sưu tập sẽ đi qua nó và cũng tạo ra một bộ sưu tập mới cho các cặp. Để tránh điều này, bạn chỉ có thể gọi zipWithIndextrình vòng lặp cho bộ sưu tập. Điều này sẽ chỉ trả về một trình lặp mới theo dõi chỉ mục trong khi lặp, do đó mà không cần tạo thêm một bộ sưu tập hoặc duyệt bổ sung.

Đây là cách scala.collection.Iterator.zipWithIndexhiện được triển khai trong 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
    }
  }

Điều này thậm chí sẽ hiệu quả hơn một chút so với việc tạo chế độ xem trên bộ sưu tập.


3

Không có gì trong stdlib sẽ làm điều đó cho bạn mà không cần tạo tuple rác, nhưng không quá khó để viết cho riêng bạn. Thật không may, tôi chưa bao giờ bận tâm để tìm ra cách thực hiện CanBuildF thích hợp từ mưa ngầm để biến những thứ như vậy trở nên chung chung trong loại bộ sưu tập mà chúng áp dụng, nhưng nếu có thể, tôi chắc rằng ai đó sẽ khai sáng cho chúng tôi. :)

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

Đây là thứ chắc chắn sẽ có ý nghĩa khi được thêm vào thư viện tiêu chuẩn.
snappy

1
Tôi không chắc lắm, vì nếu bạn muốn tuân theo các API foreach / map thông thường thì bạn vẫn mắc kẹt với các bộ giá trị.
Alex Cruise

3

Một số cách khác để lặp lại:

scala>  xs.foreach (println) 
first
second
third

foreach, và tương tự, map, sẽ trả về một thứ gì đó (kết quả của hàm, đối với println, Unit, vì vậy một Danh sách các Unit)

scala> val lens = for (x <- xs) yield (x.length) 
lens: Array[Int] = Array(5, 6, 5)

làm việc với các phần tử, không phải chỉ mục

scala> ("" /: xs) (_ + _) 
res21: java.lang.String = firstsecondthird

gấp

for(int i=0, j=0; i+j<100; i+=j*2, j+=i+2) {...}

có thể được thực hiện với đệ quy:

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) 

Phần mang theo chỉ là một số ví dụ, để làm điều gì đó với i và j. Nó không cần phải là một Int.

đối với những thứ đơn giản hơn, gần với vòng lặp for thông thường:

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)

hoặc không có lệnh:

List (1, 3, 2, 5, 9, 7).foreach (print) 

3

Tôi có các cách tiếp cận sau

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

Một cách đơn giản và hiệu quả, lấy cảm hứng từ việc triển khai transformtrong SeqLike.scala

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

0

Các giải pháp được đề xuất gặp phải thực tế là chúng hoặc lặp lại một cách rõ ràng qua một bộ sưu tập hoặc nhồi bộ sưu tập vào một hàm. Tự nhiên hơn là gắn bó với các thành ngữ thông thường của Scala và đặt chỉ mục bên trong các phương thức map- hoặc foreach thông thường. Điều này có thể được thực hiện bằng cách sử dụng ghi nhớ. Mã kết quả có thể trông giống như

myIterable map (doIndexed(someFunction))

Đây là một cách để đạt được mục đích này. Hãy xem xét tiện ích sau:

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

Đây đã là tất cả những gì bạn cần. Bạn có thể áp dụng điều này ví dụ như sau:

import TraversableUtil._
List('a','b','c').map(doIndexed((i, char) => char + i))

kết quả nào trong danh sách

List(97, 99, 101)

Bằng cách này, bạn có thể sử dụng các chức năng Traversable thông thường với chi phí đóng gói chức năng hiệu quả của bạn. Thưởng thức!

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.