计算将一系列整数分成N个bin的所有方法,其中每个bin只包含连续的数字

时间:2016-09-07 18:37:56

标签: python combinatorics

我想找到所有可能的方法将一系列(连续的)整数M = {0,1,2,...,m}映射到另一系列整数N = {0,1,2,... ,n}其中m> n,受制于约束,即M中只有连续的整数映射到N中的相同整数。

下面的python代码接近(start对应于M中的第一个元素,stop - 1对应于M中的最后一个元素,nbins对应于| N |):

import itertools
def find_bins(start, stop, nbins):
    if (nbins > 1):
        return list(list(itertools.product([range(start, ii)], find_bins(ii, stop, nbins-1))) for ii in range(start+1, stop-nbins+2))
    else:
        return [range(start, stop)]

E.g

In [20]: find_bins(start=0, stop=5, nbins=3)
Out[20]: 
[[([0], [([1], [2, 3, 4])]),
([0], [([1, 2], [3, 4])]),
([0], [([1, 2, 3], [4])])],
[([0, 1], [([2], [3, 4])]), 
([0, 1], [([2, 3], [4])])],
[([0, 1, 2], [([3], [4])])]]

但是,正如你可以看到输出是嵌套的,并且在我的生命中,我无法找到一种方法来正确修改代码而不会破坏它。

所需的输出如下所示:

In [20]: find_bins(start=0, stop=5, nbins=3)
Out[20]: 
[[(0), (1), (2, 3, 4)],
[(0), (1, 2), (3, 4)],
[(0), (1, 2, 3), (4)],
[(0, 1), (2), (3, 4)], 
[(0, 1), (2, 3), (4)],
[(0, 1, 2), (3), (4)]]

2 个答案:

答案 0 :(得分:1)

我建议采用不同的方法:分区为n非空箱由n-1个不同的索引唯一确定,这些索引标记箱之间的边界,第一个标记位于第一个元素之后,和最后一个元素之前的最终标记。 itertools.combinations()可以直接用于生成所有这样的索引元组,然后只需将它们用作切片索引即可。像这样:

def find_nbins(start, stop, nbins):
    from itertools import combinations
    base = range(start, stop)
    nbase = len(base)
    for ixs in combinations(range(1, stop - start), nbins - 1):
        yield [tuple(base[lo: hi])
               for lo, hi in zip((0,) + ixs, ixs + (nbase,))]

然后,例如,

for x in find_nbins(0, 5, 3):
    print(x)

显示:

[(0,), (1,), (2, 3, 4)]
[(0,), (1, 2), (3, 4)]
[(0,), (1, 2, 3), (4,)]
[(0, 1), (2,), (3, 4)]
[(0, 1), (2, 3), (4,)]
[(0, 1, 2), (3,), (4,)]

编辑:将其变成2个问题

只是注意到这里存在一个更普遍的潜在问题:生成将任意序列分解为n非空箱的方法。然后,这里的具体问题是将其应用于序列range(start, stop)。我相信以这种方式查看它会使代码更容易理解,所以这里是:

def gbins(seq, nbins):
    from itertools import combinations
    base = tuple(seq)
    nbase = len(base)
    for ixs in combinations(range(1, nbase), nbins - 1):
        yield [base[lo: hi]
               for lo, hi in zip((0,) + ixs, ixs + (nbase,))]

def find_nbins(start, stop, nbins):
    return gbins(range(start, stop), nbins)

答案 1 :(得分:0)

这就是我想要的;我很乐意接受更简单,更优雅的解决方案:

def _split(start, stop, nbins):
    if (nbins > 1):
        out = []
        for ii in range(start+1, stop-nbins+2):
            iterator = itertools.product([range(start, ii)], _split(ii, stop, nbins-1))
            for item in iterator:
                out.append(item)
        return out
    else:
        return [range(start, stop)]

def _unpack(nested):
    unpacked = []
    if isinstance(nested, (list, tuple)):
        for item in nested:

            if isinstance(item, tuple):
                for subitem in item:
                    unpacked.extend(_unpack(subitem))

            elif isinstance(item, list):
                unpacked.append([_unpack(subitem) for subitem in item])

            elif isinstance(item, int):
                unpacked.append([item])

        return unpacked

    else: # integer
        return nested

def find_nbins(start, stop, nbins):
    nested = _split(start, stop, nbins)
    unpacked = [_unpack(item) for item in nested]
    return unpacked