Scalaイテレータを折りたたみ、結果として遅延評価されたシーケンスを取得する方法は?


8

文字列のイテレータがあり、各文字列は"H"(ヘッダー)または"D"(詳細)のいずれかです。このイテレータをブロックに分割します。各ブロックは1つのヘッダーで始まり、0から多くの詳細を指定できます。

私はすべてをメモリにロードするこの問題を解決する方法を知っています。たとえば、次のコードは次のとおりです。

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

5ブロックを返します-- List(List(H, D, D, D), List(H, D), List(H), List(H, D, D), List(H, D))私が欲しいものです。

ただし、List[List[String]]結果ではなくIterator[List[String]]、結果を遅延評価できる他の構造が必要であり、イテレータ全体が消費された場合に入力全体をメモリにロードせず、消費されているブロックのみをメモリにロードしたい一度に(例:を呼び出すときiterator.next)。

上記のコードを変更して、必要な結果を得るにはどうすればよいですか?

編集:私が使用する環境はそれに固執するので、私は特にこれをScala 2.11で必要とします。他のバージョンの回答も受け入れてくれてうれしいです。


この部分を理解するのに問題がありますイテレータ全体が消費されている場合は、リスト全体をメモリにロードしません。これは、プログラムがすべての要素をすでに調査したことを意味しませんか?アルゴリズムの結果が何らかの方法(メモリまたはディスク上)に格納されていない場合、リストをもう一度繰り返す以外に、アルゴリズムを取得する方法がないようです。
jrook

これで私が意味したことは、戻り値またはそのように動作するものとしてイテレータがあることを期待しています。たとえば、私が言われたこと(私は間違っているかもしれません)によると、ストリームはすでに消費されたすべての要素をメモリに保持しますね?2回は使いたくないが、ブロックを使いたい。
mvallebr

2
詳細を明確にするために質問を編集しましたが、今は明確になっていると思いますが、それ以外の場合はお知らせください。
mvallebr

1
私の答えはうまくいきますか?
Scalway

1
スライドせずに命題を追加しました。それは少し長く、追加のタイプ制限がありますが、まだ効率的ではないかもしれません。良い一日をお過ごしください:)
Scalway

回答:


5

ここに私が見つけることができる最も簡単な実装があります(それは一般的で面倒です):

/** 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
  }
}

そのように使用してください:

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

これが仕様です:

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: https

(回答が役に立った場合は賛成投票してください。少し時間をかけすぎました:))

2番目の実装:

使用しない バージョン提案していますsliding。ここにありますが、以下に示す独自の問題があります。

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

特徴:

  • (-)のみ機能します T>:Nullタイプます。最後のコレクションを最後に閉じる要素を追加するだけです(nullは完璧ですが、タイプが制限されます)。
  • (〜)以前のバージョンと同じ量のtrshを作成する必要があります。2番目のタプルではなく、最初のステップでタプルを作成するだけです。
  • (+)リストの長さをチェックしません(これは正直に言うと大きな利益です)。
  • (+)コアでは、Ivan Kurchenkoの回答ですが、余分なボクシングはありません。

これがscalafiddleです:https ://scalafiddle.io/sf/q8xbQ9N/11


おい...それは美しい...命令型プログラミングで非常に簡単にできることが関数型パラダイムで私にとってどれほど難しいのか、私は驚きました。しかし、今答えを見ると、明白で理解しやすいようです。スライド部分はトリッキーでした-長さが変更されたかどうかを確認します。これは、このユースケースに固有のものです...しかし、おそらくそこでもう一度「startGroup」を確認できたでしょう。b.headがグループの開始である場合、収集できます...
mvallebr

今振り返ると、上のスライドが本当に必要ですか?最良の答えは、あなたのものと上記のイワンのものの組み合わせだと思います... scanLeft長さをチェックせずに、直接収集してstartGroupを一度だけ呼び出すことができます。これまで解決できなかったことは印象的です。あなたの答えのおかげで、可能な最適化を見ることができます。ありがとう!
mvallebr

6

Scala 2.13.xを使用している場合はIterator、元のを展開して新しいものを作成できます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)
  }
}

テスト:

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、私はEMR制限のためにscala 2.11に固執する必要があることを言及するのを忘れていました...私は質問を編集しますが、回答を
賛成し

また、nit:itr.headを使用しました-バッファ付きイテレータですよね?
mvallebr

2

scanLeftこの場合、Scala 2.11バージョンを使用したい場合は、操作が役立つと思います。

次の解決策を考えたいのですが、元の解決策よりも複雑に見えます。

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(", "))
  }

読みやすさなどのために追加されたタイプ。この助けを願っています!


1
この答えはおそらくもっと教訓的で、私もそれを受け入れたいと思います。しかし、Scalwayの回答の方が投票数が多かったので、私はそれを最大限に受け入れますが、この回答にも本当に感謝しています。非常に役に立ち、賛成しました!
mvallebr

1
@mvallebr確かに、あなたは好きなように自由に選択できます。そして、私は解決策がより良く見えることに同意します。あなたの注意と賛成に感謝します!
Ivan Kurchenko
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.