【发布时间】:2016-06-06 16:18:21
【问题描述】:
给定 Scala 中的一个集合,我想遍历这个集合,并且对于每个我想发出(生成)从 0 到多个元素的对象,这些元素应该合并到一个新集合中。
例如,我希望是这样的:
val input = Range(0, 15)
val output = input.somefancymapfunction((x) => {
if (x % 3 == 0)
yield(s"${x}/3")
if (x % 5 == 0)
yield(s"${x}/5")
})
构建一个output 集合,其中包含
(0/3, 0/5, 3/3, 5/5, 6/3, 9/3, 10/5, 12/3)
基本上,我想要 filter (1 → 0..1) 和 map (1 → 1) 的超集:映射 (1 → 0..n)。
我尝试过的解决方案
当务之急的解决方案
显然,可以在非功能性方式中这样做,例如:
var output = mutable.ListBuffer()
input.foreach((x) => {
if (x % 3 == 0)
output += s"${x}/3"
if (x % 5 == 0)
output += s"${x}/5"
})
平面图解决方案
我知道flatMap,但又是这样:
1) 如果我们谈论的是任意数量的输出元素,就会变得非常丑陋:
val output = input.flatMap((x) => {
val v1 = if (x % 3 == 0) {
Some(s"${x}/3")
} else {
None
}
val v2 = if (x % 5 == 0) {
Some(s"${x}/5")
} else {
None
}
List(v1, v2).flatten
})
2) 需要在其中使用可变集合:
val output = input.flatMap((x) => {
val r = ListBuffer[String]()
if (x % 3 == 0)
r += s"${x}/3"
if (x % 5 == 0)
r += s"${x}/5"
r
})
这实际上比从一开始就使用可变集合更糟糕,或者
3) 需要大逻辑大修:
val output = input.flatMap((x) => {
if (x % 3 == 0) {
if (x % 5 == 0) {
List(s"${x}/3", s"${x}/5")
} else {
List(s"${x}/3")
}
} else if (x % 5 == 0) {
List(s"${x}/5")
} else {
List()
}
})
恕我直言,它看起来也很丑陋,需要复制生成代码。
滚动你自己的地图功能
最后但同样重要的是,我可以推出自己的此类功能:
def myMultiOutputMap[T, R](coll: TraversableOnce[T], func: (T, ListBuffer[R]) => Unit): List[R] = {
val out = ListBuffer[R]()
coll.foreach((x) => func.apply(x, out))
out.toList
}
几乎可以随心所欲地使用:
val output = myMultiOutputMap[Int, String](input, (x, out) => {
if (x % 3 == 0)
out += s"${x}/3"
if (x % 5 == 0)
out += s"${x}/5"
})
我真的忽略了某些东西,而标准 Scala 集合库中没有这样的功能吗?
类似问题
这个问题与Can I yield or map one element into many in Scala? 有一些相似之处——但那个问题讨论的是 1 个元素 → 3 个元素的映射,我想要 1 个元素 → 任意数量的元素映射。
最后说明
请注意,这不是关于除数/除数的问题,这些条件仅用于说明目的。
【问题讨论】:
-
澄清一下——如果输入集合包含 15,那么输出集合是否应该同时包含 15/3 和 15/5?
-
@Ben 是的,确切地说,1 个元素 (15) 应该映射到 2 个(“15/3”、“15/3”)。
标签: scala functional-programming scala-collections