【问题标题】:Efficient item binning algorithm (itertools/numpy)高效的项目分箱算法(itertools/numpy)
【发布时间】:2023-03-28 05:48:01
【问题描述】:

我认为这是一个常见的组合问题,但我似乎找不到它的名称或任何关于它的资料。我在 Python 和 numpy 中做这个,但是如果有一个快速的矩阵方法,我可能可以翻译。

基本上,给定 n 个项目,我需要生成将它们放入 m 个箱的所有方法。例如,将 4 个项目分箱到 3 个箱中会得到类似 [(4, 0, 0), (3, 1, 0), (3, 0, 1), (2, 2, 0), (2, 1, 1), ...] 的结果。这是一个有固定总数的产品。

使用 itertools 实现这一点很简单。

import itertools

def fixed_total_product(bins, num_items):
""" Return iterator of all item binning possibilities. """
return itertools.ifilter(lambda combo: sum(combo) == num_items,
                         itertools.product(xrange(num_items + 1), repeat=bins))

不幸的是,我认为在循环中进行后续计算会效率低下。稍后将其用作 2D numpy 数组会更快,但我无法找到一种有效的方法来构建数组。我可以遍历 ifilter 结果,构建一个可能性列表,然后使用它来构建数组,但这似乎是一种巨大的浪费。

我猜最好的方法是“以 numpy 的方式”构建所有东西,但我不确定如何做到这一点。 stackoverflow 上有一个快速的产品实现:Using numpy to build an array of all combinations of two arrays。我猜你只能修改它以输出具有正确总和的产品。数组的大小应该是 ((m-1) + n) 选择 n,因为有 m-1 个 bin 边界。

有什么想法吗?基准非常受欢迎,但不是必需的。

【问题讨论】:

  • 我提到了the integer partition problem,它给出了相似序列的长度。不过,我认为这并不遥远,因为一旦有了整数分区序列,就很容易删除顺序(添加所有排列)并强制执行多个 bin(n bins 中的m 意味着最大的数字在任何 bin 最多可以是 m-n)。
  • 抱歉。我以为我以前见过解决/关闭。不过,我不得不不同意解决方案。我添加了一个文档字符串和更具描述性的变量来帮助理解算法......

标签: python algorithm numpy combinatorics cartesian-product


【解决方案1】:

基于递归 C(n, k) = C(n - 1, k) + C(n - 1, k - 1),记忆化,使用 numpy 操作。

import numpy as np

def binnings(n, k, cache={}):
    if n == 0:
        return np.zeros((1, k))
    if k == 0:
        return np.empty((0, 0))
    args = (n, k)
    if args in cache:
        return cache[args]
    a = binnings(n - 1, k, cache)
    a1 = a + (np.arange(k) == 0)
    b = binnings(n, k - 1, cache)
    b1 = np.hstack((np.zeros((b.shape[0], 1)), b))
    result = np.vstack((a1, b1))
    cache[args] = result
    return result

if __name__ == '__main__':
    import timeit
    print timeit.timeit('binnings(20, 5, {})', setup='from __main__ import binnings', number=1)

(20, 5) 的时间(以秒为单位):

0.0129251480103

【讨论】:

  • 传奇老兄。万分感谢。当我回到这个问题时,我会编辑这个问题。
【解决方案2】:

在 numpy.xml 中使用一些不同的技巧可能有一种更快的方法。 numpy.indicies 是您想要开始的地方。它本质上等同于itertools.product,只要将它与rollaxis 结合使用。 Sven Marnach 的回答 in this question 就是一个很好的例子(但是在他的最后一个例子中有一个小错误,这是你想要使用的。它应该是 numpy.indices((len(some_list) + 1), * some_length...

但是,对于这样的事情,使用 itertools 可能更具可读性。

numpy.fromiter 比将事物显式转换为列表要快一点,特别是如果你给它计数迭代器中的项目数。主要优点是使用的内存要少得多,这在大型迭代器的情况下非常有用。

以下是一些使用 numpy.indices 技巧和将迭代器转换为 numpy 数组的各种方法的示例:

import itertools
import numpy as np
import scipy.special


def fixed_total_product(bins, num_items):
    return itertools.ifilter(lambda combo: sum(combo) == num_items,
            itertools.product(xrange(num_items + 1), repeat=bins))

def fixed_total_product_fromiter(bins, num_items):
    size = scipy.special.binom(bins - 1 + num_items, num_items)
    combinations = fixed_total_product(bins, num_items)
    indicies = (idx for row in combinations for idx in row)
    arr = np.fromiter(indicies, count=bins * int(size), dtype=np.int)
    return arr.reshape(-1, bins)

def fixed_total_product_fromlist(bins, num_items):
    return np.array(list(fixed_total_product(bins, num_items)), dtype=np.int)

def fixed_total_product_numpy(bins, num_items):
    arr = np.rollaxis(np.indices((num_items + 1,) * bins), 0, bins + 1)
    arr = arr.reshape(-1, bins)
    arr = np.arange(num_items + 1)[arr]
    sums = arr.sum(axis=1)
    return arr[sums == num_items]

m, n = 5, 20

if __name__ == '__main__':
    import timeit
    list_time = timeit.timeit('fixed_total_product_fromlist(m, n)',
            setup='from __main__ import fixed_total_product_fromlist, m, n',
            number=1)
    iter_time = timeit.timeit('fixed_total_product_fromiter(m, n)',
            setup='from __main__ import fixed_total_product_fromiter, m, n',
            number=1)
    numpy_time = timeit.timeit('fixed_total_product_numpy(m, n)',
            setup='from __main__ import fixed_total_product_numpy, m, n',
            number=1)

    print 'All combinations of {0} items drawn from a set of {1} items...'.format(m,n)
    print '  Converting to a list and then an array: {0} sec'.format(list_time)
    print '  Using fromiter: {0} sec'.format(iter_time)
    print '  Using numpy.indices: {0} sec'.format(numpy_time)

关于时机:

All combinations of 5 items drawn from a set of 20 items...
  Converting to a list and then an array: 2.75901389122 sec
  Using fromiter: 2.10619592667 sec
  Using numpy.indices: 1.44955015182 sec

您会注意到它们都不是特别快。

您正在使用蛮力算法(生成所有可能的组合,然后过滤它们),我只是在此处基于 numpy 的示例中复制它。

可能有一种更有效的方法来做到这一点!但是,我无论如何都不是 CS 或数学人,所以我不知道是否有一个众所周知的算法可以在不首先生成所有可能的组合的情况下做到这一点......

祝你好运,无论如何!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-07-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-11
    • 1970-01-01
    • 2023-03-28
    • 1970-01-01
    相关资源
    最近更新 更多