我有一个文件解析器,该解析器生成所有元素都属于同一特征的集合。类似于以下内容。
trait Data {
val identifier: String
}
case class Meta(identifier: String, props: Properties) extends Data
case class Complete(identifier: String, contents: Map[String, Any]) extends Data
case class Partial(identifier: String, name: String, value: Any) extends Data
...
def parse(file: File): Iterator[Data] = ... // this isn't relevant
由于我正在处理大量数据并希望尽可能地了解内存,因此我试图以一种功能性的方式遍历集合。从parse方法返回的集合是Complete
,Meta
和Partial
元素的混合。逻辑是,我需要将Complete
和Meta
元素传递给原样,同时收集Partial
元素并在identifier
上分组以创建Complete
元素
仅使用Partial
个元素(Iterator[Partial]
)的集合,我可以执行以下操作:
partialsOnly.groupBy(_.identifier)
.map{
case (ident, parts) =>
Complete(ident, parts.map(p => p.name -> p.value).toMap)
}
有没有一种类似于扫描的功能性方法,它可以累积元素,但仅累加一些元素,而其余元素保持不变?
答案 0 :(得分:2)
您可以使用partition
函数根据谓词将集合分为两部分。
val (partial: List[Data], completeAndMeta: List[Data]) = parse("file").partition(_ match{
case partial: Partial => true
case _ => false
})
从那里开始,您要确保可以将partial
作为List[Partial]
处理,理想情况下不会触发有关类型擦除或进行混乱的类型转换的编译器警告。您可以使用仅接受collect
的函数来调用Partial
。
val partials: List[Partial] = partial.collect(_.match{case partial: Partial => partial}}
不幸的是,在Iterator
上使用时,partition
可能需要缓冲任意数量的数据,因此不一定是最节省内存的技术。如果内存管理非常重要,则可能需要牺牲功能的纯度。或者,如果您添加某种方法来了解Partial
的完成时间,则可以通过Map
在foldLeft
中累积它们,并在完成时发出最终值。
答案 1 :(得分:0)
递归可能是解决问题的有效方法:
def parse(list: List[Data]): (List[Data], List[Data]) = {
list match {
case (x:Partial) :: xs =>
val (partials, rest) = parse(xs)
(x :: partials, rest) //instead of creating list, you can join partials here
case x :: xs =>
val (partials, rest) = parse(xs)
(partials, x :: rest)
case _ => (Nil, Nil)
}
}
val (partials, rest) = parse(list)
不幸的是,此函数不是尾部递归的,因此它可能会使更长的列表崩溃。
您可以使用cats中的Eval
来解决此问题:
def parse2(list: List[Data]): Eval[(List[Data], List[Data])] =
Eval.now(list).flatMap {
case (x:Partial) :: xs =>
parse2(xs).map {
case (partials, rest) => (x :: partials, rest) //instead of creating list, you can join partials here
}
case x :: xs =>
parse2(xs).map {
case (partials, rest) => (partials, x :: rest)
}
case _ => Eval.now((Nil, Nil))
}
val (partialsResult, restResult) = parse2(longList).value
此解决方案对于堆栈是安全的,因为它使用的是堆,而不是堆栈。
这是版本,它也将部分片段分组:
def parse3(list: List[Data]): Eval[(Map[String, List[Partial]], List[Data])] =
Eval.now(list).flatMap {
case (x:Partial) :: xs =>
parse3(xs).map {
case (partials, rest) =>
val newPartials = x :: partials.getOrElse(x.identifier, Nil)
(partials + (x.identifier -> newPartials), rest)
}
case x :: xs =>
parse3(xs).map {
case (partials, rest) => (partials, x :: rest)
}
case _ => Eval.now((Map.empty[String, List[Partial]], Nil))
}