【问题标题】:Finding two integers that multiply to 20. Can I make this code more "pythonic"?找到两个乘以 20 的整数。我可以让这段代码更“pythonic”吗?
【发布时间】:2019-06-30 13:37:39
【问题描述】:

我编写了这段代码,它在所述列表(在本例中为 [2,4,5,1,6,40,-1])中找到两个乘以 20 的整数。一开始我有点卡住,但添加一个函数解决了我的问题。我把这段代码展示给我的一个程序员朋友,他说我可以让这段代码更“pythonic”,但我不知道怎么做。

代码如下:

num_list = [2,4,5,1,6,40,-1]

def get_mult_num(given_list):
    for i in given_list:
        for j in range(i+1, len(given_list)): #for j not to be == i and to be in the list
            mult_two_numbers = i * j
            if mult_two_numbers == 20:
                return i,j

print(get_mult_num(num_list)) 

【问题讨论】:

  • 我不认为这是你想要的:for i in given_list: for j in range(i+1, len(given_list)): ... 使用 print i,j 来查看结果。 i 不是given_list 的索引,它是它的一个元素!

标签: python python-3.x


【解决方案1】:
[(i,j) for i in num_list for j in num_list if i<j and i*j==20]

【讨论】:

  • 这完成了所需工作量的两倍多。您在长度为 N 的数字列表上循环 N ** 2 次,但只有 (N choose 2) 组合。在您的情况下,这是 49 次迭代,只需要 21 次,不到一半。你取两边都有 N 的整个正方形,但你只需要考虑那个正方形的一半并忽略对角线,所以 (N * (N - 1)) // 2.
  • @MartijnPieters:a) 条件if i&lt;j 排除其中一个三角形 b) 除非您确定它不包含sqrt(20),否则您不能忽略对角线,除非您想当sqrt(N) 是一个整数时写特殊情况,这很愚蠢。所以一般来说应该是`if i
  • @smci:这里的假设是输入的整数列表是唯一的,您必须从中选择两个不同的值。这意味着没有i == j 案例。
  • @MartijnPieters:好的,但问题从未真正说明数字必须是不同的。通俗地说,我们可能会这样解释它,但它并不禁止它。
【解决方案2】:

我可以考虑使用列表理解。如果它们存在于给定的列表中,这也有助于找到多个这样的对。

num_list = [2,4,5,1,6,40,-1]

mult_num = [(num_list[i],num_list[j]) for i in range(len(num_list)) for j in range(i+1, len(num_list)) if num_list[i]*num_list[j] == 20]
print mult_num

输出:

[(4, 5)]

【讨论】:

  • 注意:您的实现与问题中的代码有相同的错误;例如,用num_list = [2, 10] 试试这个。
  • @MartijnPieters,感谢您检查我的答案并指出错误。我已经修复它并更新了我的答案。
【解决方案3】:

这是我的看法,它使用enumerate

def get_mult_num(given_list):
    return [
        item1, item2
        for i, item1 in enumerate(given_list)
        for item2 in given_list[:i]
        if item1*item2 == 20
    ]

我认为您的朋友可能会暗示使用comprehensions,因为它可以使代码更清晰(有时不会)。

【讨论】:

    【解决方案4】:

    我不一定认为它是“unpythonic”,您正在使用标准 Python 习惯用法来循环数据并生成单个结果或NonePythonic 一词含糊不清,是一个在"I know it when I see it" 参数中受损的主题。

    并不是说你产生了一个正确的实现。当i 循环given_numbers 时,j 循环从i + 2len(given_numbers) 的整数,将given_list 的值与索引混合?对于您的样本输入,您从半开范围 [4, 7), [6, 7), [7, 7) (空), [3, 7), [8, 7) ( empty)、[42, 7) (empty) 和 [1, 7) 分别。它产生正确的答案完全是运气,而不是由于正确性;如果你给你的函数列表[2, 10],它将找不到解决方案!你想循环given_numbers再次,受限于切片,或者从i的当前索引开始生成索引,但是你的外部循环也需要添加一个enumerate()调用:

    for ii, i in enumerate(given_numbers):
        for j in given_numbers[ii + 1:]:
            # ...
    

    for ii, i in enumerate(given_numbers):
        for jj in range(ii + 1, len(given_numbers)):
            j = given_numbers[jj]
            # ...
    

    所有这一切都没有尽可能高效; Python 标准库为您提供了生成 i, j 对的工具,无需嵌套 for 循环或切片或其他形式的过滤。

    您的双循环应生成整数输入的combinations,因此请使用itertools.combinations() object 生成唯一的i, j 对:

    from itertools import combinations
    
    def get_mult_num(given_list):
        return [(i, j) for i, j in combinations(given_list, 2) if i * j == 20]
    

    这假设可以有零个或多个这样的解决方案,而不仅仅是一个解决方案。

    如果您只需要第一个结果或None,您可以使用next() function

    def get_mult_num(given_list):
        multiplies_to_20 = (
            (i, j) for i, j in combinations(given_list, 2)
            if i * j == 20)
        return next(multiplies_to_20, None)
    

    接下来,您可能想要反转问题,而不是产生所有可能的组合。如果你把given_list 变成一个集合,你可以简单地检查目标数字 20 是否可以被任何给定的数字干净地整除,并且除法的结果更大 并且 也是一组数字中的一个整数。这会在线性时间内为您提供答案。

    您可以通过除以小于目标值平方根的数字来进一步限制搜索,因为您不会在输入数字中找到更大的值来匹配(给定一个数字n,它是平方根@ 987654350@,根据定义,s * (s + 1) 将大于n)。

    如果我们将目标编号的参数添加到函数并使其成为generator function,那么您会得到:

    def gen_factors_for(target, numbers):
        possible_j = set(numbers)
        limit = abs(target) ** 0.5
        for i in numbers:
            if abs(i) < limit and target % i == 0:
                j = target // i
                if j in possible_j and abs(j) > abs(i):
                    yield i, j
    

    这种方法比测试所有排列要快很多,尤其是当您需要找到所有可能的因素时。请注意,我在这里制作了两个函数生成器以平衡比较:

    >>> import random, operator
    >>> from timeit import Timer
    >>> def gen_factors_for_division(target, numbers):
    ...     possible_j = set(numbers)
    ...     limit = abs(target) ** 0.5
    ...     for i in numbers:
    ...         if abs(i) < limit and target % i == 0:
    ...             j = target // i
    ...             if j in possible_j and abs(j) > abs(i):
    ...                 yield i, j
    ...
    >>> def gen_factors_for_combinations(target, given_list):
    ...     return ((i, j) for i, j in combinations(given_list, 2) if i * j == target)
    ...
    >>> numbers = [random.randint(-10000, 10000) for _ in range(100)]
    >>> targets = [operator.mul(*random.sample(set(numbers), 2)) for _ in range(5)]
    >>> targets += [t + random.randint(1, 100) for t in targets]  # add likely-to-be-unsolvable numbers
    >>> for (label, t) in (('first match:', 'next({}, None)'), ('all matches:', 'list({})')):
    ...     print(label)
    ...     for f in (gen_factors_for_division, gen_factors_for_combinations):
    ...         test = t.format('f(t, n)')
    ...         timer = Timer(
    ...             f"[{test} for t in ts]",
    ...             'from __main__ import targets as ts, numbers as n, f')
    ...         count, total = timer.autorange()
    ...         print(f"{f.__name__:>30}: {total / count * 1000:8.3f}ms")
    ...
    first match:
          gen_factors_for_division:    0.219ms
      gen_factors_for_combinations:    4.664ms
    all matches:
          gen_factors_for_division:    0.259ms
      gen_factors_for_combinations:    3.326ms
    

    请注意,我生成了 10 个不同的随机目标,以尽量避免任何一种方法都遇到幸运的最佳情况。

    【讨论】:

    • 感谢您的帮助和非常清晰的解释。我一定会适应的。
    • 如果列表的范围是 100,我该如何调整我的代码使用组合来返回乘以所述目标值的值?
    • @MarcelloFabrizio:这远远超出了现在的“pythonic”,现在我们正在编写数学难题解决方案。 :-) 我已经更新了我的答案以提出一种方法。
    • 我用新代码编辑了我的问题。我实现了你解释的组合。
    • @MarcelloFabrizio:这真的不需要;我们正在尝试为未来的访问者建立一个问题和答案的存储库,您的问题更适合实现该目标而无需添加。我已经回滚了编辑。
    【解决方案5】:

    您可以通过使用 itertools.combinations 而不是嵌套循环来查找所有数字对,从而使其更加 Pythonic。并非总是如此,但通常像 for i in range(len(L)): 那样迭代索引比像 for v in L: 那样直接迭代值更少 Pythonic。

    Python 还允许您通过 yield 关键字将您的函数变成一个生成器,这样您就可以通过迭代函数调用来获得每一对,而不是只返回乘以 20 的第一对。

    import itertools
    
    def factors(x, numbers):
        """ Generate all pairs in list of numbers that multiply to x.
        """
        for a, b in itertools.combinations(numbers, 2):
            if a * b == x:
                yield (a, b)
    
    numbers = [2, 4, 5, 1, 6, 40, -1]
    for pair in factors(20, numbers):
        print(pair)
    

    【讨论】:

      【解决方案6】:

      我想出了这个。它稍微反转了该方法,因为它在num_list 中搜索迭代值val 将乘以20 的所需配对伙伴。这使得代码更简单,不需要导入,即使它不是最有效的方式。

      for val in num_list:    
          if 20 / val in num_list:
              print(val, int(20/val))
      

      【讨论】:

        猜你喜欢
        • 2014-05-01
        • 1970-01-01
        • 2010-11-03
        • 1970-01-01
        • 2020-03-05
        • 2020-04-26
        • 2011-10-27
        • 1970-01-01
        • 2011-04-29
        相关资源
        最近更新 更多