我正在尝试找出使用 scala 中的字符串来处理大量数据的高效内存和功能方法。我读过很多关于惰性集合的东西,也看过很多代码示例。但是,我一次又一次地遇到“超出 GC 开销”或“Java 堆空间”问题。
通常问题是我尝试构建一个惰性集合,但是当我将每个新元素附加到不断增长的集合时评估它(我现在没有任何其他方式来增量这样做)。当然,我可以尝试先初始化一个初始惰性集合,然后通过使用 map 左右应用资源关键计算来生成包含所需值的集合,但通常我只是不知道最终集合的确切大小先验地初始化该惰性集合。
也许您可以通过给我提示或解释如何改进以下代码作为示例来帮助我,该代码根据奇数序列对属于一个文件和偶数序列对属于一个文件的规则将 FASTA(以下定义)格式的文件拆分为两个单独的文件到另一个(“股线分离”)。“最”直接的方法是通过循环遍历行并通过打开的文件流打印到相应的文件中的命令方式(这当然效果很好)。但是,我只是不喜欢重新分配给包含标题和序列的变量的风格,因此下面的示例代码使用(尾)递归,我希望找到一种方法来维护类似的设计而不会遇到资源问题!
该示例非常适用于小文件,但是对于大约 500mb 左右的文件,代码将在标准 JVM 设置中失败。我确实想处理“任意”大小的文件,比如 10-20gb 左右。
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))
我提出了三个问题:
A)假设需要维护一个大型数据结构,该结构是通过处理您getLines
在我的getFullSeqs
方法中获得的初始迭代器获得的(注意 的不同大小in
和输出getFullSeqs
),因为需要重复对整个(!)数据进行转换,因为人们不知道在任何步骤中都需要哪一部分数据。我的例子可能不是最好的,但是怎么做呢?有可能吗?
B)当所需的数据结构不是天生懒惰的时候,比如说想要将这些(header -> sequence)
对存储到 a 中Map()
怎么办?你会把它包装在一个惰性集合中吗?
C)我构建流的实现可能会颠倒输入行的顺序。调用 reverse 时,将评估所有元素(在我的代码中,它们已经是,所以这是实际问题)。有没有办法以懒惰的方式“从后面”进行后期处理?我知道reverseIterator
,但这已经是解决方案了吗,或者这实际上也不会首先评估所有元素(因为我需要在列表中调用它)?可以用 构造流newVal #:: rec(...)
,但那样我会失去尾递归,不是吗?
所以我基本上需要的是将元素添加到集合中,而不是通过添加过程来评估。所以lazy val elem = "test"; elem :: lazyCollection
不是我要找的。
编辑:我也尝试在rec
.
非常感谢您的关注和时间,我非常感谢您的帮助(再次:))。
///////////////////////////////////////// ///////////////////////////////////////// ///////////////////////////////////////// ///////////////
FASTA 被定义为由单个标题行分隔的一组顺序序列。标题定义为以“>”开头的行。标题下方的每一行都称为与标题相关的序列的一部分。当出现新标头时,序列结束。每个标题都是唯一的。例子:
>HEADER1
abcdefg
>HEADER2
hijklmn
opqrstu
>HEADER3
vwxyz
>HEADER4
zyxwv
因此,序列 2 是 seq 1 的两倍。我的程序会将该文件拆分为一个文件 A,其中包含
>HEADER1
abcdefg
>HEADER3
vwxyz
和第二个文件 B 包含
>HEADER2
hijklmn
opqrstu
>HEADER4
zyxwv
假设输入文件由偶数个头序列对组成。