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



假设我使用各种map和/或flatMap的序列来生成集合序列。是否可以从这些方法中的任何一种中访问有关"当前"集合的信息?例如,在不知道前面的maps 或flatMaps 中使用的函数的任何具体信息,并且不使用任何中间声明的情况下,我如何获得最后一个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. 通过"不使用任何中间声明",我的意思是我不想在通往最终结果的途中使用任何临时集合。所以,下面史蒂夫·沃尔德曼的例子,虽然给出了预期的结果,但并不是我想要的。(我包括这个条件主要是出于美学原因。

为澄清而编辑,第 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)

这是可能的,但不漂亮,如果你出于"审美原因"这样做,不太可能是你想要的东西。

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,而不是教条主义。

您可以定义一个mapWithSelf(或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之后保存中间列表来解决这个问题。

在当前映射/收集操作中引用先前输出的一种简单方法是在映射外部使用命名引用,然后从映射块内引用它:

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
}

这引起了人们的注意,即我们在构建新序列时引用了先前的元素。

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

最新更新