我试图概括重复的嵌套flatMap
,但不确定是否存在。
def choose3flatMap(n: Int, r: Int = 3) =
(0 to n - r)
.flatMap(i => (i + 1 to n - (r - 1))
.flatMap(j => (j + 1 to n - (r - 2))
.map(k => Seq(i, j, k))))
重复flatMap操作,我们可以得到 n选择5 ,的所有组合:
def choose5flatMap(n: Int, r: Int = 5) =
(0 to n - r)
.flatMap(i => (i + 1 to n - (r - 1))
.flatMap(j => (j + 1 to n - (r - 2))
.flatMap(k => (k + 1 to n - (r - 3))
.flatMap(l => (l + 1 to n - (r - 4))
.map(m => Seq(i, j, k, l, m)))))
显然这里有一种模式。我想利用这种相似性来获得 n选择r ,的一般解决方案。有没有一种简单的方法来实现这一目标。也许是某种更高阶的函数?
Scala让我用for表达式重写map
/ flatMap
。这看起来更干净,但选择的数量仍然是硬编码的。
def choose3Loop(n: Int, r: Int = 3) =
for {
i <- 0 to n - r
j <- i + 1 to n - (r - 1)
k <- j + 1 to n - (r - 2)
} yield Seq(i, j, k)
我可以直接使用flatMap
或使用for
表达式的糖来编写递归解决方案:
def combinationsRecursive(n: Int, r: Int, i: Int = 0): Seq[Seq[Int]] =
if (r == 1) (i until n).map(Seq(_))
else {
(i to n - r).flatMap(
i => combinationsRecursive(n, r - 1, i + 1).map(j => i +: j))
}
def combinationsRecursiveLoop(n: Int, r: Int, i: Int = 0): Seq[Seq[Int]] =
if (r == 1) (i until n).map(Seq(_))
else
for {
i <- i to n - r
j <- combinationsRecursiveLoop(n, r - 1, i + 1)
} yield i +: j
虽然这些是一般问题的解决方案,但我想知道我在这里缺少的更高级抽象是否也适用于其他问题。我认识到,对于这个特定的应用程序,我可以(0 to n).combinations(r)
使用库提供的计算组合实现。
虽然上面的代码是Scala,但在这种情况下,我感兴趣的是它的函数式编程方面,而不是语言功能。如果有一个解决方案,但Scala不支持,我对此感兴趣。
编辑他是一个示例调用者,并按请求生成输出:
阶&GT; combinationsRecursiveLoop(5,3)
res0:Seq [Seq [Int]] =向量(列表(0,1,2),列表(0,1,3),列表(0,1,4),列表(0,2,3) ,列表(0,2,4),列表(0,3,4),列表(1,2,3),列表(1,2,4),列表(1,3,4),列表(2, 3,4))
阶&GT; combinationsRecursiveLoop(5,3).map(&#34;(&#34; + _。mkString(&#34;,&#34;)+&#34;)&#34;)。mkString(&#34 ;&#34;)
res1:String =(0,1,2)(0,1,3)(0,1,4)(0,2,3)(0,2,4)(0,3,4)( 1,2,3)(1,2,4)(1,3,4)(2,3,4)
它只提供从0开始的包含 n 元素的整数集的所有 r 元素子集。 More information on combinations can be found on Wikipedia
答案 0 :(得分:12)
这是一种看待这个问题的方法,我已经想到了。
您可以将链中的一个阶段作为函数f: List[Int] => List[List[Int]]
提取,将List
作为组合的开头,并将所有可能的下一个元素添加到其中。
例如在choose(5, 3)
中,f(List(2, 0))
会产生List(List(3, 2, 0), List(4, 2, 0))
。
这是一个这样的函数的可能实现,其中添加了一些初始案例的处理:
val f: List[Int] => List[List[Int]] = l =>
(l.headOption.map(_ + 1).getOrElse(0) to n - (r - l.size))
.map(_ :: l).toList
现在,这样的函数是Kleisli arrow Kleisli[List, List[Int], List[Int]]
,它是内形的(具有相同的参数和返回类型)。
对于内形kleisli箭头有一个monoid个实例,其中monoid&#34;添加&#34;表示flatMap
操作(或伪代码,f1 |+| f2 == a => f1(a).flatMap(f2)
)。因此,要替换您需要的flatMap
链,请添加&#34;此r
函数的f
个实例,或者换句话说,将f
函数乘以r
。
这个想法直接转换为Scalaz代码:
import scalaz._, Scalaz._
def choose(n: Int, r: Int) = {
val f: List[Int] => List[List[Int]] = l =>
(l.headOption.map(_ + 1).getOrElse(0) to n - (r - l.size))
.map(_ :: l).toList
Endomorphic.endoKleisli(f).multiply(r).run(Nil)
}
以下是运行它的示例:
scala> choose(4, 3)
res1: List[List[Int]] = List(List(2, 1, 0), List(3, 1, 0), List(3, 2, 0), List(3, 2, 1))
组合是相反的,但应该可以制作一个版本,它以递增的顺序生成元素组合(或者只运行choose(n, r).map(_.reverse)
)。
另一个改进是制作一个懒惰版本,返回Stream[List[Int]]
(甚至更好scalaz.EphemeralStream[List[Int]]
:你不想让所有组合缓存在内存中),但是这个留给读者作为练习。