如何构建动态增长的嵌套列表理解?

时间:2013-10-31 19:12:22

标签: python list-comprehension

假设有以下代码检查数字的相乘数字是否等于输入数字:

results = [a for a in range(10) if a == input]
results += [a*b for a in range(10) for b in range(10) if a*b == input]
results += [a*b*c for a in range(10) for b in range(10) for c in range(10) if a*b*c == input]
...

我希望它能够更改,以便在尚未找到结果的情况下动态继续搜索匹配项。所以:

  • 如果一位数字不产生结果,请继续使用两位数字
  • 如果两位数字不产生结果,请继续使用三位数字
  • 等......

我想以优雅的方式做到这一点,即使是单线,如果这不是太渐开线。如果根本没有匹配,我还需要一个中断条件来避免无限循环。例如,如果输入是素数> 10没有结果。休息条件应该是这样的:

if(math.pow(2, countOfDigits) > input):
    return

其中countOfDigits是嵌套列表推导中当前检查的位数。换句话说,我的初始示例的第一行代表countOfDigits == 1,第二行代表countOfDigits == 2,第三行代表countOfDigits == 3

4 个答案:

答案 0 :(得分:7)

哦,继续吧:

next(
    sum(x) 
    for i in range(1, input) # overkill
    for x in itertools.product(range(10), repeat = i) 
    if reduce(operator.mul, x) == input
)

修改:您已将问题更改为返回产品而不是总和,因此请吐出input而不是sum(x)

我不能立即确定您是否想要第一场比赛,或者您想要所有匹配因素的数量等于最小的匹配。如果是后者,你可以通过将input, i吐出这个迭代器来实现,然后使用itertools.groupby根据元组中的第二个值来收集它们,然后只取结果中的第一个值并迭代它以获得所有匹配(尽管,因为您现在输出的input除了可能的长度之外还有其他无趣的感觉。)

答案 1 :(得分:2)

编辑:

你想要的是一个可以迭代的东西,但是直到尽可能晚才起作用。这不是一个列表,因此列表理解是错误的工具。幸运的是,您可以使用生成器表达式。您的代码非常复杂,因此我们希望在itertools中使用标准库中定义的一些帮助程序。

让我们从查看零件的一般情况开始:

[n

   for x0 in range(10) 
   for x1 in range(10)
   ...
   for xn in range(10) 

 if x0 * x1 * ... * xn == input]

我们有三个部分要概括。我们将从嵌套的for循环开始,作为N的参数。为此,我们将使用itertools.product,它需要一系列序列,类似于[range(10), range(10), ... , range(10)]并生成每个可能的项目组合。那些序列。在多次循环遍历序列的特殊情况下,您可以将嵌套深度作为repeat传递,因此我们可以到达:

[n

   for x in itertools.product(xrange(10), repeat=n)

 if x[0] * x[1] * ... * x[n] == input]

对于输出中的总和,我们可以使用sum()将其展平为单个值,对于产品,实际上并不是等价的。我们可以使用reduce中的一个和一个将两个数字相乘的函数,我们可以从另一个标准库中获取:operator.mul

(n
 for x in itertools.product(xrange(10), repeat=n)
 if reduce(operator.mul, x, 1) == input)

到目前为止,我们只需要为n的每个值重复这个内部部分。假设我们想要永远搜索,我们可以使用itertools.count(1)获得无穷无尽的数字序列,最后,我们只需要将这个扁平的序列序列转换为单个序列,我们可以使用itertools.chain.from_iterable

itertools.chain.from_iterable(
     (n
      for x in itertools.product(xrange(10), repeat=n)
      if reduce(operator.mul, x, 1) == input)
     for n in itertools.count(1)
     if 2 ** n > input))
In [32]: input = 32

In [33]: next(itertools.chain.from_iterable(
        (n
         for x in itertools.product(xrange(10), repeat=n)
         if reduce(operator.mul, x, 1) == input)
        for n in itertools.count(1) if 2 ** n > input))
Out[33]: 6

答案 2 :(得分:1)

我认为你不想要列表理解。我认为发电机在这里会更好:

def generate(x):
    for digits in itertools.count(1):
        for i in itertools.product(range(1, 10), repeat=digits):
            if reduce(operator.mul, i) == x:
                yield i
    if (math.pow(2, digits) > x):
        break

然后你做for i in generate(input_number)

(您还需要import itertoolsfrom functools import reduceimport math位于顶部。)

((math.pow(2,digits)> x)只是休息条件。)

答案 3 :(得分:1)

您有多个序列,每个序列可能包含也可能不包含解决方案。将它们转换为生成器,使用itertools将序列“链接”在一起,然后询问结果序列的第一个元素(请注意,不包含该解决方案的序列将为空)。

首先,您需要一种方法来为任何n生成n - 元组的序列。 itertools模块有几个功能,其效果可与嵌套的for循环相媲美。与您的算法匹配的是itertools.product。以下内容生成n个数字的所有元组:

tuples = itertools.product(range(10), repeat=n) 

使用itertools.combinations_with_replacement实际上更好,因为对(4,5)(5,4)都没有必要进行测试。它有类似的语法。所以这里有一个生成器,它会给你一个无限的n元组序列,用于增加n

sequences = ( itertools.product(range(10), repeat=n) for n in itertools.count(1) )

接下来,您希望将序列串联起来(实际上还没有实际遍历它们)到一个序列中。因为这些是生成器,所以只在需要时才会对它们进行评估。

bigchain = itertools.chain.from_iterable(sequences)

bigchain会吐出你需要检查的所有元组。要测试它们,您需要一种方法来乘以任意长度的元组。我们来定义它:

def mytest(x):
    return reduce(operator.mul, x, 1) == target

您现在可以使用此测试来“过滤”此序列,以便仅选择匹配的元组(由于您在组合中包含数字1,因此总是会有很多),然后请求第一个。< / p>

print itertools.islice(ifilter(mytest, bigchain), 1).next()     

我已经更改了你的代码以将你的解决方案作为一个元组返回,因为否则,你只需要找回你要搜索的数字(例如,32) - 这不会告诉你你不知道的任何事情!

在这里,所有在一起:

from itertools import *
import operator

target = 32

sequences = ( combinations_with_replacement(range(10), n) for n in count(1) )
bigchain = chain.from_iterable(sequences)

def mytest(x):
    return reduce(operator.mul, x, 1) == target

print islice(ifilter(mytest, bigchain), 1).next()
# prints (4, 8)

您还可以消除上面的中间变量,并将所有内容组合成一个表达式;但有什么意义呢?