仅缩小/折叠某些元素

时间:2019-01-11 17:19:48

标签: scala functional-programming

我有一个文件解析器,该解析器生成所有元素都属于同一特征的集合。类似于以下内容。

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方法返回的集合是CompleteMetaPartial元素的混合。逻辑是,我需要将CompleteMeta元素传递给原样,同时收集Partial元素并在identifier上分组以创建Complete元素

仅使用Partial个元素(Iterator[Partial])的集合,我可以执行以下操作:

partialsOnly.groupBy(_.identifier)
 .map{ 
   case (ident, parts) => 
     Complete(ident, parts.map(p => p.name -> p.value).toMap)
 }

有没有一种类似于扫描的功能性方法,它可以累积元素,但仅累加一些元素,而其余元素保持不变?

2 个答案:

答案 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的完成时间,则可以通过MapfoldLeft中累积它们,并在完成时发出最终值。

答案 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))
}