Scala使用惰性集合处理大型Scala数据的函数式方法

Scala使用惰性集合处理大型Scala数据的函数式方法,scala,memory,collections,lazy-evaluation,fasta,Scala,Memory,Collections,Lazy Evaluation,Fasta,我正试图找出在scala中使用字符串处理大规模数据的高效内存和功能性方法。我读过很多关于惰性集合的文章,也看过很多代码示例。然而,我一次又一次地遇到“超出GC开销”或“Java堆空间”问题 通常问题是,我试图构造一个惰性集合,但在将每个新元素附加到不断增长的集合中时对其进行求值(我现在没有任何其他方法以增量方式进行求值)。当然,我可以先初始化一个初始的惰性集合,然后通过使用map等应用ressource关键计算来生成包含所需值的集合,但通常我只是在初始化该惰性集合之前不知道最终集合的确切大小 也

我正试图找出在scala中使用字符串处理大规模数据的高效内存和功能性方法。我读过很多关于惰性集合的文章,也看过很多代码示例。然而,我一次又一次地遇到“超出GC开销”或“Java堆空间”问题

通常问题是,我试图构造一个惰性集合,但在将每个新元素附加到不断增长的集合中时对其进行求值(我现在没有任何其他方法以增量方式进行求值)。当然,我可以先初始化一个初始的惰性集合,然后通过使用map等应用ressource关键计算来生成包含所需值的集合,但通常我只是在初始化该惰性集合之前不知道最终集合的确切大小

也许你可以帮助我,给我一些提示或解释,告诉我如何改进下面的代码,作为一个例子,它根据奇数序列对属于一个文件,偶数序列对属于另一个文件的规则(“链的分离”)将一个FASTA(定义如下)格式的文件拆分为两个单独的文件。“最”直接的方法是通过循环行并通过打开的文件流打印到相应的文件中(这当然非常有效)。但是,我不喜欢重新分配给包含头和序列的变量的风格,因此下面的示例代码使用(尾)递归,我希望找到一种方法来维护类似的设计,而不会遇到ressource问题

该示例非常适用于小文件,但对于大约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
方法一样(注意
的大小不同,以及
getFullSeqs
的输出),因为需要重复转换整个(!)数据,因为人们不知道在任何步骤中需要哪部分数据。我的例子可能不是最好的,但如何做到这一点呢?有可能吗

B) 当所需的数据结构不是固有的惰性时,比如说希望将
(header->sequence)
对存储到
映射()
?你会把它包装成一个懒散的集合吗

C) 我构造流的实现可能会颠倒输入行的顺序。调用reverse时,将计算所有元素(在我的代码中,它们已经计算过了,所以这是实际问题)。有没有办法以一种懒散的方式“从后面”进行后期处理?我知道<代码>反转器,但这已经是解决方案了吗,或者这实际上也不会首先计算所有元素(因为我需要在列表中调用它)?可以用
newVal::rec(…)
构造流,但是我会失去尾部递归,不是吗

所以我基本上需要的是向集合中添加元素,这些元素不是通过添加过程来计算的。所以
lazy val elem=“test”;elem::lazyCollection
不是我要找的

编辑:我还尝试在
rec
中使用by name参数作为流参数

非常感谢您的关注和时间,我真的非常感谢您的帮助(再次:)

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

FASTA被定义为由单个标题行分隔的序列序列集。标题定义为以“>”开头的行。标题下的每一行称为与标题关联的序列的一部分。当出现新的标头时,序列结束。每个标题都是唯一的。例如:

>校长1
abcdefg
>校长2
hijklmn
opqrstu
>校长3
vwxyz
>校长4
zyxwv

因此,序列2是序列1的两倍大。我的程序会将该文件拆分为一个包含

>校长1
abcdefg
>校长3
vwxyz

第二个文件B包含

>校长2
hijklmn
opqrstu
>校长4
zyxwv


假设输入文件由偶数个头序列对组成。

处理真正大型数据结构的关键是只在内存中保存对执行所需操作至关重要的内容。所以,在你的情况下,这是

  • 您的输入文件
  • 您的两个输出文件
  • 当前文本行
就这样。在某些情况下,您可能需要存储诸如序列长度等信息;在这种情况下,您将在第一次过程中构建数据结构,并在第二次过程中使用它们。例如,假设您决定要编写三个文件:一个用于偶数记录,一个用于奇数记录,另一个用于总长度小于300个核苷酸的条目。您可能会执行类似的操作(警告——它可以编译,但我从未运行过,因此可能无法实际运行):

然后,为了进行处理,您使用该地图并再次通过:

import java.io._
final def writeFiles(
  source: Iterator[String], targets: Array[PrintWriter],
  sizes: Map[String,Long], count: Int = -1, which: Int = 0
) {
  if (!source.hasNext) targets.foreach(_.close)
  else {
    val s = source.next
    if (s(0) == '>') {
      val w = if (sizes.get(s).exists(_ < 300)) 2 else (count+1)%2
      targets(w).println(s)
      writeFiles(source, targets, sizes, count+1, w)
    }
    else {
      targets(which).println(s)
      writeFiles(source, targets, sizes, count, which)
    }
  }
}
导入java.io_
最终def写入文件(
源:迭代器[String],目标:数组[PrintWriter],
大小:Map[String,Long],count:Int=-1,其中:Int=0
) {
如果(!source.hasNext)targets.foreach(u.close)
否则{
val s=source.next
如果(s(0)='>'){
val w=如果(size.get.s.存在(<300))2其他(计数+1)%2
目标(w)。打印项次(s)
写入文件(源、目标、大小、计数+1、w)
}
否则{
目标(哪个)。打印项次(个)
WriteFile(源、目标、大小、计数等)
}
}
}
然后使用
Source.fromFile(f).getLines()
两次
import java.io._
final def writeFiles(
  source: Iterator[String], targets: Array[PrintWriter],
  sizes: Map[String,Long], count: Int = -1, which: Int = 0
) {
  if (!source.hasNext) targets.foreach(_.close)
  else {
    val s = source.next
    if (s(0) == '>') {
      val w = if (sizes.get(s).exists(_ < 300)) 2 else (count+1)%2
      targets(w).println(s)
      writeFiles(source, targets, sizes, count+1, w)
    }
    else {
      targets(which).println(s)
      writeFiles(source, targets, sizes, count, which)
    }
  }
}
def inputStreams(fileName: String): (Stream[String], Stream[String]) = {
  val in = (io.Source fromFile fileName).getLines.toStream
  val SeqPatt = "^[^>]".r
  def demultiplex(s: Stream[String], skip: Boolean): Stream[String] = {
    if (s.isEmpty) Stream.empty
    else if (skip) demultiplex(s.tail dropWhile (SeqPatt findFirstIn _ nonEmpty), skip = false)
         else s.head #:: (s.tail takeWhile (SeqPatt findFirstIn _ nonEmpty)) #::: demultiplex(s.tail dropWhile (SeqPatt findFirstIn _ nonEmpty), skip = true)
  }
  (demultiplex(in, skip = false), demultiplex(in, skip = true))
}
val (a, b) = inputStreams(fileName)
def inputStreams(fileName: String): Vector[Stream[String]] = {
  val in = (io.Source fromFile fileName).getLines.toStream
  val SeqPatt = "^[^>]".r
  def demultiplex(s: Stream[String], skip: Boolean): Stream[String] = {
    if (s.isEmpty) Stream.empty
    else if (skip) demultiplex(s.tail dropWhile (SeqPatt findFirstIn _ nonEmpty), skip = false)
         else s.head #:: (s.tail takeWhile (SeqPatt findFirstIn _ nonEmpty)) #::: demultiplex(s.tail dropWhile (SeqPatt findFirstIn _ nonEmpty), skip = true)
  }
  Vector(demultiplex(in, skip = false), demultiplex(in, skip = true))
}

inputStreams(fileName).zipWithIndex.par.foreach { 
  case (stream, strand) => 
    val p = new PrintWriter(new File("FASTA" + "." + strand))
    stream foreach p.println
    p.close
}
def in = (scala.io.Source fromFile fileName).getLines.toStream
def inputStream(in: Stream[String], strand: Int = 1): Stream[(String, Int)] = {
  if (in.isEmpty) Stream.empty
  else if (in.head startsWith ">") (in.head, 1 - strand) #:: inputStream(in.tail, 1 - strand)
       else                        (in.head, strand) #:: inputStream(in.tail, strand)
}
val printers = Array.tabulate(2)(i => new PrintWriter(new File("FASTA" + "." + i)))
inputStream(in) foreach {
  case (line, strand) => printers(strand) println line
}
printers foreach (_.close)
def in = (scala.io.Source fromFile fileName).getLines
val printers = Array.tabulate(2)(i => new PrintWriter(new File("FASTA" + "." + i)))
def printStrands(in: Iterator[String], strand: Int = 1) {
  if (in.hasNext) {
    val next = in.next
    if (next startsWith ">") { 
      printers(1 - strand).println(next)
      printStrands(in, 1 - strand)
    } else {
      printers(strand).println(next)
      printStrands(in, strand)
    }
  }
}
printStrands(in)
printers foreach (_.close)