Scala - 在 map 或 flatMap 中访问集合成员

Scala - access collection members within map or flatMap

假设我使用各种maps and/or flatMaps 的序列来生成一系列集合。是否可以从任何这些方法中访问有关 "current" 集合的信息?例如,在不知道任何关于前面 mapflatMap 中使用的函数的具体信息,并且不使用任何中间声明的情况下,我如何获得最大值(或长度,或第一个元素,等)最后一个 map 所作用的集合?

List(1, 2, 3)
  .flatMap(x => f(x) /* some unknown function */)
  .map(x => x + ??? /* what is the max element of the collection? */)

编辑澄清:

  1. 在示例中,我不是在寻找初始 List 的最大值(或其他)。我正在寻找应用 flatMap 后集合的最大值。

  2. "without using any intermediate declarations" 我的意思是我不想在获得最终结果的途中使用任何临时集合。因此,下面史蒂夫沃尔德曼的例子虽然给出了预期的结果,但并不是我想要的。 (我包括这个条件主要是出于审美原因。)

编辑澄清,第 2 部分:

理想的解决方案是一些神奇的关键字或语法糖,让我引用当前的集合:

List(1, 2, 3)
  .flatMap(x => f(x))
  .map(x => x + theCurrentList.max)

我准备接受这个事实,然而,这是不可能的。

也许只需将列表定义为 val,这样您就可以命名它了?我不知道 map(...)flatMap(...) 内置的任何功能会有帮助。

val myList = List(1, 2, 3)
myList
  .flatMap(x => f(x) /* some unknown function */)
  .map(x => x + myList.max /* what is the max element of the List? */)

更新:至少通过这种方法,如果您有多个转换并且想要查看转换后的版本,您必须为其命名。你可以逃脱

val myList = List(1, 2, 3).flatMap(x => f(x) /* some unknown function */)

myList.map(x => x + myList.max /* what is the max element of the List? */)

或者,如果会有多个转换,养成阶段命名的习惯。

val rawList    = List(1, 2, 3)
val smordified = rawList.flatMap(x => f(x) /* some unknown function */)
val maxified   = smordified.map(x => x + smordified.max /* what is the max element of the List? */)
maxified

更新 2:观察它在 REPL 中的工作,即使是异构类型也是如此:

scala> def f( x : Int ) : Vector[Double] = Vector(x * math.random, x * math.random )
f: (x: Int)Vector[Double]

scala> val rawList    = List(1, 2, 3)
rawList: List[Int] = List(1, 2, 3)

scala> val smordified = rawList.flatMap(x => f(x) /* some unknown function */)
smordified: List[Double] = List(0.40730853571901315, 0.15151641399798665, 1.5305929709857609, 0.35211231420067435, 0.644241939254793, 0.15530230501048903)

scala> val maxified   = smordified.map(x => x + smordified.max /* what is the max element of the List? */)
maxified: List[Double] = List(1.937901506704774, 1.6821093849837476, 3.0611859419715217, 1.8827052851864352, 2.1748349102405538, 1.6858952759962498)

scala> maxified
res3: List[Double] = List(1.937901506704774, 1.6821093849837476, 3.0611859419715217, 1.8827052851864352, 2.1748349102405538, 1.6858952759962498)

可能,但不是很漂亮,如果你是为了 "aesthetic reasons."

做的话,也不太可能是你想要的
import scala.math.max

def f(x: Int): Seq[Int] = ???

List(1, 2, 3).
  flatMap(x => f(x) /* some unknown function */).
  foldRight((List[Int](),List[Int]())) {
    case (x, (xs, Nil)) => ((x :: xs), List.fill(xs.size + 1)(x))
    case (x, (xs, xMax :: _)) => ((x :: xs), List.fill(xs.size + 1)(max(x, xMax)))
  }.
  zipped.
  map {
    case (x, xMax) => x + xMax
  }

// Or alternately, a slightly more efficient version using Streams.
List(1, 2, 3).
  flatMap(x => f(x) /* some unknown function */).
  foldRight((List[Int](),Stream[Int]())) {
    case (x, (xs, Stream())) =>
      ((x :: xs), Stream.continually(x))
    case (x, (xs, curXMax #:: _)) =>
      val newXMax = max(x, curXMax)
      ((x :: xs), Stream.continually(newXMax))
  }.
  zipped.
  map {
    case (x, xMax) => x + xMax
  }

说真的,我只是接受了这个,看看我是否能做到。虽然代码并没有像我预期的那样糟糕,但我仍然不认为它的可读性特别好。我不鼓励在类似于 的东西上使用它。有时,简单地介绍一个 val 而不是教条式的更好。

在当前 map/collect 操作中引用先前输出的一种 somewhat-simple 方法是在地图外部使用命名引用,然后从地图块中引用它:

var prevOutput = ...  // starting value of whatever is referenced within the map
myValues.map {
  prevOutput = ... // expression that references prior `prevOutput`
  prevOutput       // return above computed value for the map to collect
}

这引起了我们在构建新序列时引用先前元素这一事实的注意。

不过,如果您想引用任意以前的值,而不仅仅是以前的值,这会更混乱。

您可以按照这些行定义一个 mapWithSelf(resp. flatMapWithSelf)操作,并将其作为隐式扩充添加到集合中。对于 List,它可能看起来像:

// Scala 2.13 APIs
object Enrichments {
  implicit class WithSelfOps[A](val lst: List[A]) extends AnyVal {
    def mapWithSelf[B](f: (A, List[A]) => B): List[B] =
      lst.map(f(_, lst))

    def flatMapWithSelf[B](f: (A, List[A]) => IterableOnce[B]): List[B] =
      lst.flatMap(f(_, lst))
  }
}

充实基本上在操作之前固定集合的值并将其线程化。应该可以对此进行泛化(至少对于严格的集合),尽管它在 2.12 和 2.13+ 中看起来有点不同。

用法看起来像

import Enrichments._

val someF: Int => IterableOnce[Int] = ???

List(1, 2, 3)
  .flatMap(someF)
  .mapWithSelf { (x, lst) =>
    x + lst.max
  }

所以在使用现场,它是美观的。请注意,如果您正在计算遍历列表的内容,则每次都会遍历列表(导致二次运行时)。您可以通过一些可变性或仅在 flatMap.

之后保存中间列表来解决这个问题