纯功能的解决方案能否像当务之急一样干净?


10

我在Python中有一个练习,如下所示:

  • 多项式作为系数的元组给出,因此幂由索引确定,例如:(9,7,5)表示9 + 7 * x + 5 * x ^ 2

  • 编写一个函数以计算给定x的值

由于我最近从事函数式编程,所以我写了

def evaluate1(poly, x):
  coeff = 0
  power = 1
  return reduce(lambda accu,pair : accu + pair[coeff] * x**pair[power],
                map(lambda x,y:(x,y), poly, range(len(poly))),
                0)

我认为这不可读,所以我写了

def evaluate2(poly, x):
  power = 0
  result = 1
  return reduce(lambda accu,coeff : (accu[power]+1, accu[result] + coeff * x**accu[power]),
                poly,
                (0,0)
               )[result]

这至少是不可读的,所以我写了

def evaluate3(poly, x):
  return poly[0]+x*evaluate(poly[1:],x) if len(poly)>0 else 0

这可能效率较低(编辑:我错了!),因为它使用许多乘法而不是求幂,原则上,我不在这里测量(编辑:我有多傻!测量会指出我的误解!),仍然不如迭代解决方案可读(可以说):

def evaluate4(poly, x):
  result = 0
  for i in range(0,len(poly)):
      result += poly[i] * x**i
  return result

是否有一个纯功能性的解决方案像当务之急那样可读并且在效率上接近它?

诚然,更改表示形式会有所帮助,但这是通过练习给出的。

也可以是Haskell或Lisp,而不仅仅是Python。


7
以我的经验,在不使用可变变量(例如还意味着不使用for循环)的意义上,纯函数式代码是Python的目标。明智地重新绑定变量而不更改对象可以为您带来几乎所有的好处,并使代码无限地可读。由于数字对象是不可变的,并且仅重新绑定了两个本地名称,因此您的“命令式”解决方案比任何“严格的纯” Python代码更好地实现了功能编程的美德。

2
顺便说一句,乘法方法是霍纳方法,它在每个步骤上都比求幂更有效,因为求幂需要非常相同的乘法,然后再进行更多乘法。

1
lambda与使用较轻的匿名语法功能的语言相比,使用时Python非常难看。部分原因可能是“不干净”的外观。
KChaloux 2013年

@KChaloux正是我要说的。从很多方面来说,函数式编程支持在Python中都是事后的想法,它可以证明这一点。即便如此,我也不认为第一个版本是如此难以阅读,以至于您无法弄清楚到底发生了什么。
Evicatos

我对您的代码感到非常困惑,而问题范围内的数学方程式非常清晰,为什么不逐字使用该数学方程式呢?可以很容易地将其转换为给定任何语言的函数……不确定当问题要求一个评估单个方程并给出该方程的函数时,您想要映射或减少或迭代任何对象的原因-它不需要根本没有迭代...
Jimmy Hoffa 2013年

Answers:


13

@delnan指出,Horner方法的计算效率可能更高,但我将其称为幂运算解决方案在Python中相当可读:

def eval_poly(poly, x):
    return sum( [a * x**i for i,a in enumerate(poly)] )

17
放下方括号,为变量赋予更多描述性名称,甚至更好: sum(coeff * X**power for power, coeff in enumerate(poly))
Izkata 2013年

1
其他张贴的答案如此复杂,让我感到很难过。使用语言为您带来好处!
Izkata

理解就像是进入函数编程的for循环“走私”
user1358 2013年

7
@ user1358不,它是map和组成的语法糖filter。也可以将其视为特定形状的for循环,但是该形状的循环等效于上述函数组合器。

7

许多功能语言都有mapi实现,可让您在地图中编织索引。将其与和相结合,在F#中具有以下内容:

let compute coefficients x = 
    coefficients 
        |> Seq.mapi (fun i c -> c * Math.Pow(x, (float)i))
        |> Seq.sum

2
即使他们没有,只要您了解其map工作原理,编写自己的一份就应该非常简单。
KChaloux

4

我不了解您的代码与您定义的问题范围之间的关系,因此,我将给出忽略问题范围的代码版本(基于您编写的命令性代码)。

可读性强的haskell(可以轻松地将此方法转换为具有列表解构功能且纯净且可读的FP语言):

eval acc exp val [] = acc
eval acc exp val (x:xs) = eval (acc + execPoly) (exp+1) xs
  where execPoly = x * (val^exp)

有时,haskell这样简单的天真简单方法要比不习惯FP的人更简洁的方法更简洁。

仍然完全纯净的更明确的命令性方法是:

steval val poly = runST $ do
  accAndExp <- newSTRef (0,1)
  forM_ poly $ \x -> do
    modifySTRef accAndExp (updateAccAndExp x)
  readSTRef accAndExp
  where updateAccAndExp x (acc, exp) = (acc + x*(val^exp), exp + 1)

第二种方法的好处是在ST monad中,它将表现非常好。

尽管可以肯定,Haskeller最可能的真正实现是上面另一个答案中提到的zipwith。zipWith这是一种非常典型的方法,我相信Python可以模仿结合功能和可映射索引器的压缩方法。


4

如果您只有一个(固定的)元组,为什么不这样做(在Haskell中):

evalPolyTuple (c, b, a) x = c + b*x + a*x^2

相反,如果您有一个系数列表,则可以使用:

evalPolyList coefs x = sum $ zipWith (\c p -> c*x^p) coefs [0..]

或按照您的意愿减少:

evalPolyList' coefs x = foldl' (\sum (c, p) -> sum + c*x^p) 0 $ zip coefs [0..]

1
这不是功课!更不用说我已经做了3个解决方案。
user1358

在Python中有一半的时间(包括本例在内),“ tuple”表示“不可变列表”,因此具有任意长度。

显然是任意长度
user1358

1
不是因为python,而是因为多项式隐含了任意长度,并且固定大小并不是一项大问题
user1358 2013年

1
@delnan很有意思。我一直tuple指的是一组固定大小的值,每个值都可能不同,不能添加或删除。我从来没有真正理解过为什么带有列表的动态语言接受异类输入会需要它们。
KChaloux

3

您可以使用一般步骤来提高功能算法的可读性:

  • 将名称放在中间结果上,而不是尝试将所有内容都填入一行。
  • 使用命名函数代替lambda,尤其是在具有冗长lambda语法的语言中。evaluateTerm与长的lambda表达式相比,阅读这样的内容要容易得多。仅仅因为您可以使用lambda并不一定意味着您应该这样做
  • 如果您现在命名的函数之一看起来很频繁出现,则很可能它已经存在于标准库中。环视四周。我的python有点生锈,但看起来您基本上是重塑了enumeratezipWith
  • 通常,看到命名的函数和中间结果可以更轻松地推断正在发生的事情并简化它,这时将lambda放回去或将某些行放回一起可能是有意义的。
  • 如果命令式的for循环看起来更具可读性,那么很容易理解。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.