【问题标题】:Filtering out a generator过滤生成器
【发布时间】:2017-05-10 17:22:07
【问题描述】:

从生成器中过滤掉一些子集的最佳方法是什么。例如,我有一个字符串“1023”并且想要生成每个数字的所有可能组合。所有组合将是:

['1', '0', '2', '3']
['1', '0', '23']
['1', '02', '3']
['1', '023']
['10', '2', '3']
['10', '23']
['102', '3']
['1023']

我对包含前导 0 的任何项目的子集不感兴趣,因此有效的是:

['1', '0', '2', '3']
['1', '0', '23']
['10', '2', '3']
['10', '23']
['102', '3']
['1023']

我有两个问题。

1) 如果使用生成器,过滤掉前导零的最佳方法是什么。目前,我生成所有组合,然后循环遍历它,只有在子集有效时才继续。为简单起见,我只打印示例代码中的子集。假设创建的生成器很长,或者如果它包含很多无效的子集,那么循环整个生成器几乎是一种浪费。有没有办法在生成器看到无效项目(前导零)时停止生成器,然后将其从“allCombinations”中过滤掉

2) 如果上述不存在,那么生成这些组合的更好方法是什么(忽略带前导零的组合)。

使用生成器的代码:

import itertools

def isValid(subset):         ## DIGITS WITH LEADING 0 IS NOT VALID
    valid = True
    for num in subset:
        if num[0] == '0' and len(num) > 1:
            valid = False
            break

    return valid

def get_combinations(source, comb):
    res = ""
    for x, action in zip(source, comb + (0,)):
        res += x
        if action == 0:
            yield res
            res = ""

digits = "1023"
allCombinations = [list(get_combinations(digits, c)) for c in itertools.product((0, 1), repeat=len(digits) - 1)]


for subset in allCombinations:   ## LOOPS THROUGH THE ENTIRE GENERATOR
    if isValid(subset):
        print(subset)

【问题讨论】:

  • 你见过filter内置函数吗?
  • @aryamccarthy:在 Python 2 中,filter 是急切的,它返回一个列表,而不是生成器。
  • 在 Python 2 中,itertools.ifilter 用于惰性求值。我没有看到 Python 2 标记。
  • 正在寻找 python 3 的解决方案,但 python 2 也可以。我如何将“过滤器”集成到“allCombinations”的列表理解中?

标签: python filter generator


【解决方案1】:

过滤诸如“无前导零”之类的简单而明显的条件,可以在组合构建级别更有效地完成。

def generate_pieces(input_string, predicate):
    if input_string:
        if predicate(input_string):
            yield [input_string]
        for item_size in range(1, len(input_string)+1):
            item = input_string[:item_size]
            if not predicate(item):
                continue
            rest = input_string[item_size:]
            for rest_piece in generate_pieces(rest, predicate):
                yield [item] + rest_piece

生成每一个剪辑组合,只要它甚至都不好笑:

>>> list(generate_pieces('10002', lambda x: True))
[['10002'], ['1', '0002'], ['1', '0', '002'], ['1', '0', '0', '02'], ['1', '0', '0', '0', '2'], ['1', '0', '00', '2'], ['1', '00', '02'], ['1', '00', '0', '2'], ['1', '000', '2'], ['10', '002'], ['10', '0', '02'], ['10', '0', '0', '2'], ['10', '00', '2'], ['100', '02'], ['100', '0', '2'], ['1000', '2']]

只有那些没有片段有前导零的:

>>> list(generate_pieces('10002', lambda x: not x.startswith('0')))
[['10002'], ['1000', '2']]

从零开始的子字符串不会被考虑用于递归步骤。

【讨论】:

  • 是的,在构建过程中进行过滤会更有效。感谢您的示例代码
  • “0”实际上是有效的。只有以 0 开头的项目,即 len 至少为 2 或更多且以 '0' 开头的项目是无效的。
  • lambda x: x == '0' or not x.startswith('0') 工作。
【解决方案2】:

一种常见的解决方案是在使用yield 之前尝试过滤。我已经给你一个在 yield 之前过滤的例子:

import itertools

def my_gen(my_string):

    # Create combinations
    for length in range(len(my_string)):
        for my_tuple in itertools.combinations(my_string, length+1):

            # This is the string you would like to output
            output_string = "".join(my_tuple)

            # filter here:
            if output_string[0] != '0':
                yield output_string


my_string = '1023'
print(list(my_gen(my_string)))

编辑:在生成器理解替代中添加

import itertools

my_string = '1023'
my_gen = ("".join(my_tuple)[0] for length in range(len(my_string))
                      for my_tuple in itertools.combinations(my_string, length+1)
                      if "".join(my_tuple)[0] != '0')

【讨论】:

  • 您的解决方案没有给出我想要的答案,但我认为您只是想在“屈服”之前向我展示一个过滤器。我在想,但我认为我做不到,因为我对“allCombinations”有一个列表理解。想坚持使用列表理解来提高速度,但也许我不能
  • 列表推导速度很快,因为它们在 C/C++ 中为列表预先分配了空间。我不认为生成器理解比生成器函数更快,因为没有要填充的列表 - 项目一次计算一个。
猜你喜欢
  • 1970-01-01
  • 2011-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-11
相关资源
最近更新 更多