Làm thế nào để gấp một trình lặp Scala và nhận được một chuỗi đánh giá lười biếng như là kết quả?


8

Tôi có một trình vòng lặp của chuỗi, trong đó mỗi chuỗi có thể là "H"(tiêu đề) hoặc "D"(chi tiết). Tôi muốn chia iterator này thành các khối, trong đó mỗi khối bắt đầu bằng một tiêu đề và có thể có 0 đến nhiều chi tiết.

Tôi biết làm thế nào để giải quyết vấn đề này tải mọi thứ vào bộ nhớ. Ví dụ, mã dưới đây:

Seq("H","D","D","D","H","D","H","H","D","D","H","D").toIterator
  .foldLeft(List[List[String]]())((acc, x) => x match {
    case "H" => List(x) :: acc
    case "D" => (x :: acc.head) :: acc.tail })
  .map(_.reverse)
  .reverse

trả về 5 khối - List(List(H, D, D, D), List(H, D), List(H), List(H, D, D), List(H, D))- đó là những gì tôi muốn.

Tuy nhiên, thay vì List[List[String]]trong kết quả, tôi muốn một Iterator[List[String]]hoặc một số cấu trúc khác cho phép tôi đánh giá kết quả một cách lười biếng và không tải toàn bộ đầu vào vào bộ nhớ nếu toàn bộ iterator bị tiêu thụ , tôi chỉ muốn tải vào bộ nhớ tại một thời điểm (ví dụ: khi tôi gọi iterator.next).

Làm cách nào để sửa đổi mã ở trên để đạt được kết quả tôi muốn?

EDIT: Tôi cần điều này trong Scala 2.11, vì môi trường tôi sử dụng dính vào nó. Vui mừng cũng chấp nhận câu trả lời cho các phiên bản khác mặc dù.


Tôi gặp khó khăn khi hiểu phần này: và không tải toàn bộ danh sách vào bộ nhớ nếu toàn bộ trình lặp được tiêu thụ . Điều này không có nghĩa là chương trình đã kiểm tra tất cả các yếu tố rồi sao? Nếu kết quả của thuật toán không được lưu trữ theo một cách nào đó (trong bộ nhớ hoặc trên đĩa), thì dường như không có cách nào để truy xuất nó ngoại trừ lặp lại danh sách.
jrook

Điều tôi muốn nói với điều này là tôi hy vọng sẽ có một iterator như một sự trở lại hoặc một cái gì đó hoạt động giống như nó. Ví dụ, một luồng, theo những gì tôi đã nói (tôi có thể sai) sẽ giữ trong bộ nhớ tất cả các yếu tố đã tiêu thụ, phải không? Tôi không muốn tiêu thụ hai lần, nhưng tôi muốn tiêu thụ các khối.
mvallebr

2
Tôi đã chỉnh sửa câu hỏi để làm rõ hơn, hy vọng nó đã rõ ràng, nếu không hãy cho tôi biết.
mvallebr

1
Là câu trả lời của tôi làm việc cho bạn?
Đường cao tốc

1
Tôi đã thêm đề xuất mà không trượt. Nó dài hơn một chút và có giới hạn loại bổ sung nhưng có thể hiệu quả hơn, chưa chắc chắn. Chúc một ngày tốt lành :)
Scalway

Câu trả lời:


5

Đây là cách thực hiện đơn giản nhất mà tôi có thể tìm thấy (Đó là chung chung và lười biếng):

/** takes 'it' and groups consecutive elements 
 *  until next item that satisfy 'startGroup' predicate occures. 
 *  It returns Iterator[List[T]] and is lazy 
 *  (keeps in memory only last group, not whole 'it'). 
*/
def groupUsing[T](it:Iterator[T])(startGroup:T => Boolean):Iterator[List[T]] = {
  val sc = it.scanLeft(List.empty[T]) {
    (a,b) => if (startGroup(b)) b::Nil else b::a
  }

  (sc ++ Iterator(Nil)).sliding(2,1).collect { 
    case Seq(a,b) if a.length >= b.length => a.reverse
  }
}

sử dụng nó như thế:

val exampleIt = Seq("H1","D1","D2","D3","H2","D4","H3","H4","D5","D6","H5","D7").toIterator
groupUsing(exampleIt)(_.startsWith("H"))
// H1 D1 D2 D3 / H2 D4 / H3 / H4 D5 D6 / H5 D7

đây là thông số kỹ thuật:

X | GIVEN            | EXPECTED     |
O |                  |              | empty iterator
O | H                | H            | single header
O | D                | D            | single item (not header)
O | HD               | HD           |
O | HH               | H,H          | only headers
O | HHD              | H,HD         |
O | HDDDHD           | HDDD,HD      |
O | DDH              | DD,H         | heading D's have no Header as you can see.
O | HDDDHDHDD        | HDDD,HD,HDD  |

scalafiddle với các bài kiểm tra và nhận xét bổ sung: https://scalafiddle.io/sf/q8xbQ9N/11

(nếu câu trả lời là hữu ích, hãy bỏ phiếu. Tôi đã dành quá nhiều thời gian cho nó :))

THỰC HIỆN THỨ HAI:

Bạn đã đề xuất phiên bản không sử dụng sliding . Đây là nó, nhưng nó có vấn đề riêng của nó được liệt kê dưới đây.

def groupUsing2[T >: Null](it:Iterator[T])(startGroup:T => Boolean):Iterator[List[T]] = {
  type TT = (List[T], List[T])
  val empty:TT = (Nil, Nil)
  //We need this ugly `++ Iterator(null)` to close last group.
  val sc = (it ++ Iterator(null)).scanLeft(empty) {
    (a,b) => if (b == null || startGroup(b)) (b::Nil, a._1) else (b::a._1, Nil)
  }

  sc.collect { 
    case (_, a) if a.nonEmpty => a.reverse
  }
}

Đặc điểm:

  • (-) Nó chỉ hoạt động cho T>:Nullcác loại. Chúng ta chỉ cần thêm phần tử sẽ đóng bộ sưu tập cuối cùng vào cuối (null là hoàn hảo nhưng nó giới hạn loại của chúng tôi).
  • (~) nó sẽ tạo ra cùng một lượng trsh như phiên bản trước. Chúng tôi chỉ tạo bộ dữ liệu trong bước đầu tiên thay vì thứ hai.
  • (+) nó không kiểm tra độ dài của Danh sách (và đây là mức tăng lớn phải trung thực).
  • (+) Trong cốt lõi, đó là câu trả lời của Ivan Kurchenko nhưng không có thêm quyền anh.

Đây là scalafiddle: https://scalafiddle.io/sf/q8xbQ9N/11


Anh bạn ... Thật đẹp ... Tôi đã ngạc nhiên khi một thứ rất dễ làm trong lập trình mệnh lệnh lại có thể khó đến thế trong mô hình chức năng. Nhưng nhìn vào câu trả lời bây giờ, nó có vẻ rõ ràng và quá dễ hiểu. Phần trượt rất khó khăn - bạn kiểm tra xem độ dài có thay đổi hay không, đó là điều cụ thể cho trường hợp sử dụng này ... Nhưng có lẽ bạn có thể đã kiểm tra lại "startgroup" ở đó không? Nếu b.head là sự khởi đầu của một nhóm, bạn có thể thu thập ...
mvallebr

Nghĩ lại bây giờ, bạn có thực sự cần trượt ở trên không? Tôi nghĩ rằng câu trả lời tốt nhất sẽ là sự kết hợp của bạn và Ivan ở trên ... Bạn có thể thu thập trực tiếp từ scanLeftvà gọi startgroup chỉ một lần mà không cần kiểm tra độ dài. Thật ấn tượng khi tôi không thể giải quyết nó trước đây và nhờ câu trả lời của bạn bây giờ tôi thậm chí có thể thấy các tối ưu hóa có thể. Cảm ơn!
mvallebr

6

Nếu bạn đang sử dụng Scala 2.13.x thì bạn có thể tạo mới Iteratorbằng cách mở ra bản gốc Iterator.

import scala.collection.mutable.ListBuffer

val data = Seq("H","D","D","D","H","D","H","H","D","D","H","D").iterator

val rslt = Iterator.unfold(data.buffered){itr =>
  Option.when(itr.hasNext) {
    val lb = ListBuffer(itr.next())
    while (itr.hasNext && itr.head == "D")
      lb += itr.next()
    (lb.toList, itr)
  }
}

thử nghiệm:

rslt.next()   //res0: List[String] = List(H, D, D, D)
rslt.next()   //res1: List[String] = List(H, D)
rslt.next()   //res2: List[String] = List(H)
rslt.next()   //res3: List[String] = List(H, D, D)
rslt.next()   //res4: List[String] = List(H, D)
rslt.hasNext  //res5: Boolean = false

ufff, tôi đã quên đề cập đến việc tôi phải dính vào scala 2.11 do hạn chế EMR ... Tôi sẽ chỉnh sửa câu hỏi, nhưng nêu lên câu trả lời, cảm ơn ...
mvallebr

Ngoài ra, nit: bạn đã sử dụng itr.head - vì vậy đó là một trình vòng lặp được đệm, phải không?
mvallebr

2

Tôi nghĩ rằng scanLefthoạt động có thể giúp đỡ trong trường hợp này, nếu bạn muốn sử dụng phiên bản Scala 2.11.

Tôi muốn đưa ra giải pháp tiếp theo, nhưng tôi sợ nó có vẻ phức tạp hơn so với giải pháp ban đầu:

def main(args: Array[String]): Unit = {
    sealed trait SequenceItem
    case class SequenceSymbol(value: String) extends SequenceItem
    case object Termination extends SequenceItem

    /**
      * _1 - HD sequence in progress
      * _2 - HD sequences which is ready
      */
    type ScanResult = (List[String], List[String])
    val init: ScanResult = Nil -> Nil

    val originalIterator: Iterator[SequenceItem] = Seq("H","D","D","D", "H","D", "H", "H","D","D", "H","D")
      .toIterator.map(SequenceSymbol)

    val iteratorWithTermination: Iterator[SequenceItem] = originalIterator ++ Seq(Termination).toIterator
    val result: Iterator[List[String]] = iteratorWithTermination
      .scanLeft(init) {
        case ((progress, _), SequenceSymbol("H")) =>  List("H") -> progress
        case ((progress, _), SequenceSymbol("D")) => ("D" :: progress) -> Nil
        case ((progress, _), Termination) => Nil -> progress
      }
      .collect {
        case (_, ready) if ready.nonEmpty => ready
      }
      .map(_.reverse)

    println(result.mkString(", "))
  }

Các loại được thêm vào ví dụ dễ đọc. Hy vọng điều này giúp đỡ!


1
Câu trả lời này có lẽ mang tính mô phạm hơn và tôi cũng sẽ vui vẻ chấp nhận nó. Tuy nhiên, vì câu trả lời của Scalway nhận được nhiều phiếu bầu hơn, tôi sẽ chấp nhận nó tốt nhất, nhưng tôi thực sự biết ơn vì câu trả lời này, nó rất hữu ích và tôi đã ủng hộ nó!
mvallebr

1
@mvallebr Chắc chắn, bạn có thể tự do lựa chọn, những gì bạn thích và tôi đồng ý rằng giải pháp đó có vẻ tốt hơn. Tôi đánh giá cao sự chú ý của bạn và upvote!
Ivan Kurchenko
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.