简体   繁体   English

如何建立一个动态增长的嵌套列表理解?

[英]How to build a dynamically growing nested list comprehension?

Assuming there is the following code which checks if the multiplied digits of a number equal an input number: 假设有以下代码检查数字的乘数是否等于输入数字:

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]
...

I want it to change so that it dynamically continues to search for a match if no result was found yet. 我希望更改它,以便在没有找到结果的情况下继续动态搜索匹配项。 So: 所以:

  • If one-digit numbers don't yield a result, continue with two-digit numbers 如果一位数字没有结果,请继续两位数字
  • If two-digit numbers don't yield a result, continue with three-digit numbers 如果两位数没有结果,请继续输入三位数
  • and so on ... 等等 ...

I'd like to do that in an elegant way, even a one-liner if that's not too involute. 我想以一种优雅的方式做到这一点,即使这种方式不太复杂,也可以采用单线方式。 I also would need a break condition to avoid infinite loops if there is no match at all. 如果根本没有匹配项,我还需要一个中断条件来避免无限循环。 For example if the input is a prime number > 10 there is no result. 例如,如果输入的质数> 10,则没有结果。 The break condition should be something like: 中断条件应类似于:

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

where countOfDigits is the number of digits that are currently checked in the nested list comprehension. 其中countOfDigits是嵌套列表countOfDigits中当前检查的位数。 In other words, the first line of my initial example represents countOfDigits == 1 , the second line countOfDigits == 2 and the third line countOfDigits == 3 . 换句话说,我的初始示例的第一行代表countOfDigits == 1 ,第二行countOfDigits == 2和第三行countOfDigits == 3

Oh, go on then: 哦,那就继续:

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
)

Edit: you've changed the question to return the product rather than the sum, so spit out input instead of sum(x) . 编辑:您已更改问题以返回乘积而不是总和,所以吐出input而不是sum(x)

I'm not immediately sure whether you want the first match, or you want all matches where the number of factors is equal to the smallest possible. 我不确定您是否要进行第一个匹配,还是想要所有匹配都等于最小因子的匹配。 If the latter, you can do it by spitting input, i out of this iterator, then use itertools.groupby to collect them according to the second value in the tuple, then take only the first value in the result of that, and iterate over it to get all matches (although, since you're now outputting input that's kind of uninteresting other than maybe for its length). 如果是后者,则可以通过将input, i从此迭代器中吐出,然后使用itertools.groupby根据元组中的第二个值来收集它们,然后仅取结果中的第一个值并进行迭代来实现它可以获取所有匹配项(尽管,由于您现在输出的input可能与长度无关,所以没有意思)。

Edit: 编辑:

What you want is a thing which can be iterated over, but which does no work until as late as possible. 您想要的是可以迭代的东西,但是直到尽可能晚才起作用。 That's not a list, and so a list comprehension is the wrong tool. 那不是列表,因此列表理解是错误的工具。 Fortunately, you can use a generator expression . 幸运的是,您可以使用生成器表达式 Your code is quite elaborate, so we'll want to probably use some helpers defined in the standard library, in itertools . 您的代码非常复杂,因此我们可能希望使用在标准库itertools定义的一些帮助程序。

lets start by looking at the general case of your parts: 让我们先看一下零件的一般情况:

[n

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

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

We have three parts to generalize. 我们可以归纳为三个部分。 We'll start with the nested for loops, as arguments of N. for this we'll use itertools.product , It takes a sequence of sequences, something like [range(10), range(10), ... , range(10)] and produces every possible combination of items out of those sequences. 我们将从嵌套的for循环开始,作为N的参数。为此,我们将使用itertools.product ,它需要一个序列序列,例如[range(10), range(10), ... , range(10)]并从这些序列中产生所有可能的项目组合。 In the special case of looping over a sequence multiple times, you can just pass the depth of nesting as repeat , so we can get to: 在多次遍历一个序列的特殊情况下,您可以将嵌套深度作为repeat传递,这样我们就可以:

[n

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

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

For the sum in the output, we can flatten it to a single value with sum() , for the product, there's not really an equivalent. 对于输出中的总和,我们可以使用sum()将其展平为单个值,对于乘积,实际上并没有等效项。 We can make one out of reduce and a function that multiplies two numbers together, which we can get from another standard library: operator.mul : 我们可以用reduce做成一个函数,并将两个数字相乘,这可以从另一个标准库中获得: operator.mul

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

So far so good, now we just need to repeat this inner part for every value of n. 到目前为止,到目前为止,我们只需要对每个n值重复此内部部分。 Supposing we want to search forever, we can get an unending sequence of numbers with itertools.count(1) , and Finally, we just need to turn this flatten sequence of sequences into a single sequence, for which we can use itertools.chain.from_iterable 假设我们要永远搜索,我们可以使用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

I don't think you want a list comprehension. 我认为您不希望列表理解。 I think a generator would be better here: 我认为发电机在这里会更好:

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

Then you do for i in generate(input_number) . 然后,您for i in generate(input_number)做。

(You also need import itertools , from functools import reduce and import math at the top). (您还需要from functools import reduce import itertools到顶部的from functools import reduceimport math )。

((math.pow(2, digits) > x) is just the break condition.) ((math.pow(2,digits)> x)只是中断条件。)

You have several sequences, each of which may or may not contain the solution. 您有几个序列,每个序列可能包含也可能不包含解决方案。 Convert them to generators, use itertools to "chain" the sequences together, and then ask for the first element of the resulting sequence (notice that a sequence that does not contain the solution will be empty). 将它们转换为生成器,使用itertools将序列“链接”在一起,然后询问结果序列的第一个元素(请注意,不包含解决方案的序列将为空)。

First of all, you need a way to generate sequences of n -tuples for any n . 首先,您需要一种为任何n生成n元组的序列的方法。 The itertools module has several functions that have effect comparable to nested for-loops. itertools模块具有几个功能,这些功能的效果可与嵌套的for循环相比。 The one that matches your algorithm is itertools.product . 与您的算法匹配的一个是itertools.product The following generates all tuples of n digits: 以下生成所有n位元组:

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

It's actually better to use itertools.combinations_with_replacement , because there's no point testing both (4,5) and (5,4) . 使用itertools.combinations_with_replacement实际上更好,因为(4,5)(5,4)都没有测试点。 It has similar syntax. 它具有相似的语法。 So here's a generator that will give you an infinite sequence of n-tuples, for increasing n : 所以这是一个生成器,它将为您提供无限个n元组序列,以增加n

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

Next, you want to string the sequences together (without actually traversing them yet), into a single sequence. 接下来,您想将序列串在一起(实际上尚未遍历它们),成为一个序列。 Because these are generators, they will only be evaluated if needed. 由于这些是生成器,因此仅在需要时才对其进行评估。

bigchain = itertools.chain.from_iterable(sequences)

bigchain will spit out all tuples you need to check. bigchain将吐出您需要检查的所有元组。 To test them, you need a way to multiply a tuple of arbitrary length. 为了测试它们,您需要一种将任意长度的元组相乘的方法。 Let's define it: 让我们定义一下:

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

You can now use this test to "filter" this sequence to select only tuples that match (there will always be many, since you include digit 1 in your combinations), then ask for the first one. 现在,您可以使用此测试来“过滤”此序列,以仅选择匹配的元组(由于组合中包含数字1 ,因此将总是有很多元组),然后请求第一个。

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

I've changed your code to return your solution as a tuple, because otherwise, well, you'll just get back the number you were searching for (eg, 32 )-- which doesn't tell you anything you didn't know! 我更改了代码,以元组形式返回解决方案,因为否则,您会得到想要搜索的数字(例如32 ),但不会告诉您您不知道的任何内容!

Here it is, all together: 这是一起:

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)

You could also eliminate the intermediate variables in the above and combine everything into one expression; 您还可以消除上面的中间变量,并将所有内容组合为一个表达式。 but what would be the point? 但是有什么意义呢?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM