【问题标题】:Python - building sublist that meets certain criteria from huge number of combinationsPython - 从大量组合中构建满足某些标准的子列表
【发布时间】:2012-12-11 18:53:32
【问题描述】:

阅读了很长时间,我第一次找不到我正在研究的东西的答案。

我有一个包含 93 个字符串的列表,每个字符串长度为 6 个字符。从这 93 个字符串中,我想确定一组 20 个字符串,它们都符合相对于集合中其他字符串的特定标准。虽然 itertools.combinations 会给我所有可能的组合,但并非所有条件都值得检查。

例如,如果 [list[0], list[1], etc] 因为 list[0] 和 list[1] 不能在一起而失败,那么不管其他 18 个字符串是什么,集合都会失败每次都是这样,那是一大堆浪费的检查。

目前我有 20 个嵌套的 for 循环,但似乎必须有更好/更快的方法来做到这一点。:

for n1 in bclist:
    building = [n1]
    n2bclist = [bc for bc in bclist if bc not in building]
    for n2 in n2bclist:              #this is the start of what gets repeated 19 times
        building.append(n2)
        if test_function(building): #does set fail? (counter intuitive, True when fail, False when pass)
            building.remove(n2)
            continue
        n3bclist = [bc for bc in bclist if bc not in building]
        #insert the additional 19 for loops, with n3 in n3, n4 in n4, etc
        building.remove(n2)

在第 20 个 for 循环中有打印语句来提醒我是否存在一组 20 个。 for 语句至少允许我在单次加法失败时提前跳过集合,但不记得更大的组合何时失败:

例如 [list[0], list[1]] 失败,所以跳到通过的 [list[0], [list[2]]。接下来是[list[0], list[2], list[1]],它将失败,因为 0 和 1 再次在一起,因此它将移动到 [list[0], list[2], list[3]],这可能会或不会通过。我担心的是最终它也会测试:

  • [list[0], list[3], list[2]]
  • [list[2], list[0], list[3]]
  • [list[2], list[3], list[0]]
  • [list[3], list[0], list[2]]
  • [list[3], list[2], list[0]]

所有这些组合的结果都与之前的组合相同。基本上,我交换了 itertools.combinations 的恶魔测试所有我知道失败的集合组合,因为早期值失败了 for 循环的恶魔,当我不关心它们的顺序时,它们将值的顺序视为一个因素。这两种方法都显着增加了我的代码完成所需的时间。

任何关于如何摆脱恶魔的想法将不胜感激。

【问题讨论】:

  • itertools.combinations 不重复元素。 itertools.permutations 做你认为 combinations 正在做的事情。 combinations(range(4), 3) --> 012 013 023 123
  • 订单重要吗?例如,[list[0], list[3], list[2]] 是否有可能通过但 [list[2], list[3], list[0]] 失败?
  • @Stuart 不,顺序无关紧要。您的两个示例都将失败或都将通过。
  • @GP89 ​​抱歉,我可能不够清楚。我的 for 循环目前模仿 itertoools.permutations 这是一个问题,因为顺序无关紧要,因此测试了许多不需要测试的东西。 itertools.combinations 不重复元素,但会测试 012 和 013,即使 01 是导致再次测试不需要测试的东西失败的原因。

标签: python for-loop itertools


【解决方案1】:

使用您当前的方法,但也要跟踪索引,以便在您的内部循环中可以跳过您已经检查过的元素:

bcenum = list(enumerate(bclist))
for i1, n1 in bcenum:
    building = [n1]
    for i2, n2 in bcenum[i1+1:]:              #this is the start of what gets repeated 19 times
        building.append(n2)
        if test_function(building): #does set fail? (counter intuitive, True when fail, False when pass)
            building.remove(n2)
            continue
        for i3, n3 in bcenum[i2+1:]:
            # more nested loops
        building.remove(n2)

【讨论】:

  • 到目前为止喜欢这个解决方案。据我所知,这提供了非冗余测试(即不会同时测试 [0,2,3,4] 和 [4,3,2,0])并且不会浪费对失败集的测试(即 [0 ,1] = 失败,因此不测试 [0,1,2])。仍在评估其他答案。
  • 做了一些额外的改进,但这在合理的时间内完成了工作,并且需要对我现有的代码进行最少的更改。其他变化:
  • if (len(building) + len(bcenum[i#:] < 20: break 在测试功能测试之前添加。而不是追加、测试和删除:if test_function(building + [n#]): continue
【解决方案2】:
def gen(l, n, test, prefix=()):
  if n == 0:
    yield prefix
  else:
    for i, el in enumerate(l):
      if not test(prefix + (el,)):
        for sub in gen(l[i+1:], n - 1, test, prefix + (el,)):
          yield sub

def test(l):
  return sum(l) % 3 == 0 # just a random example for testing

print list(gen(range(5), 3, test))

这将从l 中选择基数n 的子集,使得test(subset) == False

它试图避免不必要的工作。但是,鉴于从 93 个元素中选择 20 个元素有 1e20 种方法,您可能需要重新考虑您的整体方法。

【讨论】:

    【解决方案3】:

    你可以利用问题的两个方面:

    1. 顺序无关紧要
    2. 如果test_function(L)True,那么L 的任何子列表中的test_function 也将是True

    您还可以通过处理索引 0-92 而不是 list[0]-list[92] 来简化一些事情 - 只有在 test_function 内,我们才可能关心列表的内容。

    下面的代码首先找到可行的配对,然后是四组、八组和十六组。最后,它找到了 16 和 4 的所有可行组合来获得 20 的列表。但是有超过 100,000 组 8 组,所以它仍然太慢,我放弃了。可能您可以按照相同的方式做一些事情,但使用 itertools 加快速度,但可能还不够。

    target = range(5, 25)
    def test_function(L):
        for i in L:
            if not i in target:
                return True
    def possible_combos(A, B):
        """
        Find all possible pairings of a list within A and a list within B
        """
        R = []
        for i in A:
            for j in B:
                if i[-1] < j[0] and not test_function(i + j):
                    R.append(i + j)
        return R
    def possible_doubles(A):
        """
        Find all possible pairings of two lists within A
        """
        R = []
        for n, i in enumerate(A):
            for j in A[n + 1:]:
                if i[-1] < j[0] and not test_function(i + j):
                    R.append(i + j)
        return R
    # First, find all pairs that are okay
    L = range(92) 
    pairs = []
    for i in L:
        for j in L[i + 1:]:
            if not test_function([i, j]):
                pairs.append([i, j])
    
    # Then, all pairs of pairs
    quads = possible_doubles(pairs)
    print "fours", len(quads), quads[0]
    # Then all sets of eight, and sixteen
    eights = possible_doubles(quads)
    print "eights", len(eights), eights[0]
    sixteens = possible_doubles(eights)
    print "sixteens", len(sixteens), sixteens[0]
    
    # Finally check all possible combinations of a sixteen plus a four
    possible_solutions = possible_combos(sixteens, fours)
    print len(possible_solutions), possible_solutions[0]
    

    编辑:我找到了一个更好的解决方案。首先,识别范围 (0-92) 内符合test_function 的所有值对,保持这些对的顺序。大概第一对的第一个值必须是解决方案的第一个值,最后一对的第二个值必须是解决方案的最后一个值(但是检查...对于test_function,这个假设是否成立?如果这个不是一个安全的假设,那么您将需要对所有可能的开始和结束值重复find_paths)。然后找到从第一个值到最后一个值的路径,该路径有 20 个值,并且也符合 test_function

    def test_function(S):
        for i in S:
            if not i in target:
                return True
        return False
    
    def find_paths(p, f):
        """ Find paths from end of p to f, check they are the right length,
            and check they conform to test_function
        """
        successful = []
        if p[-1] in pairs_dict:
            for n in pairs_dict[p[-1]]:
                p2 = p + [n]
                if (n == f and len(p2) == target_length and
                    not test_function(p2)):
                    successful.append(p2)
                else:
                    successful += find_paths(p2, f)
        return successful
    
    list_length = 93              # this is the number of possible elements
    target = [i * 2 for i in range(5, 25)] 
        # ^ this is the unknown target list we're aiming for...
    target_length = len(target)   # ... we only know its length
    L = range(list_length - 1)
    pairs = []
    for i in L:
        for j in L[i + 1:]:
            if not test_function([i, j]):
                pairs.append([i, j])
    firsts = [a for a, b in pairs]
    nexts = [[b for a, b in pairs if a == f] for f in firsts]
    pairs_dict = dict(zip(firsts, nexts))
    print "Found solution(s):", find_paths([pairs[0][0]], pairs[-1][1])
    

    【讨论】:

      【解决方案4】:

      您的解决方案应该基于itertools.combinations,因为这将解决订购问题;短路滤波比较容易解决。

      递归解

      让我们快速回顾一下如何实现combinations 的工作原理;最简单的方法是采用嵌套循环的方法并将其转换为递归样式:

      def combinations(iterable, r):
          pool = tuple(iterable)
          for i in range(0, len(pool)):
              for j in range(i + 1, len(pool)):
                  ...
                      yield (i, j, ...)
      

      转换为递归形式:

      def combinations(iterable, r):
          pool = tuple(iterable)
          def inner(start, k, acc):
              if k == r:
                  yield acc
              else:
                  for i in range(start, len(pool)):
                      for t in inner(i + 1, k + 1, acc + (pool[i], )):
                          yield t
          return inner(0, 0, ())
      

      现在应用过滤器很容易:

      def combinations_filterfalse(predicate, iterable, r):
          pool = tuple(iterable)
          def inner(start, k, acc):
              if predicate(acc):
                  return
              elif k == r:
                  yield acc
              else:
                  for i in range(start, len(pool)):
                      for t in inner(i + 1, k + 1, acc + (pool[i], )):
                          yield t
          return inner(0, 0, ())
      

      让我们检查一下:

      >>> list(combinations_filterfalse(lambda t: sum(t) % 2 == 1, range(5), 2))
      [(0, 2), (0, 4), (2, 4)]
      

      迭代解决方案

      the documentation 中列出的itertools.combinations 的实际实现使用了迭代循环:

      def combinations(iterable, r):
          pool = tuple(iterable)
          n = len(pool)
          if r > n:
              return
          indices = range(r)
          yield tuple(pool[i] for i in indices)
          while True:
              for i in reversed(range(r)):
                  if indices[i] != i + n - r:
                      break
              else:
                  return
              indices[i] += 1
              for j in range(i+1, r):
                  indices[j] = indices[j-1] + 1
              yield tuple(pool[i] for i in indices)
      

      为了优雅地适应谓词,有必要稍微重新排序循环:

      def combinations_filterfalse(predicate, iterable, r):
          pool = tuple(iterable)
          n = len(pool)
          if r > n or predicate(()):
              return
          elif r == 0:
              yield ()
              return
          indices, i = range(r), 0
          while True:
              while indices[i] + r <= i + n:
                  t = tuple(pool[k] for k in indices[:i+1])
                  if predicate(t):
                      indices[i] += 1
                  elif len(t) == r:
                      yield t
                      indices[i] += 1
                  else:
                      indices[i+1] = indices[i] + 1
                      i += 1
              if i == 0:
                  return
              i -= 1
              indices[i] += 1
      

      再次检查:

      >>> list(combinations_filterfalse(lambda t: sum(t) % 2 == 1, range(5), 2))
      [(0, 2), (0, 4), (2, 4)]
      >>> list(combinations_filterfalse(lambda t: t == (1, 4), range(5), 2))
      [(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (2, 3), (2, 4), (3, 4)]
      >>> list(combinations_filterfalse(lambda t: t[-1] == 3, range(5), 2))
      [(0, 1), (0, 2), (0, 4), (1, 2), (1, 4), (2, 4)]
      >>> list(combinations_filterfalse(lambda t: False, range(5), 2))
      [(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
      >>> list(combinations_filterfalse(lambda t: False, range(5), 0))
      [()]
      

      比较

      事实证明,递归解决方案不仅更简单,而且速度更快:

      In [33]: timeit list(combinations_filterfalse_rec(lambda t: False, range(20), 5))
      10 loops, best of 3: 24.6 ms per loop
      
      In [34]: timeit list(combinations_filterfalse_it(lambda t: False, range(20), 5))
      10 loops, best of 3: 76.6 ms per loop
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-02-14
        • 1970-01-01
        • 1970-01-01
        • 2020-08-17
        • 1970-01-01
        相关资源
        最近更新 更多