【问题标题】:Generator for combinations in special order特殊订单组合生成器
【发布时间】:2014-06-23 19:39:05
【问题描述】:

我有以下递归生成器,它产生从0top-1 的每个数字组合:

def f(width, top):
  if width == 0:
    yield []
  else:
    for v in range(top):
      for subResult in f(width - 1, top):
        yield [ v ] + subResult

如果调用为f(3, 3),则会产生值

[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 1, 0], [0, 1, 1], [0, 1, 2],
[0, 2, 0], [0, 2, 1], [0, 2, 2], [1, 0, 0], [1, 0, 1], [1, 0, 2],
[1, 1, 0], [1, 1, 1], [1, 1, 2], [1, 2, 0], [1, 2, 1], [1, 2, 2],
[2, 0, 0], [2, 0, 1], [2, 0, 2], [2, 1, 0], [2, 1, 1], [2, 1, 2],
[2, 2, 0], [2, 2, 1], [2, 2, 2]

(尝试将其称为list(f(3,3)) 以获取这些列表。)

我需要以不同的顺序获得相同的值:我希望这些值按最大值排序,即。 e.首先是值[0, 0, 0],然后是所有以1 为最大值的值,即。 e. [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], ...,然后是那些包含2,即。 e. [0, 0, 2], [0, 1, 2], [0, 2, 0], [0, 2, 1], [0, 2, 2], [2, 0, 0], ...

生成器永远不会产生两次值(当然),并且必须可以使用非常大的值(例如 f(4, 1000))调用它,然后根本不会完全耗尽它(因此首先生成所有值,然后在它们的最大值之后对其进行排序是不可能的)。

我能想到的唯一方法是首先为f(w, 0) 生成所有值,然后为f(w, 1) 生成所有值,然后为f(w, 2) 生成所有值,并且总是跳过之前生成的值,但我有一种唠叨的感觉,他们可能是更好的方法:

def g(width, top):
  for t in range(top):
    for v in f(width, t+1):
      if t in v:
        yield v

有什么想法吗?

【问题讨论】:

  • 您对两个具有相同最大值的列表有首选顺序吗?
  • 嗯,不是真的,但我认为g 产生的订单是最不令人惊讶的订单之一 ;-)
  • 老实说,您对g 的实现与我的做法差不多。有一些方法可以避免跳过,但增加的复杂性可能不值得。
  • “下一个排列”版本现已编码(请参阅答案)。

标签: python algorithm generator combinations


【解决方案1】:
def h(width,top,top_count):
    """
    Producing lists of length 'width' containing numbers from 0 to top-1.
    Where top-1 only occur exactly top_count times.
    """
    if width == 0:
        yield []
    elif width == top_count:
        yield [top-1]*top_count
    else:
        for x in range(top-1):
            for result in h(width-1,top,top_count):
                yield [x]+result
        if top_count > 0:
            for result in h(width-1,top,top_count-1):
                yield [top-1]+result


def m(width,top):
    yield [0]*width
    for current_top in range(2,top+1):
        for top_count in range(1,width+1):
            print "=== h{}".format((width,current_top,top_count))
            for result in h(width,current_top,top_count):
                print result
                yield result

ans = [x for x in m(3,3)]

结果:

=== h(3, 2, 1)
[0, 0, 1]
[0, 1, 0]
[1, 0, 0]
=== h(3, 2, 2)
[0, 1, 1]
[1, 0, 1]
[1, 1, 0]
=== h(3, 2, 3)
[1, 1, 1]
=== h(3, 3, 1)
[0, 0, 2]
[0, 1, 2]
[0, 2, 0]
[0, 2, 1]
[1, 0, 2]
[1, 1, 2]
[1, 2, 0]
[1, 2, 1]
[2, 0, 0]
[2, 0, 1]
[2, 1, 0]
[2, 1, 1]
=== h(3, 3, 2)
[0, 2, 2]
[1, 2, 2]
[2, 0, 2]
[2, 1, 2]
[2, 2, 0]
[2, 2, 1]
=== h(3, 3, 3)
[2, 2, 2]

添加了打印语句以显示对函数h 的每次调用及其结果。 h 函数的注释应该足够清楚,可以解释大致的想法。

【讨论】:

    【解决方案2】:

    我自己找到了解决方案。我首先循环顶部值,然后生成具有一个或多个该顶部值的所有值。为此,我循环了顶部值的数量(1 到宽度)。对于每个这样的数量,我会遍历这些最高值可以具有的所有位置组合。然后我用最高值填充这些位置,用低于最高值的所有值的普通乘积填充剩余的值。

    代码如下:

    from itertools import product, combinations
    
    def h(width, top):
      for t in range(top):
        for topAmount in range(1, width+1):  # how many top values are present?
          for topPositions in combinations(range(width), topAmount):
            for fillers in product(
                *[ range(t) for x in range(width-len(topPositions)) ]):
              fillers = list(fillers)
              yield [ t if i in topPositions else fillers.pop()
                  for i in range(width) ]
    

    但我仍然想邀请您提出更优雅的解决方案。在我看来,这仍然是一种蛮力方法,而我建立价值的方式肯定不是我见过的最便宜的。

    【讨论】:

    • combinations的使用让代码更紧凑,否则这个方案和我的思路很相似。
    • 对,我在发帖前没有更新,所以我之前没有看到你的,现在我更喜欢我想出的更紧凑的版本,但我们解决方案背后的想法是相同:-)(所以在接受时我总是更喜欢你的答案)。
    【解决方案3】:

    成长方块的想法

    (从“对角线”的想法更新)

    当我在纸上画出任务时,我想到了这样的事情:

     |0|1|2|3|
    -|-|-|-|-|
    0|a|b|c|d|
    -|-|-|-|-|
    1|b|b|c|d|
    -|-|-|-|-|
    2|c|c|c|d|
    -|-|-|-|-|
    3|d|d|d|d|
    -|-|-|-|-|
    

    它只显示二维,实际上它的维度与数字一样多。

    字母abcd 显示,您希望在哪些组中获得组合。

    我想说的是,这些群体正在塑造一个n维成长立方体的角落表面。

    所有组合都由这个立方体中所有点的坐标表示(包括内部空间)。请注意,我们的坐标使用离散值(0、1、2..),所以它们是有限的。

    如果你找到了一个规则来扫描那个不断增长的立方体表面上的所有坐标,你就会得到你想要的生成器。

    【讨论】:

    • 听起来很有希望,不知何故。唉,这个简单的想法太少了,无法帮助我理解你的方法到它变得有用的程度:)
    • 我的意思是:是的,当然,这是我想要的值的顺序(在 n 维空间中),但是你能提供一种算法,以一种优雅的方式产生它们(更优雅比我的g,也就是说)?
    • 转念一想,不,对角线不是我想要的顺序。您将 (1,1) 与 (0,2) 和 (2,0) 一起放在组 c 中,但它应该与 (1) 一起放在组 b 中,0) 和 (0,1)。因此,我们宁愿需要一个正方形(立方)形状,而不是对角线顺序,一个正方形包含另一个正方形。尽管如此,一个不错的想法(带有修正),也许这会导致一个更好的解决方案。
    • 根据您的图形方法,我找到了解决方案 :) 查看我的答案(即将推出)。
    • @Alfe 期待。我今天的容量用完了,但不知道会发生什么。顺便说一句,您可能会猜到,my favourite book 是什么
    【解决方案4】:

    我很确定您的函数 f 产生与 itertools.product 相同的值; IE。我认为您可以将f 替换为:

    from itertools import product
    
    def f(width, top):
        for p in product(range(top), repeat=width):
            yield list(p)
    

    要按照您的问题所述订购这些值,您只需使用itertools.groupby

    from itertools import groupby
    from collections import defaultdict
    
    def group_by_max_value(x, y):
        grouped = defaultdict(list)
        for k, g in groupby(f(x, y), key=max):
            grouped[k].extend(list(g))
        return [grouped[k] for k in sorted(grouped.keys())]
    

    修改后的函数定义,无需先生成整个序列即可生成排序值。

    from itertools import groupby
    from collections import defaultdict
    
    def lazy_group_by_max_value(width, top):
        grouped = defaultdict(list)
        # using `itertools.product` with a `range` object
        # guarantees that the product-tuples are emitted
        # in sorted order.
        ps = product(range(top), repeat=width)
        for k, g in groupby(ps, key=max):
            xs = list(g)
            grouped[k].extend(xs)
            # if xs[-1] is of the form (0, 0, .., 0), (1, 1, .., 1), .., (n, n, .., n) etc
            # then we have found all the maxes for `k`, because all future
            # sequences will contain at least one value which is greater than k.
            if set(xs[-1]) == {k}:
                # `pop` (ie. remove) the values from `grouped`
                # which are associated with key `k`.
                all_maxes_for_k = grouped.pop(k)
                for coll in all_maxes_for_k:
                    yield coll
    

    【讨论】:

    • groupby() 是否仅适用于已经分组的块中的值?
    • @superjump OP 不想排序,你的 sortedmax 正在排序。
    • @JanVlcinsky OP 说:“我想要按最大值排序的值”。我误会了吗?
    • @superjump 是的,按最大值排序是一个要求,但也有“先生成所有值,然后在最大值之后再排序”的要求。
    • 我还说过,我不能先收集所有值,然后再应用适当的排序。我刚刚声明了“排序顺序”,以明确我想按什么顺序生成它们。
    【解决方案5】:

    这是一个生成下一个字典排列的算法(顺便说一下,我也喜欢将每个集合作为具有不同基数的数字的想法;例如,以 1 为基数,以 2 为基数等):

        虽然不是所有数字都最大化
            根据最左侧最大值的右侧增加所有数字 到以下算法:
                增加未最大化的最右边数字并设置所有数字 在它的右边归零
            如果它们被最大化,则将第一个数字增加到左侧。如果它被最大化,则将所有数字设置为
            它的权利归零;否则,将最右边的数字设置为最大值,并将中间的数字设置为零。

    Python 代码:

    def nextP(perm,top):
      if all (i == top for i in perm):
        return None
    
      left_max = perm.index(top)
    
      if all (i == top for i in perm[left_max:]):
        perm[left_max - 1] = perm[left_max - 1] + 1
        perm[left_max:] = [0] * (len(perm) - left_max - 1) + ([0] if perm[left_max - 1] == top else [top])
      else:
        right_max = len(perm) - next(x[0] for x in enumerate(perm[left_max + 1:][::-1]) if x[1] < top) - 1
        perm = perm[:right_max] + [perm[right_max] + 1] + [0] * (len(perm) - right_max - 1)
    
      return perm
    

    例子:

    permutation = [0,0,2]
    
    while permutation:
      print permutation
      permutation = nextP(permutation,2)
    
    [0, 0, 2]
    [0, 1, 2]
    [0, 2, 0]
    [0, 2, 1]
    [0, 2, 2]
    [1, 0, 2]
    [1, 1, 2]
    [1, 2, 0]
    [1, 2, 1]
    [1, 2, 2]
    [2, 0, 0]
    [2, 0, 1]
    [2, 0, 2]
    [2, 1, 0]
    [2, 1, 1]
    [2, 1, 2]
    [2, 2, 0]
    [2, 2, 1]
    [2, 2, 2]
    

    【讨论】:

      【解决方案6】:

      首先请注意,您可以使用包含1 作为最大值的唯一解决方案列表轻松生成包含2 作为最大值的唯一解决方案列表。只需增加1 的所有可能组合。例如,从[1,0,1],您只需生成[2,0,1][1,0,2][2,0,2]。这提出了以下解决方案:

      import itertools
      
      def g(n) :
          if n == 0 :
              yield [ 0,0,0 ]
          else :
              for x in g(n-1) : # for each solution containing `1` as the maximum
                  idx = [ i for (i,xi) in enumerate(x) if xi == n-1 ] # locate the '1' to be incremented
                  for j in xrange(1,len(idx)+1) : # increment one '1', then two '1', then three '1', etc
                      for tup in itertools.combinations( idx, j ) : # all possible combinations of j '1'
                          y = list(x)
                          for t in tup : # prepare the new solution
                              y[t] += 1
                          yield y
      

      例子:

      list( g(0) )
      
      [[0, 0, 0]]
      
      list( g(1) )
      
      [[1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 1, 0], [1, 0, 1], [0, 1, 1], [1, 1, 1]]
      
      list( g(2) )
      
      [[2, 0, 0],
       [0, 2, 0],
       [0, 0, 2],
       [2, 1, 0],
       [1, 2, 0],
       [2, 2, 0],
       [2, 0, 1],
       [1, 0, 2],
       [2, 0, 2],
       [0, 2, 1],
       [0, 1, 2],
       [0, 2, 2],
       [2, 1, 1],
       [1, 2, 1],
       [1, 1, 2],
       [2, 2, 1],
       [2, 1, 2],
       [1, 2, 2],
       [2, 2, 2]]
      

      【讨论】:

      • 我真的很喜欢这种方法!我特别喜欢递归。它还会将组合与产品混合(即使没有明确表示),因此这再次暗示这种方法可能是解决此问题的最有效方法! :-)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-03-18
      • 2011-09-14
      • 2023-03-13
      • 1970-01-01
      • 1970-01-01
      • 2011-03-05
      • 1970-01-01
      相关资源
      最近更新 更多