私は、scala で文字列を使用して大規模なデータを処理するための、メモリ効率が高く機能的な方法を見つけようとしています。私は遅延コレクションについて多くのことを読み、かなりの数のコード例を見てきました。ただし、「GC オーバーヘッドの超過」または「Java ヒープ領域」の問題が何度も発生します。
多くの場合、問題は遅延コレクションを構築しようとすることですが、成長するコレクションに追加するときに新しい要素をそれぞれ評価します (これを段階的に行う方法は他にありません)。もちろん、最初に初期遅延コレクションを初期化するようなことを試して、マップなどを使用してリソースクリティカルな計算を適用することで、目的の値を保持するコレクションを生成することもできますが、最終的なコレクションの正確なサイズがわからないことがよくあります。その怠惰なコレクションを初期化するアプリオリ。
奇数シーケンスのペアが 1 つのファイルに属し、偶数シーケンスのペアが 1 つのファイルに属するという規則に従って、FASTA (以下の定義) 形式のファイルを 2 つの個別のファイルに分割する例として、次のコードを改善する方法についてのヒントや説明を教えてください。別のものに(「ストランドの分離」)。これを行うための「最も」簡単な方法は、行をループし、開いているファイルストリームを介して対応するファイルに出力するという命令的な方法です (もちろん、これはうまく機能します)。ただし、ヘッダーとシーケンスを保持する変数に再割り当てするスタイルが好きではないため、次のコード例では (末尾) 再帰を使用しています。リソースの問題に遭遇することなく同様の設計を維持する方法を見つけていただければ幸いです。 !
この例は小さなファイルに対しては完全に機能しますが、約 500 MB のファイルでは、標準の JVM セットアップではコードが失敗します。「任意の」サイズ、たとえば 10 ~ 20 GB 程度のファイルを処理したい。
val fileName = args(0)
val in = io.Source.fromFile(fileName) getLines
type itType = Iterator[String]
type sType = Stream[(String, String)]
def getFullSeqs(ite: itType) = {
//val metaChar = ">"
val HeadPatt = "(^>)(.+)" r
val SeqPatt = "([\\w\\W]+)" r
@annotation.tailrec
def rec(it: itType, out: sType = Stream[(String, String)]()): sType =
if (it hasNext) it next match {
case HeadPatt(_,header) =>
// introduce new header-sequence pair
rec(it, (header, "") #:: out)
case SeqPatt(seq) =>
val oldVal = out head
// concat subsequences
val newStream = (oldVal._1, oldVal._2 + seq) #:: out.tail
rec(it, newStream)
case _ =>
println("something went wrong my friend, oh oh oh!"); Stream[(String, String)]()
} else out
rec(ite)
}
def printStrands(seqs: sType) {
import java.io.PrintWriter
import java.io.File
def printStrand(seqse: sType, strand: Int) {
// only use sequences of one strand
val indices = List.tabulate(seqs.size/2)(_*2 + strand - 1).view
val p = new PrintWriter(new File(fileName + "." + strand))
indices foreach { i =>
p.print(">" + seqse(i)._1 + "\n" + seqse(i)._2 + "\n")
}; p.close
println("Done bro!")
}
List(1,2).par foreach (s => printStrand(seqs, s))
}
printStrands(getFullSeqs(in))
3 つの疑問が生じます。
getLines
A)私の方法のように取得した初期イテレータを処理することによって得られる大きなデータ構造を維持する必要があると仮定しましょう( のサイズと の出力がgetFullSeqs
異なることに注意してください)。これは、(!) データ全体の変換が繰り返し必要になるためです。どの段階でもデータのどの部分が必要になるか分からないからです。私の例は最善ではないかもしれませんが、どうすればよいでしょうか? そもそも可能ですか??in
getFullSeqs
(header -> sequence)
B) 必要なデータ構造が本質的に遅延していない場合、ペアをに格納したいとしMap()
ます。怠惰なコレクションでラップしますか?
C) ストリームを構築する私の実装は、入力された行の順序を逆にする可能性があります。reverse を呼び出すと、すべての要素が評価されます (私のコードでは既に評価されているため、これが実際の問題です)。怠惰な方法で「後ろから」後処理する方法はありますか? 私は知っていますがreverseIterator
、これはすでに解決策ですか、それとも実際には最初にすべての要素を評価するわけではありません(リストで呼び出す必要があるため)? でストリームを構築することもできますがnewVal #:: rec(...)
、そうすると末尾再帰が失われてしまいますね。
したがって、基本的に必要なのは、追加のプロセスによって評価されない要素をコレクションに追加することです。だからlazy val elem = "test"; elem :: lazyCollection
私が探しているものではありません。
編集: のストリーム引数に by-name パラメータを使用してみましたrec
。
ご清聴ありがとうございました。
/////////////////////////////////////////////// /////////////////////////////////////////////// /////////////////////////////////////////////// ///////////////
FASTA は、1 つのヘッダー行で区切られたシーケンスの連続セットとして定義されます。ヘッダーは、">" で始まる行として定義されます。ヘッダーの下のすべての行は、ヘッダーに関連付けられたシーケンスの一部と呼ばれます。新しいヘッダーが存在する場合、シーケンスは終了します。すべてのヘッダーは一意です。例:
>HEADER1
abcdefg
>HEADER2
hijklmn
opqrstu
>HEADER3
vwxyz
>HEADER4
zyxwv
したがって、シーケンス 2 はシーケンス 1 の 2 倍の大きさです。私のプログラムは、そのファイルをファイル A に分割します。
>HEADER1
abcdefg
>HEADER3
vwxyz
を含む 2 番目のファイル B
>HEADER2
hijklmn
opqrstu
>HEADER4
zyxwv
入力ファイルは、偶数のヘッダーとシーケンスのペアで構成されていると想定されます。