2011-08-22 23 views
15

Ví dụ giả sử tôi cóCó cách nào để xử lý trường hợp cuối cùng khác nhau trong một vòng lặp Scala không?

for (line <- myData) { 
    println("}, {") 
    } 

Có cách nào để có được những dòng cuối cùng để in

println("}") 
+0

Như câu trả lời của @ Nicolas đã cho biết, thường dễ dàng hơn nhiều nếu bạn có thể tái cấu trúc hành vi mong muốn của mình để hoạt động khác nhau cho lần lặp * đầu tiên *, chứ không phải là cuối cùng. Nó là tầm thường để xác định lần lặp đầu tiên. –

+4

+1 để sử dụng scala –

Trả lời

18

Trước khi tiếp tục, tôi 'd khuyên bạn nên tránh println để hiểu. Đôi khi nó có thể hữu ích cho việc theo dõi một lỗi xảy ra ở giữa một bộ sưu tập, nhưng nếu không thì sẽ dẫn đến mã khó tái cấu trúc và kiểm tra hơn.

Nói chung, cuộc sống thường trở nên dễ dàng hơn nếu bạn có thể giới hạn ở bất kỳ nơi nào loại tác dụng phụ xảy ra. Vì vậy, thay vì:

for (line <- myData) { 
    println("}, {") 
} 

Bạn có thể viết:

val lines = for (line <- myData) yield "}, {" 
println(lines mkString "\n") 

Tôi cũng sẽ mất một đoán ở đây mà bạn muốn nội dung của mỗi dòng trong đầu ra!

val lines = for (line <- myData) yield (line + "}, {") 
println(lines mkString "\n") 

Mặc dù bạn sẽ tốt hơn nếu bạn chỉ sử dụng trực tiếp mkString - đó là những gì nó dành cho!

val lines = myData.mkString("{", "\n}, {", "}") 
println(lines) 

Lưu ý cách đầu tiên chúng tôi sản xuất String, sau đó in trong một thao tác. Cách tiếp cận này có thể dễ dàng được chia thành các phương thức riêng biệt và được sử dụng để thực hiện toString trên lớp của bạn hoặc để kiểm tra Chuỗi được tạo trong các thử nghiệm.

6

Bạn có thể lấy addString chức năng của đặc điểm TraversableOnce làm ví dụ.

def addString(b: StringBuilder, start: String, sep: String, end: String): StringBuilder = { 
    var first = true 

    b append start 
    for (x <- self) { 
    if (first) { 
     b append x 
     first = false 
    } else { 
     b append sep 
     b append x 
    } 
    } 
    b append end 

    b 
} 

Trong trường hợp của bạn, tách là }, { và cuối cùng là }

+0

+1. Rất vui được xem nó được thực hiện như thế nào trong thư viện chuẩn. –

28

Bạn có thể cấu trúc lại mã của bạn để tận dụng built-in mkString?

scala> List(1, 2, 3).mkString("{", "}, {", "}") 
res1: String = {1}, {2}, {3} 
2

Nếu bạn không muốn sử dụng built-in chức năng mkString, bạn có thể làm một cái gì đó giống như

for (line <- lines) 
    if (line == lines.last) println("last") 
    else println(line) 

UPDATE: Như didierd nêu trong ý kiến, giải pháp này là sai vì giá trị cuối cùng có thể xảy ra nhiều lần, anh ấy cung cấp giải pháp tốt hơn trong số answer của mình.

Nó là tốt cho Vectors, vì last chức năng mất "thời gian một cách hiệu quả liên tục" cho họ, như đối với Lists, phải mất thời gian tuyến tính, vì vậy bạn có thể sử dụng mô hình kết hợp

@tailrec 
def printLines[A](l: List[A]) { 
    l match { 
    case Nil => 
    case x :: Nil => println("last") 
    case x :: xs => println(x); printLines(xs) 
    } 
} 
+1

Giải pháp đệ quy là tốt. Một với == lines.last là sai, trừ khi bạn biết giá trị của phần tử cuối cùng xảy ra ở đâu khác. –

+0

@didierd cảm ơn, tôi đã bỏ lỡ nó. Tôi đã chỉ ra sai lầm trong câu trả lời. – 4e6

+0

Tôi đã thử nó bằng cách sử dụng 'eq' thay vì' == 'và nó vẫn không hoạt động - các chuỗi phải được lưu trữ, đó là một chút đau đớn –

12

Tôi đồng ý hoàn toàn với những gì đã được nói trước đây về việc sử dụng mkstring và phân biệt lần lặp đầu tiên thay vì lần lặp lại đầu tiên. Bạn vẫn cần phải phân biệt vào lần cuối, các bộ sưu tập scala có phương thức init, trả về tất cả các phần tử nhưng cuối cùng. Vì vậy, bạn có thể làm

for(x <- coll.init) workOnNonLast(x) 
workOnLast(coll.last) 

(initlast là loại trái ngược với người đứng đầu và đuôi, mà là người đầu tiên và và tất cả nhưng trước tiên). Tuy nhiên, lưu ý tùy thuộc vào cấu trúc, chúng có thể tốn kém. Trên Vector, tất cả đều nhanh. Trên List, trong khi đầu và đuôi cơ bản là miễn phí, initlast là cả hai tuyến tính trong độ dài của danh sách. headOptionlastOption có thể giúp bạn khi bộ sưu tập có thể rỗng, thay thế workOnlast bởi

for (x <- coll.lastOption) workOnLast(x) 
1

câu trả lời khác là chính đáng chỉ vào mkString, và cho một số lượng bình thường của dữ liệu tôi cũng sẽ sử dụng đó.

Tuy nhiên, mkString tạo (tích lũy) kết quả trong bộ nhớ thông qua StringBuilder. Điều này không phải lúc nào cũng mong muốn, tùy thuộc vào lượng dữ liệu chúng tôi có.

Trong trường hợp này, nếu tất cả những gì chúng tôi muốn là "in", chúng tôi không cần phải tạo kết quả lớn trước (và có thể chúng tôi thậm chí muốn tránh điều này).

Xem xét việc thực hiện các chức năng helper này:

def forEachIsLast[A](iterator: Iterator[A])(operation: (A, Boolean) => Unit): Unit = { 
    while(iterator.hasNext) { 
    val element = iterator.next() 
    val isLast = !iterator.hasNext // if there is no "next", this is the last one 
    operation(element, isLast) 
    } 
}  

Nó lặp lại trên tất cả các yếu tố và gọi operation đi qua mỗi phần tử lần lượt, với một giá trị boolean. Giá trị là true nếu phần tử được chuyển là ảnh cuối cùng.

Trong trường hợp của bạn nó có thể được sử dụng như thế này:

forEachIsLast(myData) { (line, isLast) => 
    if(isLast) 
    println("}") 
    else 
    println("}, {") 
} 

Chúng tôi có những ưu điểm sau đây:

  • Nó hoạt động trên mỗi phần tử, từng người một, mà không nhất thiết phải tích lũy kết quả trong bộ nhớ (trừ khi bạn muốn).
  • Bởi vì nó không cần phải tải toàn bộ bộ sưu tập vào bộ nhớ để kiểm tra kích thước của nó, nó đủ để yêu cầu Iterator nếu nó đã cạn kiệt hay không. Bạn có thể đọc dữ liệu từ một tệp lớn hoặc từ mạng, v.v.
+2

Hoặc, hơi thanh lịch hơn, có thể: 'forEachIsLast' chỉ có thể là' val it = seq.toIterator it.foreach {operation (_, it.hasNext)} ' –

+0

Cảm ơn rất nhiều @TheArchetypalPaul! Đã chỉnh sửa câu trả lời của tôi để sử dụng triển khai dựa trên đề xuất của bạn. –

Các vấn đề liên quan