【问题标题】:Integer partitioning in ScalaScala中的整数分区
【发布时间】:2011-10-25 22:56:38
【问题描述】:

给定 n(比如 3 个人)和 s(比如 100$),我们想将 s 划分给 n 个人。

所以我们需要所有可能的和为 s 的 n 元组

我的 Scala 代码如下:

def weights(n:Int,s:Int):List[List[Int]] = {
     List.concat( (0 to s).toList.map(List.fill(n)(_)).flatten, (0 to s).toList).
     combinations(n).filter(_.sum==s).map(_.permutations.toList).toList.flatten
}

println(weights(3,100))

这适用于较小的 n 值。 (n=1、2、3 或 4)。

超过 n=4 需要很长时间,几乎无法使用。

我正在寻找使用惰性评估/流来重新编写代码的方法。

我的要求:必须为 n 最多 10 工作。

警告:问题变得非常大非常快。我从 Matlab 得到的结果 -

---For s =100, n = 1 thru 5 results are ---
n=1 :1 combinations
n=2 :101 combinations
n=3 :5151 combinations
n=4 :176851 combinations
n=5: 4598126 combinations
---

【问题讨论】:

  • 所以我猜“公平”不是问题?如果您对 s 的分区方式添加约束,那么组合将会少得多。
  • 我不明白您为什么要实际生成所有组合的列表...?这样做的目的是什么?
  • 我认为不可能在普通计算机中列出所有总和为 100 的 10 元组,因为这个数字太大了。但是,我们可以给出所有 10 元组的计数。
  • Luigi,这是大多数金融机构的标准资产配置问题。假设给你 s$ 并要求在 n 个股票中分配它以最大化某个指标(比如夏普值),你首先需要对 s$ 进行分区。如果股票数量很少(低于 5 只股票),这种天真的蛮力方法非常有效。除此之外,我们还使用线性代数技术。
  • @Daniel:我认为你只需要在你的记忆表中为min 提供一个维度。

标签: scala functional-programming permutation combinations data-partitioning


【解决方案1】:

您需要dynamic programmingmemoization。无论如何,相同的概念。

假设您必须将 s 除以 n。递归的定义如下:

def permutations(s: Int, n: Int): List[List[Int]] = n match {
  case 0 => Nil
  case 1 => List(List(s))
  case _ => (0 to s).toList flatMap (x => permutations(s - x, n - 1) map (x :: _))
}

现在,这仍然会慢得要命,但这里有一个问题……您不需要为已经计算过的数字重新计算 permutations(s, n)。所以你可以这样做:

val memoP = collection.mutable.Map.empty[(Int, Int), List[List[Int]]]
def permutations(s: Int, n: Int): List[List[Int]] = {
  def permutationsWithHead(x: Int) = permutations(s - x, n - 1) map (x :: _)

  n match {
    case 0 => Nil
    case 1 => List(List(s))
    case _ => 
      memoP getOrElseUpdate ((s, n), 
                             (0 to s).toList flatMap permutationsWithHead)
  }
}

这可以进一步改进,因为它会计算每个排列。您只需要计算每个组合,然后置换 那个 而无需重新计算。

要计算每个组合,我们可以像这样更改代码:

val memoC = collection.mutable.Map.empty[(Int, Int, Int), List[List[Int]]]
def combinations(s: Int, n: Int, min: Int = 0): List[List[Int]] = {
  def combinationsWithHead(x: Int) = combinations(s - x, n - 1, x) map (x :: _)

  n match {
    case 0 => Nil
    case 1 => List(List(s))
    case _ => 
      memoC getOrElseUpdate ((s, n, min), 
                             (min to s / 2).toList flatMap combinationsWithHead)
  }
}

仅考虑到组合的绝对数量,运行combinations(100, 10) 仍然很慢。只需在组合上调用 .permutation 即可获得每个组合的排列。

【讨论】:

  • 我不会说你在这里需要记忆,对吧?这只是一种可能的方法,而且与流解决方案不是特别兼容。
  • 更具体地说,这种方法将需要很多很多 GB 的内存,仅用于 n = 6
  • @Travis 这取决于您要优化的内容。在我的回答中,我试图更快地计算所有内容。在你的(顺便说一句,非常好)中,你尝试尽快获得第一个结果,并优化内存。
  • @Krishnan 我看到你在我发布combinations 定义后接受了我的回答。这个定义是不正确的。我已经用目前的定义修正了它,尽管对于 s = 100, n = 10,即使是单独的组合也需要很长时间来计算。
  • 谢谢丹尼尔。我将使用 Travis 的解决方案,因为我的特定例程更适合他的方法。您的方法针对速度进行了优化,而不针对内存进行了优化,因此对于 n5,有时我的代码会崩溃(内存不足错误)。使用 Stream,即使计算速度变慢,我也可以更有效地管理内存。顺便说一句,分区总数是 s+n-1 选择 s,以防您对组合学感兴趣...
【解决方案2】:

这是一个快速而肮脏的Stream 解决方案:

 def weights(n: Int, s: Int) = (1 until s).foldLeft(Stream(Nil: List[Int])) {
   (a, _) => a.flatMap(c => Stream.range(0, n - c.sum + 1).map(_ :: c))
 }.map(c => (n - c.sum) :: c)

它在我的机器上大约 15 秒内适用于 n = 6

scala> var x = 0
scala> weights(100, 6).foreach(_ => x += 1)
scala> x
res81: Int = 96560646

附带说明:当您到达 n = 10 时,已经有 4,263,421,511,271 这些东西了。光是流式传输就需要几天时间。

【讨论】:

  • 谢谢,这正是我想要的。我可以从这里获取并在此基础上进行构建。非常感谢。
【解决方案3】:

我对这个问题的解决方法,能电脑n到6点:

object Partition {
  implicit def i2p(n: Int): Partition = new Partition(n)
  def main(args : Array[String]) : Unit = {
    for(n <- 1 to 6) println(100.partitions(n).size)
  }

}

class Partition(n: Int){
  def partitions(m: Int):Iterator[List[Int]] = new Iterator[List[Int]] {
    val nums = Array.ofDim[Int](m)
    nums(0) = n

    var hasNext = m > 0 && n > 0

    override def next: List[Int] = {
      if(hasNext){
        val result = nums.toList
        var idx = 0
        while(idx < m-1 && nums(idx) == 0) idx = idx + 1
        if(idx == m-1) hasNext = false
        else {
          nums(idx+1) = nums(idx+1) + 1
          nums(0)     = nums(idx) - 1
          if(idx != 0) nums(idx)   = 0
        }
        result
      }
      else Iterator.empty.next
    }
  }
}

1 101 5151 176851 4598126 96560646


但是,我们可以只显示可能的 n 元组的数量:

val pt: (Int,Int) => BigInt =  {
    val buf = collection.mutable.Map[(Int,Int),BigInt]()
    (s,n) => buf.getOrElseUpdate((s,n),
        if(n == 0 && s > 0) BigInt(0)
        else if(s == 0) BigInt(1)
        else (0 to s).map{k => pt(s-k,n-1)}.sum
        )
  }

  for(n <- 1 to 20) printf("%2d :%s%n",n,pt(100,n).toString)

 1 :1
 2 :101
 3 :5151
 4 :176851
 5 :4598126
 6 :96560646
 7 :1705904746
 8 :26075972546
 9 :352025629371
10 :4263421511271
11 :46897636623981
12 :473239787751081
13 :4416904685676756
14 :38393094575497956
15 :312629484400483356
16 :2396826047070372396
17 :17376988841260199871
18 :119594570260437846171
19 :784008849485092547121
20 :4910371215196105953021

【讨论】:

  • 先生,您可以很容易地计算出分区的总数,而不必费力地先生成它们然后计算它们。 (s+n-1) 选择 s = 分区数 所以对于 s = 100,n = 3 人,(100+3-1) 选择 100 = 102C100 = 5051。
猜你喜欢
  • 2012-05-21
  • 2023-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-21
  • 2020-06-05
  • 1970-01-01
相关资源
最近更新 更多