我有一个大文本文件,每行有2个数字,用于表示行中第一个和第二个元素之间的有向边。我试图通过将其表示为Map[tailOfEdge,ArrayofHeadsOfEdges]
如果我的文件有
1 2
1 3
2 3
这应该是Map(1-> Array(2,3),2-> Array(3))
但是,我的文件非常大(约500万行)
我最初尝试阅读整个文件,使用toArray
然后使用groupBy
并以此方式累积。但是,我一直遇到堆大小问题(更不用说他可能是一个非常天真的方式)
现在,对我有用的东西(虽然速度极慢)是创建一个可变映射,循环遍历文件的每一行(带有for循环),将该行拆分为2个数字。给定节点的所有边缘在文件中是有余的,所以我只是跟踪我期望的节点,如果它是同一个节点,我累积新边缘,如果它是一个新节点,那么我添加完成的累积数组到地图,重置我期望的节点,并用这个新列表重新启动累积数组。
肯定有更好的方法可以做到这一点......
答案 0 :(得分:3)
您可以使用左侧折叠和不可变映射非常干净地完成此操作:
val source = scala.io.Source.fromFile(args(0))
val graph = source.getLines.foldLeft[Map[Int, Vector[Int]]](
Map.empty withDefaultValue Vector.empty
) {
case (acc, line) => line.trim.split("\\s+").map(_.toInt) match {
case Array(k, v) => acc.updated(k, acc(k) :+ v)
}
}
source.close()
在我的机器上,在大约七秒内在一个包含五百万行的文件上运行。 getLines
是一个迭代器,因此您无需将整个文件读入内存。
我不确定“非常慢”对你来说意味着什么。这个实现不会对文件中的键顺序做出任何假设,如果你真的需要它比一秒钟的行速度快一百,你应该能够利用它们被订购的事实。但它可能无济于事,几乎肯定会涉及更复杂的代码。
你也可以使用数组而不是矢量 - 我在这里使用向量来表明你甚至不需要头部列表是可变的。
答案 1 :(得分:0)
如果您的输入序列非常大,那么另一个解决方案最终将是OOME。这是我的必要解决方案,它依赖于调用者在生成组时干净地处理组,但AFAICT以常量堆栈运行并保留最小堆以供自己使用。 :)
希望其他人能够在Stream上或某些具有类似性能特征的情况下提出折叠,只要您小心不要保留对头部的引用。
/**
* @param in the input
* @param disposal a function that will dispose of groups as they're identified
*/
def groupByInfinite[A,B](in: Iterator[(A,B)])(disposal: (A,Seq[B]) => Unit) {
/**
* @param in the input
* @param current the current A value
* @param got the B values being accumulated for the current A value
*/
@tailrec
def group0(in: Iterator[(A,B)], current: A, got: Seq[B]) {
if (in.hasNext) {
val (a,b) = in.next()
if (a == current) {
group0(in, a, got :+ b)
} else {
disposal(current, got)
group0(in, a, Vector(b))
}
} else {
disposal(current, got)
}
}
if (in.hasNext) {
val (a,b) = in.next()
group0(in, a, Vector(b))
}
}