将块状序列划分为偶数包裹?

时间:2013-10-11 18:15:08

标签: python algorithm

我有一个“块状”项目序列,我希望将其划分为一定数量的大致相等大小的宗地,同时保持宗地内容(以及宗地本身)的排序顺序。由于在阳光下没什么新东西,我猜我只是缺少问题的正确名称。我的最终目标是算法的python实现,但我至少需要朝着正确的方向推进。

问题是我有一个文本,分为不同长度的部分,我想把它分成一系列公平的读数。当然,读数的顺序必须保持不变。

对于某些细节,我有2,519个部分。最长的是1,876个单词,最短的是7个单词。平均长度为305个字,中位数长度为242个。

3 个答案:

答案 0 :(得分:5)

我不确定它是否完全解决了你的程序,但在麻省理工学院开放式课程Introduction to Algorithms中,他们使用dynamic programming来解决行的最佳分割问题,这样就可以很好地填写文本。 page('word-wrapping'),类似于Latex的功能。查看this video后17分钟。

该算法将给出保证的最优分裂,给定一些函数,该函数根据线分裂的难度来定义惩罚。在讲座中,他们将这个丑陋函数定义为(pagewidth - actual_linewidth)^3,但您可以定义自己的函数。算法比或多或少尝试所有不同的分裂可能性(以智能方式)并选择最佳的算法。 DP的主要要求是问题可以分为子程序,例如可以基于n字的先前解决方案来描述n-1, n-2, ...字的解决方案。这些类型的DP算法通常是O(n^2)O(n^3),所以绝对不是NP难。

如果您对基本算法感兴趣,我强烈建议您观看整个系列讲座,老师们很棒。

答案 1 :(得分:1)

这应该会给你一个好的结果,一个“贪婪”的策略:

  • 弄清楚你正在努力的平均值,avg = total_words / num_readings
  • 开始迭代这些部分,累积到目前为止所拥有的单词数。
  • 如果您达到完全匹配,请标记该部分并继续。
  • 否则,如果您要查看单词计数,请根据更接近平均值的内容选择是否包含下一部分,例如如果你没有包括它,那么如果你没有包括它,那么如果你没有包含100,那么就把它留下来。

要做得比你需要一些启发式更好。如果您搞砸了输入,比如一个巨大的部分和许多较小的部分,请说

100 100 100 100 100 100 40000 100 100 100 100

你想把它分成5个部分,你希望你的输出看起来像什么?我的算法会给你这个:

100 100 100 100 100 100
40000
100 100 100 100
0
0

您可以轻松地调整它以强制每个部分至少一个单词:

100 100 100 100 100 100
40000
100 100
100
100

但那可能不像这个选项那么“好”:

100 100 100
100 100 100
40000
100 100
100 100

是的,我建议您查看Bas建议的讲座。你必须调整一下启发式。例如,对于你来说,在一个部分中有更多的单词是可以的,而对于行包装,如果你过去那么它就是无限的。

答案 2 :(得分:1)

好吧,这引起了我的好奇心,所以我用abs(num_words - avg_words)**3的简单不良启发式编写了一个动态编程算法。它应该适用于任何启发式方法。以下是示例输出:

>>> section_words = [100, 100, 100, 100, 100, 100, 40000, 100, 100, 100, 100]
>>> def heuristic(num_words, avg):
...     return abs(num_words - avg)**3
... 
>>> print_solution(solve(section_words, heuristic, 3))
Total=41000, 3 readings, avg=13666.67
Reading #1 (  600 words): [100, 100, 100, 100, 100, 100]
Reading #2 (40000 words): [40000]
Reading #3 (  400 words): [100, 100, 100, 100]
>>> print_solution(solve(section_words, heuristic, 5))
Total=41000, 5 readings, avg=8200.00
Reading #1 (  300 words): [100, 100, 100]
Reading #2 (  300 words): [100, 100, 100]
Reading #3 (40000 words): [40000]
Reading #4 (  200 words): [100, 100]
Reading #5 (  200 words): [100, 100]

>>> section_words = [7, 300, 242, 100, 115, 49, 563, 
                     1000, 400, 9, 14, 300, 200, 400, 
                     500, 200, 10, 19, 1876, 100, 200, 
                     15, 59, 299, 144, 85, 400, 600, 534, 200, 143, 15]
>>> print_solution(solve(section_words, heuristic, 10))
Total=9098, 10 readings, avg=909.80
Reading #1 (  649 words): [7, 300, 242, 100]
Reading #2 (  727 words): [115, 49, 563]
Reading #3 ( 1000 words): [1000]
Reading #4 (  723 words): [400, 9, 14, 300]
Reading #5 (  600 words): [200, 400]
Reading #6 (  729 words): [500, 200, 10, 19]
Reading #7 ( 1876 words): [1876]
Reading #8 (  902 words): [100, 200, 15, 59, 299, 144, 85]
Reading #9 ( 1000 words): [400, 600]
Reading #10 (  892 words): [534, 200, 143, 15]

>>> print_solution(solve(section_words, heuristic, 5))
Total=9098, 5 readings, avg=1819.60
Reading #1 ( 2376 words): [7, 300, 242, 100, 115, 49, 563, 1000]
Reading #2 ( 2023 words): [400, 9, 14, 300, 200, 400, 500, 200]
Reading #3 ( 1905 words): [10, 19, 1876]
Reading #4 ( 1302 words): [100, 200, 15, 59, 299, 144, 85, 400]
Reading #5 ( 1492 words): [600, 534, 200, 143, 15]

>>> print_solution(solve(section_words, heuristic, 3))
Total=9098, 3 readings, avg=3032.67
Reading #1 ( 3099 words): [7, 300, 242, 100, 115, 49, 563, 1000, 400, 9, 14, 300]
Reading #2 ( 3205 words): [200, 400, 500, 200, 10, 19, 1876]
Reading #3 ( 2794 words): [100, 200, 15, 59, 299, 144, 85, 400, 600, 534, 200, 143, 15]

Here是代码。虽然我建议你自己尝试实施它以进行良好的锻炼!

子问题是R(n, i, j),它是:通过i读取jn的部分的最低级别是什么?

基本情况很简单:

R(1, i, j) = heuristic(num words in sections i thru j, total words / total sections)

然后,对于递归,您可以找到所有可能的方法中的最佳解决方案,以分割左侧和左侧之间留下的部分数量。正确的,以及放置该分区的最佳位置:

R(n, i, j) = the lowest badness out of
    R(1, i, i+1) + R(n-1, i+1, j)
    R(1, i, i+2) + R(n-1, i+2, j)
    ...
    R(1, i, j-1) + R(n-1, j-1, j)

    R(2, i, i+1) + R(n-2, i+1, j)
    R(2, i, i+2) + R(n-2, i+2, j)
    ...
    R(2, i, j-1) + R(n-2, j-1, j)

    ...
    ...

    R(n-1, i, i+1) + R(1, i+1, j)
    R(n-1, i, i+2) + R(1, i+2, j)
    ...
    R(n-1, i, j-1) + R(1, j-1, j)

病理案例是指你的读数多于部分:

R(n, i, j) = infinity if n > j-i

您从n=1开始构建解决方案,然后从j-i = 1开始,然后从i=0开始构建解决方案。

它最终有5个嵌套的for循环,所以我不确定它是否尽可能高效,但它似乎可以解决这个问题。