Scala:基于列的聚合文件

时间:2014-04-17 00:29:39

标签: scala

我有一个巨大的文件(不适合内存),它由两列(keyvalue)分隔,并在key列上预先排序。我需要为键的所有值调用一个函数并写出结果。为简单起见,可以假设值是数字,函数是加法。

所以,给出一个输入:

A 1
A 2
B 1
B 3

输出结果为:

A 3
B 4

对于这个问题,我对阅读/编写文件不是那么感兴趣,而是在列表理解方面更多。尽管整个内容(输入和输出)并不适合内存,但这很重要。我是Scala的新手,来自Java,我很感兴趣的是功能/ Scala方法。

更新

根据AmigoNico的评论,我提出了以下常量内存解决方案。 任何意见/改进都表示赞赏!

val writeAggr = (kv : (String, Int)) => {println(kv._1 + " " + kv._2)}
writeAggr( 
  (  ("", 0) /: scala.io.Source.fromFile("/tmp/xx").getLines ) { (keyAggr, line) => 
    val Array(k,v) = line split ' '
    if (keyAggr._1.equals(k)) {
      (k, keyAggr._2 + v.toInt) 
    } else { 
      if (!keyAggr._1.equals("")) {
        writeAggr(keyAggr)
      }
      (k, v.toInt)
    }
  }
)

2 个答案:

答案 0 :(得分:3)

这可以用Scalaz streams非常优雅地完成(与基于迭代器的解决方案不同,它“真正”有效):

import scalaz.stream._

val process =
  io.linesR("input.txt")
    .map { _.split("\\s") }
    .map { case Array(k, v) => k -> v.toInt }
    .pipe(process1.chunkBy2(_._1 == _._1))
    .map { kvs => s"${ kvs.head._1 } ${ kvs.map(_._2).sum }\n" }
    .pipe(text.utf8Encode)
    .to(io.fileChunkW("output.txt"))

这不仅会从输入中读取,聚合行,还会在常量内存中写入输出,但您也可以获得有关资源管理的良好保证,例如: source.getLines无法提供。

答案 1 :(得分:2)

你可能想要使用折叠,如下所示:

scala> ( ( Map[String,Int]() withDefaultValue 0 ) /: scala.io.Source.fromFile("/tmp/xx").getLines ) { (map,line) =>
  val Array(k,v) = line split ' '
  map + ( k -> ( map(k) + v.toInt ) )
} 
res12: scala.collection.immutable.Map[String,Int] = Map(A -> 3, B -> 4)

折叠对于积累结果非常有用(与理解不同)。由于getLines返回Iterator,因此一次只能在内存中保留一行。

更新:好的,有一个新要求我们也没有将结果保存在内存中。在那种情况下,我想我只是写一个递归函数并像这样使用它:

scala> val kvPairs = scala.io.Source.fromFile("/tmp/xx").getLines map { line =>
  val Array(k,v) = line split ' '
  ( k, v.toInt )
}
kvPairs: Iterator[(String, Int)] = non-empty iterator

scala> final def loop( key:String, soFar:Int ) {
  if ( kvPairs.hasNext ) {
    val (k,v) = kvPairs.next
    if ( k == key )
      loop( k, soFar+v )
    else {
      println( s"$key $soFar" )
      loop(k,v)
    }
  } else println( s"$key $soFar" )
}
loop: (key: String, soFar: Int)Unit

scala> val (k,v) = kvPairs.next
k: String = A
v: Int = 1

scala> loop(k,v)
A 3
B 4

但唯一有用的是它使用递归函数而不是循环。如果您可以在内存中保存特定键的所有值,那么您可以编写一个迭代文件行的函数,生成类似键对的迭代器迭代器,您可以然后只是求和并打印,但代码仍然没有特别的功能,而且速度会慢一些。

Travis的Scalaz管道解决方案在这些方面看起来很有趣,但迭代隐藏在一些方便的构造之后。如果您特别想要一个功能性解决方案,我会说他是最好的答案。