【问题标题】:How to use functional programming to iterate and find maximum product of five consecutive numbers in a list?如何使用函数式编程迭代并找到列表中五个连续数字的最大乘积?
【发布时间】:2018-05-02 04:03:48
【问题描述】:

我必须使用函数式编程来实现以下函数,接收从 0 到 9 的数字列表。目标是找到列表中积最大的五个连续元素。该函数应该返回最大乘积的索引和最大乘积的值的元组不使用 max 函数。

我可以在没有函数式编程的情况下轻松实现它,但是在没有任何循环的情况下我很难实现它。 到目前为止,这是我的方法,但我坚持的部分是如何遍历数组以找到那些没有循环的连续五个数字。我正在尝试使用 map 来做到这一点,但我认为它不正确。是否可以以任何方式合并枚举?任何帮助表示赞赏。

def find_products(L):
    val = map(lambda a: reduce(lambda x,y: x*y, L),L)
    print (val)

【问题讨论】:

  • mapreduce 在幕后使用 loops,例如试图避免在 list-comprehensions 中使用循环将非常困难并且没有真正的好处。
  • 如果您使用递归,您几乎可以直接避免循环。 Haskell 就是这样做的。然而,Haskell 对此进行了优化,而 Python 可能没有。因此,您将很快遇到最大递归深度。 — 无论如何,你以实用的方式来做这件事的动机是什么?
  • 函数式编程 =/= 避免循环。
  • 我意识到你说max 是不允许的,但值得注意的是,在真正的函数式编程中,最好的方法可能是使用带有滑动窗口迭代器的 max 函数(例如max(sliding(L, 5), key=product) 用于slidingproduct 的适当定义)。
  • IMO,循环与递归只是顺序思维与分而治之的一个实例。

标签: python list lambda functional-programming


【解决方案1】:

这没有任何显式循环或调用max 函数。该函数假定输入列表中至少有五个元素,并输出一个元组(start_index, max_product)

from functools import reduce, partial
import operator

def f(l):
    win = zip(l, l[1:], l[2:], l[3:], l[4:])
    products = map(partial(reduce, operator.mul), win)
    return reduce(lambda x, y: x if x[1] > y[1] else y, enumerate(products))
In [2]: f([1, 2, 3, 4, 7, 8, 9])
Out[2]: (2, 6048)

In [3]: f([2, 6, 7, 9, 1, 4, 3, 5, 6, 1, 2, 4])
Out[3]: (1, 1512)

win = zip(l, l[1:], l[2:], l[3:], l[4:]) 在输入列表上创建一个大小为 5 的滑动窗口迭代器。 products = map(partial(reduce, operator.mul), win) 是一个迭代器,在 win 的每个元素上调用 partial(reduce, operator.mul)(转换为 reduce(operator.mul, ...))。 reduce(lambda x, y: x if x[1] > y[1] else y, enumerate(products))products 添加一个计数器,并返回具有最高值的索引值对。

如果您需要更通用的版本和/或输入列表很大,您可以使用itertools.islice

from itertools import islice

def f(l, n=5):
    win = zip(*(islice(l, i, None) for i in range(n)))
    ...

上面的代码使用了一个生成器表达式,从技术上讲,它是一个循环。它的纯函数版本可能看起来像

from itertools import islice

def f(l, n=5):
    win = zip(*map(lambda i: islice(l, i, None), range(n)))
    ...

【讨论】:

  • 这看起来不错,可能最接近 OP 的预期。
  • 顺便说一句,您可以使用win = zip(*(l[i:] for i in range(5))) 获得更一般的答案。
  • 我仍然对部分操作在做什么感到困惑?如果我们不使用它会发生什么?
  • @vaultah 非常感谢您的帮助!我认为您的回答以真正的函数式编程方式表达了代码!
【解决方案2】:
from functools import reduce #only for python3, python2 doesn't need import
def find_products(L):
    if len(L)==0:
        return 0
    if len(L) <= 5:
        return reduce( lambda x,y:x*y, L)
    pdts = ( reduce(lambda a,b:a*b,L[pos:pos+5]) for pos in range(len(L)-4)) # or pdts = map(lambda pos: reduce(lambda a,b:a*b,L[pos:pos+5],0),range(len(L)-4))
    mx = reduce(lambda x,y: x if x>y else y, pdts)
    return mx

pdts包含所有可能的5个元组产品,然后使用reduce模拟max函数,我们找到产品中的最大值。

【讨论】:

  • 如果我错了请纠正我,但我们不能在函数式编程中使用条件语句,对吗?或者列表理解中的任何循环?
  • @comp.eng。函数式编程中的条件没有错。 Haskell 有 if、保护子句和模式匹配,所有形式的条件——没有条件就无法停止递归。
【解决方案3】:

您可以执行以下操作:

  • 对于range(0, len(L) - 5) 中的每个起始索引
  • 将索引映射到start 的元组和项目L[start:start + 5] 的乘积
  • 将元组减少到具有最高乘积的元组
  • 获取结果元组的第一个值 = 具有最高乘积的 5 个元素的起始索引
  • 返回切片L[result:result + 5]

此算法可以进一步改进以避免重新计算子产品,但使用“滚动产品”,当您从左到右减少时更新,除以删除的元素,然后乘以新的添加的元素。

【讨论】:

    【解决方案4】:

    这是一个纯函数式的 Haskell 解决方案:

    import Data.List
    
    multiply :: [Int] -> Int
    multiply = foldr (*) 1
    
    consecutiveProducts :: [Int] -> [(Int,Int)]
    consecutiveProducts xs =
        [(i,multiply $ take 5 h) | (i,h) <- zipped, length h >= 5]
        where
            indices = reverse [0..(length xs)]
            zipped = zip indices (tails xs)
    
    myComp (i1,h1) (i2,h2) = compare h2 h1
    
    main = print $ head $ sortBy myComp $ consecutiveProducts [4,5,3,1,5,3,2,3,5]
    

    这是它的作用:

    • 从最后一行开始,计算该列表中的连续产品。
    • tails xs 给出以不同起始值开头的所有子集:

      > tails [4,5,3,1,5,3,2,3,5]
      [[4,5,3,1,5,3,2,3,5],[5,3,1,5,3,2,3,5],[3,1,5,3,2,3,5],[1,5,3,2,3,5],[5,3,2,3,5],[3,2,3,5],[2,3,5],[3,5],[5],[]]
      
    • 我们只从这些尾部中提取长度至少为 5 个元素的尾部。
    • 然后我们用自然数zip它们,这样我们就有了与之关联的起始索引。
    • 我们从每个子集中获取前五个元素。
    • 这五个元素被传递给multiply 函数。在那里,这些被简化为一个数字,即产品。
    • 之后我们回到最后一行,按产品值降序对列表进行排序。
    • 从结果列表中,我们只取第一个元素。
    • 然后我们打印结果,这是我输入数据的(5,450)

    【讨论】:

      【解决方案5】:

      此解决方案使用reduce 计算一个 5 值产品,使用列表推导生成所有这些产品,创建元组以使每个产品都有索引,再次使用reduce 以获得最佳元组。

      if else 运算符用于捕获输入中没有 5 个值的情况。

      from functools import reduce
      
      def find_products(values):
          return None if len(values) < 5 else reduce(
              lambda best, this: this if this[1] > best[1] else best,
              [(i, reduce(lambda a,b: a*b, values[i:i+5], 1)) for i in range(0, len(values)-4)]
          )
      
      result = find_products([1, 0, 8, 3, 5, 1, 0, 2, 2, 3, 2, 2, 1])
      print (result)
      

      示例调用的输出是:

      (7, 48)
      

      【讨论】:

        【解决方案6】:

        使用recursion的纯Python解决方案

        首先,我们需要创建一个recursive function 来查找productlist

        def product(l, i=0, s=1):
            s *= l[i]
            if i+1 < len(l):
                return product(l, i+1, s)
            return s
        

        我们可以做一些测试:

        >>> product([1, 2, 3])
        6
        >>> product([1, 1, 1])
        3
        >>> product([2, 2, 2])
        8
        

        那么,我们可以在另一个recursive function 中使用这个function 来解决你的问题:

        def find_products(l, i=0, t=(0, -1)):
             p = product(l[i:i+5])
             if p > t[1]:
                 t = (i, p)
             if i+5 < len(l):
                 return find_products(l, i+1, t)
             return t
        

        哪个有效!

        这里有一些测试表明它可以工作:

        >>> find_products([1, 1, 5, 5, 5, 5, 5, 1, 1])
        (2, 3125)
        >>> find_products([1, 1, 1, 1, 1, 0, 0, 0, 0])
        (0, 1)
        >>> find_products([1, 4, 5, 2, 7, 9, 3, 1, 1])
        (1, 2520)
        

        【讨论】:

        • 您请求了一些反馈。这段代码看起来很明智。你最终会遇到 Python 中最大递归深度的问题。此外,在 Python 中调用函数的开销也不容忽视,因为它们都是虚拟的。所以我认为使用reducemap 会更快更容易阅读。我还是不明白 OP 的动机。
        • @MartinUeding 是的,我没有考虑最大递归深度的好处。而且我同意 OP 想要的并不是真正实用的
        【解决方案7】:

        想要一个使用 max 而没有 max 的班轮试试这个

        from numpy import prod
        l=[2,6,7,9,1,4,3]
        max([prod(l[i:i+5]) for i in range(len(l))])
        sorted([prod(l[i:i+5]) for i in range(len(l))])[-1]  // without max
        

        【讨论】:

        • 最大禁止:) .
        【解决方案8】:

        命令式范式通常是:

         state = state0
         while condition:
           # change state 
        

        对于很多人来说,这是“自然”的编程方式,您知道如何以这种方式进行。

        functional paradigm禁止变量,有一些优点。它适用于通过参数(IN)和返回值(OUT)进行通信的函数。它经常使用递归函数。

        一个通用的函数递归方案是:

        f = lambda *args : result(*args) if  condition(*args) else f(*newparams(*args))   
        

        这里我们可以找到以(l,i,imax,prodmax)为参数的解决方案,并且:

        condition = lambda  l,i,_,__ : i>=len(l)-5        
        
        result = lambda _,__,*args : args
        
        newparams = lambda l,i,imax,prodmax: (l, i+1, imax, prodmax)  \
                    if   l[i]*l[i+1]*l[i+2]*l[i+3]*l[i+4] <= prodmax  \
                    else (l, i+1, i, l[i]*l[i+1]*l[i+2]*l[i+3]*l[i+4]) 
        

        除了函数没有定义。

        您甚至可以不定义任何函数来执行此操作,例如参见here,但可读性受到的影响更大。

        运行:

        In [1]: f([random.randint(0,9) for i in range (997)],0,0,0)
        Out[1]: (386, 59049)                
        

        Python 通过将递归深度设置为 2000 来限制这种方法,从 Python 3 开始,通过将功能工具隐藏在模块 functools 中。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-03-09
          • 2014-02-27
          • 1970-01-01
          • 1970-01-01
          • 2017-01-08
          • 1970-01-01
          • 2013-12-31
          • 1970-01-01
          相关资源
          最近更新 更多