【问题标题】:How to build a dynamically growing nested list comprehension?如何构建动态增长的嵌套列表理解?
【发布时间】:2013-10-31 19:12:21
【问题描述】:

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

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

【问题讨论】:

  • 您的代码和您的文字不一致。您在谈论乘以数字,但您的代码添加了它们。另外,您只想要第一个结果,还是 n 位数字的所有结果(对于产生任何结果的第一个 n)?
  • “完全不匹配”是什么意思?停止条件是什么? (从您提出问题的方式来看,它似乎无限期地继续下去。
  • 感谢您的 cmets。我编辑了我的帖子以使事情更清晰
  • 我不明白你最后对countOfDigits的定义。
  • 我也很好奇这样做的目的是什么

标签: python list-comprehension


【解决方案1】:

哦,那就继续吧:

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,除了它的长度之外,这有点无趣)。

【讨论】:

  • @sweeneyrod 为什么不呢?看起来合法。
  • @Hyperboreus:我认为他的意思是他(非常明智地)不写这样的代码 ;-)
  • @Alp:只需将第一个范围的上限更改为 int(math.ceil(math.log(input+1,2))) 或其他任何内容。我懒得计算出严格的界限,你的问题的第一个版本只是要求 a 限制,不一定是 good 限制:-)
  • 而且我想如果你可以从 int(math.floor(math.log(input, 9))) 这样的东西开始,那么从 1 开始是没有意义的。
  • @sweeneyrod:“一些奇怪的没有赋值或循环体的 Python 版本”——欢迎来到“用命令式语言以函数式风格编写......为了乐趣和利润!” :-)
【解决方案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

【讨论】:

  • OP 改变了他的问题,现在他只用input 本身填充他的列表。请再次查看问题。
  • 很好的解释。无需再次检查我的问题,这很有帮助
  • 还有一个问题:如果将input设置为31next会尝试在无限循环中寻找结果,因为没有结果
  • 什么时候该放弃?
  • 仍在尝试找出一个好的休息条件,2 ** n > input 是第一次尝试
【解决方案3】:

您有几个序列,每个序列可能包含也可能不包含解决方案。将它们转换为生成器,使用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),然后询问第一个。

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)

你也可以去掉上面的中间变量,把所有的东西组合成一个表达式;但有什么意义呢?

【讨论】:

  • 谢谢,但缺少最重要的部分:如果需要,动态生成更多序列。感谢其他答案,已经弄清楚了
【解决方案4】:

我认为您不需要列表理解。我认为这里有一个生成器会更好:

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) 只是中断条件。)

【讨论】:

  • 出于好奇:是否可以将其转换为单线?
  • @Alp 是的(请参阅 Steve Jessop 的回答,基本上是采用不同的格式)。但我不认为你想要,恕我直言,这样的东西更具可读性。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-12-24
  • 1970-01-01
  • 2016-08-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多