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

Gre*_*Cat 7 functional-programming scala scala-collections

给定Scala中的一个集合,我想遍历这个集合,并为每个对象发送(yield)从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")
})
Run Code Online (Sandbox Code Playgroud)

构建一个output包含的集合

(0/3, 0/5, 3/3, 5/5, 6/3, 9/3, 10/5, 12/3)
Run Code Online (Sandbox Code Playgroud)

基本上,我想要filter(1→0..1)和map(1→1)允许的超集:映射(1→0..n).

解决方案我试过了

势在必行的解决方案

显然,在非功能性的maneer中可以这样做,例如:

var output = mutable.ListBuffer()
input.foreach((x) => {
  if (x % 3 == 0)
    output += s"${x}/3"
  if (x % 5 == 0)
    output += s"${x}/5"
})
Run Code Online (Sandbox Code Playgroud)

Flatmap解决方案

我知道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
})
Run Code Online (Sandbox Code Playgroud)

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
})
Run Code Online (Sandbox Code Playgroud)

从一开始就使用可变集合实际上更糟糕,或者

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()
  }
})
Run Code Online (Sandbox Code Playgroud)

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

滚你自己的地图功能

最后,但并非最不重要的是,我可以推出自己的功能:

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
}
Run Code Online (Sandbox Code Playgroud)

它几乎可以像我想要的那样使用:

val output = myMultiOutputMap[Int, String](input, (x, out) => {
  if (x % 3 == 0)
    out += s"${x}/3"
  if (x % 5 == 0)
    out += s"${x}/5"
})
Run Code Online (Sandbox Code Playgroud)

我真的忽略了什么,标准的Scala集合库中没有这样的功能吗?

类似的问题

这个问题有些相似之处我可以在Scala中将一个元素生成或映射到多个元素吗? - 但是那个问题讨论了1个元素→3个元素的映射,我希望1个元素→任意数量的元素映射.

最后的说明

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

Ben*_*Ben 6

不是为每个除数设置一个单独的案例,而是将它们放在一个容器中,然后迭代它们以便理解:

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

或者等效地collect嵌套在flatMap:

val output = input.flatMap { n =>
  Seq(3, 5).collect {
    case d if n % d == 0 => s"$n/$d"
  }
}
Run Code Online (Sandbox Code Playgroud)

在更一般的情况下,不同的情况可能具有不同的逻辑,您可以将每个案例放在单独的部分函数中并迭代部分函数:

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)
Run Code Online (Sandbox Code Playgroud)


rss*_*ssh 0

您可以尝试收集:

val input = Range(0,15)
val output = input.flatMap { x =>
     List(3,5) collect { case n if (x%n == 0) => s"${x}/${n}" }
}
System.out.println(output)
Run Code Online (Sandbox Code Playgroud)