2013-07-17 52 views
9

Tôi có một Iterator của các yếu tố và tôi muốn để tiêu thụ chúng cho đến khi một điều kiện được đáp ứng trong phần tử tiếp theo, như:Làm thế nào để sử dụng takeWhile với một Iterator trong Scala

val it = List(1,1,1,1,2,2,2).iterator 
val res1 = it.takeWhile(_ == 1).toList 
val res2 = it.takeWhile(_ == 2).toList 

res1 đưa ra một dự kiến ​​List(1,1,1,1) nhưng res2 trả về List(2,2) vì trình vòng lặp phải kiểm tra phần tử ở vị trí 4.

Tôi biết rằng danh sách sẽ được sắp xếp sao cho không có điểm nào vượt qua toàn bộ danh sách như partition. Tôi muốn hoàn thành ngay sau khi điều kiện không được đáp ứng. Có cách nào thông minh để làm điều này với Iterator? Tôi không thể làm một toList cho trình vòng lặp vì nó đến từ một tệp rất lớn.

Trả lời

2

Với câu trả lời khác của tôi (mà tôi đã rời riêng biệt vì chúng là không liên quan), tôi nghĩ bạn có thể thực hiện groupWhen trên Iterator như sau:

def groupWhen[A](itr: Iterator[A])(p: (A, A) => Boolean): Iterator[List[A]] = { 
    @annotation.tailrec 
    def groupWhen0(acc: Iterator[List[A]], itr: Iterator[A])(p: (A, A) => Boolean): Iterator[List[A]] = { 
    val (dup1, dup2) = itr.duplicate 
    val pref = ((dup1.sliding(2) takeWhile { case Seq(a1, a2) => p(a1, a2) }).zipWithIndex collect { 
     case (seq, 0)  => seq 
     case (Seq(_, a), _) => Seq(a) 
    }).flatten.toList 
    val newAcc = if (pref.isEmpty) acc else acC++ Iterator(pref) 
    if (dup2.nonEmpty) 
     groupWhen0(newAcc, dup2 drop (pref.length max 1))(p) 
    else newAcc 
    } 
    groupWhen0(Iterator.empty, itr)(p) 
} 

Khi tôi chạy nó trên một ví dụ:

println(groupWhen(List(1,1,1,1,3,4,3,2,2,2).iterator)(_ == _).toList) 

tôi nhận được List(List(1, 1, 1, 1), List(2, 2, 2))

+0

Hãy coi chừng rằng việc triển khai này sẽ làm giảm các phần tử mà vị từ trả về false. Sử dụng borice tốt hơn. –

0

Bạn có thể sử dụng phương pháp toStream trên Iterator.

Stream tương đương với số lười là List.

Như bạn có thể thấy từ implementation của toStream nó tạo ra một Stream mà không vượt qua toàn bộ Iterator.

Stream giữ tất cả phần tử trong bộ nhớ. Bạn nên bản địa hóa việc sử dụng liên kết đến Stream ở một số phạm vi địa phương để ngăn rò rỉ bộ nhớ.

Với Stream bạn nên sử dụng span như thế này:

val (res1, rest1) = stream.span(_ == 1) 
val (res2, rest2) = rest1.span(_ == 2) 
+1

Nhưng luồng có nhược điểm lớn nhất phải biết: không giống như trình vòng lặp nó ** giữ tất cả các mục ** anh ấy đã đọc trong bộ nhớ. –

+0

@ om-nom-nom: OP cần tất cả các mục nếu anh ta muốn nhắc lại về bộ sưu tập. Và 'Stream' chỉ giữ các phần tử trong khi có một liên kết đến phần tử đầu tiên. – senia

+0

Nhưng sau đó lần đầu tiên tôi thực hiện takeWhile tôi nhận được một Stream (1, 1, 1, 1, 2,?) Và takeWhile thứ hai bắt đầu lại từ đầu Stream (1, 1, 1, 1, 2, ?) cho một Dòng trống – tonicebrian

0

Tôi đoán một chút ở đây nhưng do báo cáo kết quả "cho đến khi một điều kiện được đáp ứng trong phần tử tiếp theo", có vẻ như bạn có thể muốn nhìn vào groupWhen phương pháp trên ListOps trong scalaz

scala> import scalaz.syntax.std.list._ 
import scalaz.syntax.std.list._ 

scala> List(1,1,1,1,2,2,2) groupWhen (_ == _) 
res1: List[List[Int]] = List(List(1, 1, 1, 1), List(2, 2, 2)) 

về cơ bản này "khối "up chuỗi đầu vào khi một điều kiện (một (A, A) => Boolean) được đáp ứng giữa một phần tử và người kế nhiệm của nó. Trong ví dụ trên điều kiện là bình đẳng, do đó, miễn là một phần tử bằng với người kế thừa của nó, chúng sẽ ở trong cùng một đoạn.

+0

Vâng, đó là chức năng tôi đang tìm kiếm, nhưng vấn đề là tôi không thể giữ trong bộ nhớ kết quả của nhómKhi đó. Tôi nhận được các giá trị thông qua một dòng đọc lặp từ một tập tin lớn. Có một nhómKhi cho vòng lặp tồn tại trong scalaz? – tonicebrian

+0

Không - scalaz không "thích" trình lặp (chúng không thuần túy). Họ có một lớp gọi là 'EphemeralStream'. Nó không đi kèm với một 'groupWhen' nhưng bạn có thể viết một cách dễ dàng đủ, cho rằng đó là một * monad *. Tôi sẽ không đảm bảo nó sẽ không tràn ngăn xếp mặc dù! –

+0

Tôi đã thêm một câu trả lời khác bên dưới, cho thấy cách bạn có thể thêm groupBy vào một Iterator bằng cách sử dụng chức năng 'iterator.duplicate'. –

3

tôi đã có một nhu cầu tương tự, nhưng solution từ @oxbow_lakes không mất trong để giải thích tình hình khi danh sách chỉ có một phần tử hoặc thậm chí nếu danh sách chứa các phần tử không được lặp lại. Ngoài ra, giải pháp đó không cho vay tốt với một trình lặp vô hạn (nó muốn "xem" tất cả các phần tử trước khi nó mang lại cho bạn một kết quả).

Điều tôi cần là khả năng nhóm các phần tử tuần tự khớp với vị từ, nhưng cũng bao gồm các phần tử đơn (tôi luôn có thể lọc chúng ra nếu tôi không cần chúng).Tôi cần các nhóm đó được phân phối liên tục, mà không phải đợi cho bản lặp đầu tiên hoàn toàn được tiêu thụ trước khi chúng được sản xuất.

tôi đã đưa ra các phương pháp sau đây mà làm việc cho nhu cầu của tôi, và nghĩ rằng tôi nên chia sẻ:

implicit class IteratorEx[+A](itr: Iterator[A]) { 
    def groupWhen(p: (A, A) => Boolean): Iterator[List[A]] = new AbstractIterator[List[A]] { 
    val (it1, it2) = itr.duplicate 
    val ritr = new RewindableIterator(it1, 1) 

    override def hasNext = it2.hasNext 

    override def next() = { 
     val count = (ritr.rewind().sliding(2) takeWhile { 
     case Seq(a1, a2) => p(a1, a2) 
     case _ => false 
     }).length 

     (it2 take (count + 1)).toList 
    } 
    } 
} 

Ở trên là sử dụng một vài lớp helper:

abstract class AbstractIterator[A] extends Iterator[A] 

/** 
* Wraps a given iterator to add the ability to remember the last 'remember' values 
* From any position the iterator can be rewound (can go back) at most 'remember' values, 
* such that when calling 'next()' the memoized values will be provided as if they have not 
* been iterated over before. 
*/ 
class RewindableIterator[A](it: Iterator[A], remember: Int) extends Iterator[A] { 
    private var memory = List.empty[A] 
    private var memoryIndex = 0 

    override def next() = { 
    if (memoryIndex < memory.length) { 
     val next = memory(memoryIndex) 
     memoryIndex += 1 
     next 
    } else { 
     val next = it.next() 
     memory = memory :+ next 
     if (memory.length > remember) 
     memory = memory drop 1 
     memoryIndex = memory.length 
     next 
    } 
    } 

    def canRewind(n: Int) = memoryIndex - n >= 0 

    def rewind(n: Int) = { 
    require(memoryIndex - n >= 0, "Attempted to rewind past 'remember' limit") 
    memoryIndex -= n 
    this 
    } 

    def rewind() = { 
    memoryIndex = 0 
    this 
    } 

    override def hasNext = it.hasNext 
} 

Ví dụ sử dụng:

List(1,2,2,3,3,3,4,5,5).iterator.groupWhen(_ == _).toList 

cung cấp: List(List(1), List(2, 2), List(3, 3, 3), List(4), List(5, 5))
Nếu bạn muốn lọc ra các yếu tố duy nhất, chỉ cần áp dụng một filter hoặc withFilter sau groupWhen

Stream.continually(Random.nextInt(100)).iterator 
     .groupWhen(_ + _ == 100).withFilter(_.length > 1).take(3).toList 

cho: List(List(34, 66), List(87, 13), List(97, 3))

2

Giải pháp đơn giản nhất tôi tìm thấy:

val it = List(1,1,1,1,2,2,2).iterator 
val (r1, it2) = it.span(_ == 1) 

println(s"group taken is: ${r1.toList}\n rest is: ${it2.toList}") 

đầu ra:

group taken is: List(1, 1, 1, 1) 
rest is: List(2, 2, 2) 

Rất ngắn nhưng bạn phải sử dụng trình lặp mới.

Với bất kỳ bộ sưu tập bất biến nó sẽ tương tự như:

  • sử dụng takeWhile khi bạn muốn chỉ có một số tiền tố của bộ sưu tập,
  • tuổi sử dụng khi bạn cần nghỉ ngơi cũng có.