【问题标题】:Cyclically reduce a list of operands to a single result循环地将操作数列表缩减为单个结果
【发布时间】:2018-12-03 14:22:01
【问题描述】:

(这是另一个问题的重新整理、自我回答的版本,由于没有很好地提出,该问题已关闭。)

我有一个整数列表:

numbers = [1, 2, 3, 4, 5, 6]

我的目标是交替地对这些数字应用 summultiplication 运算符以获得单个结果。

例如,对于这个输入,结果是

((1 + 2) * 3 + 4) * 5 + 6

减少到 71。基本上,这可以分解为:

t1 =  1 + 2 
t2 = t1 * 3 
t3 = t2 + 4
... 

等等。

奖励:可以推广到两个以上周期性操作的解决方案将受到欢迎。

【问题讨论】:

    标签: python list function cyclic


    【解决方案1】:

    这里有一个稍微不同的答案,避免在 lambda 函数中使用 next

    import operator
    from itertools import cycle
    
    def apply_cyclic(numbers, functions):
        numbers = iter(numbers)
        functions = cycle(functions)
        result = next(numbers)
        for num, fun in zip(numbers, functions):
            result = fun(result, num)
        result num
    
    print(apply_cyclic([1,2,3,4,5,6], [operator.add, operator.mul]))
    

    【讨论】:

      【解决方案2】:

      这是针对这种情况的非 itertools 方法。

      首先想象有一个版本的functools.reduce 一次从一个可迭代对象中接收 3 个项目。我们称这个假设函数为reduce3

      如果存在,我们可以这样做:

      reduce3(lambda a, b, c: (a+b)*c, numbers)
      

      如果我们要查看这个操作的中间结果,我们会得到类似的结果:

      1, 2, 3, 4, 5, 6  # Initial list
      9, 4, 5, 6        # Step 1
      65, 6             # Step 2
      (65 + 6) * ??     # Step 3
      

      所以这几乎就是我们想要的,除了在第 3 步中没有要乘以的第 3 项。事实上,这将发生在任何偶数长度的列表中。好吧,如果它的长度是偶数,我们就在列表中附加一个1

      if not len(numbers) % 2:
          numbers.append(1)
      

      在此之后,第三步将是:

      (65 + 6)*1
      

      正确答案为 71。

      很遗憾,这个神奇的功能并不存在。但是,我们可以修改原始列表以模仿此功能。我们只需要获取数字列表并将连续的数字对(不包括第一个元素)分组为元组。另外,如果列表是偶数长度,我们需要在末尾添加元素1

      本质上,让我们编写一个函数preprocess()[1, 2, 3, 4, 5, 6] 变成[1, (2, 3), (4, 5), (6, 1)]

      def preprocess(myList):
          my_output = [myList[0], *zip(numbers[1::2], numbers[2::2])]
          if not len(myList) % 2:
              my_output.append((myList[-1], 1))
          return my_output
      
      print(preprocess(numbers))
      #[1, (2, 3), (4, 5), (6, 1)]
      

      现在我们可以reduce处理列表:

      from functools import reduce
      result = reduce(lambda a, b: (a+b[0])*b[1], preprocess(numbers))
      print(result)
      #71
      

      reducer 接受 2 个输入 - 一个数字和一个元组。它将数字添加到元组的第一个元素,并将结果乘以第二个元素。结果是另一个数字,然后将其传递给下一个reduce操作。


      更新

      这是reduceN 的一般实现。 N 是由传入函数的长度决定的,所以可以推广到任意数量的函数。

      from itertools import islice  # couldn't get away from itertools this time
      
      def reduceN(functions, iterable, initializer=None):
          it = iter(iterable)
          n = len(functions)
          if initializer is None:
              value = next(it)
          else:
              value = initializer
          elements = list(islice(it, n))
          while(elements):
              for fn, el in zip(functions, elements):
                  value = fn(value, el)
              elements = list(islice(it, n))
          return value
      

      我们可以使用它来循环应用任意数量的函数。所以原来的例子:

      from operator import add, mul
      numbers = [1, 2, 3, 4, 5, 6]
      functions = [add, mul]
      print(reduceN(functions, numbers))
      #71
      

      如果我们从numbers 中删除最后一个元素:

      print(reduceN(functions=functions, iterable=[1, 2, 3, 4, 5]))
      #65
      

      【讨论】:

        【解决方案3】:

        一种解决方案是使用itertools.cycle 构建一个循环生成器,并在functools.reduce 中交替应用每个函数。

        from itertools import cycle
        from functools import reduce
        import operator
        
        fn = cycle((operator.add, operator.mul))
        result = reduce(lambda x, y: next(fn)(x, y), numbers)
        

        print(result)
        71
        

        这种方案的好处是可以改变fn的定义,让任意数量的运算符连续应用:

        fn = cycle((operator.add, operator.mul, operator.sub, ...))
        

        此外,当您一次只处理两个操作数时,不存在优先级问题。

        注意:不支持一元运算符。

        【讨论】:

          猜你喜欢
          • 2019-04-29
          • 1970-01-01
          • 2018-02-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多