scan() 的常见模式,我最终不关心状态

Common pattern of scan() where I don't ultimately care about the state

我发现自己经常做以下事情:

val adjustedActions = actions.scanLeft((1.0, null: CorpAction)){
  case ((runningSplitAdj, _), action) => action match {
    case Dividend(date, amount) => 
      (runningSplitAdj, Dividend(date, amount * runningSplitAdj))
    case s @ Split(date, sharesForOne) => 
      ((runningSplitAdj * sharesForOne), s)
  } 
}
.drop(1).map(_._2)

在这种情况下,我需要累积 runningSplitAdj,以便更正操作列表中的红利。在这里,我使用 scan 来维护我需要的状态,以便更正操作,但最终,我只需要操作。因此,我需要为状态中的初始操作使用 null,但最后,删除该项目并映射所有状态。

是否有更优雅的方式来构造这些?在 RxScala Observables 的上下文中,我实际上创建了一个新的运算符来执行此操作(在 RxJava 邮件列表的一些帮助之后):

implicit class ScanMappingObs[X](val obs: Observable[X]) extends AnyVal {
 def scanMap[S,Y](f: (X,S) => (Y,S), s0: S): Observable[Y] = {
   val y0: Y = null.asInstanceOf[Y]
    // drop(1) because scan also emits initial state
    obs.scan((y0, s0)){case ((y, s), x) => f(x, s)}.drop(1).map(_._1)
  }
}

但是,现在我发现自己也在对列表和向量执行此操作,所以我想知道我是否可以做一些更通用的事情?

您描述的组合器(或至少非常相似的东西)通常称为 mapAccum。采取以下scanLeft的简化用法:

val xs = (1 to 10).toList

val result1 = xs.scanLeft((1, 0.0)) {
  case ((acc, _), i) => (acc + i, i.toDouble / acc)
}.tail.map(_._2)

这等同于以下内容(使用 Scalaz's implementation of mapAccumLeft):

xs.mapAccumLeft[Double, Int](1, {
  case (acc, i) => (acc + i, i.toDouble / acc)
})._2

mapAccumLeft returns 一对最终状态和每一步的结果序列,但不需要您指定虚假的初始结果(将被忽略然后丢弃),并且您不必映射整个集合来摆脱状态——您只需取对中的第二个成员。

遗憾的是 mapAccumLeft 在标准库中不可用,但如果您正在寻找一个名称或有关实现的想法,这是一个开始的地方。