Scala:转换一个集合,每次迭代产生 0..many 元素

标签 scala functional-programming scala-collections

给定 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()
  }
})

也就是说,恕我直言,它看起来也很丑陋,需要复制生成代码。

滚动你自己的 map 功能

最后但并非最不重要的是,我可以推出自己的那种功能:
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 个元素 → 任意数量的元素映射。

最后说明

请注意,这不是关于除法/除数的问题,这些条件仅用于说明目的。

最佳答案

与其为每个除数设置一个单独的 case,不如将它们放在一个容器中并在 a 中迭代它们以进行理解:

val output = for {
  n <- input
  d <- Seq(3, 5)
  if n % d == 0
} yield s"$n/$d"

或者等效地在 collect嵌套在 flatMap :
val output = input.flatMap { n =>
  Seq(3, 5).collect {
    case d if n % d == 0 => s"$n/$d"
  }
}

在不同情况可能具有不同逻辑的更一般情况下,您可以将每个情况放在单独的部分函数中并迭代部分函数:
val output = for {
  n <- input
  f <- Seq[PartialFunction[Int, String]](
    {case x if x % 3 == 0 => s"$x/3"},
    {case x if x % 5 == 0 => s"$x/5"})
  if f.isDefinedAt(n)
} yield f(n)

关于Scala:转换一个集合,每次迭代产生 0..many 元素,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35601077/

相关文章:

c - 如何在 C 中实现将名称与 void 函数相关联的映射?

scala - 如何形成 scala SortedMaps 的联合?

scala - Scala 如何知道使用什么集合实现?

java - 有没有办法在 Scala 中创建自定义注释并编写自定义注释处理器来验证注释?

scala - Kleisli 是仿函数、应用函数还是单子(monad)?

scala - Scala Netbeans 插件的编译问题

scala - 理解 Scala FP 库

scala - VectorAssembler 不支持 StringType 类型的 scala spark 转换

Python 列表理解

scala - 为什么(复制)附加到 Scala 中的 Seq 被定义为 :+ and not just + as in Set and Map?