如何用python算出1乘到100_Python 如何编程从 1 乘到 50?

@追远·J 答案不错了,写一个递归的方案。

LEVEL 1: 递归

def fac1(n):

if n == 0 or n == 1:

return 1

else:

return n * fac1(n - 1)

print(fac1(50))

LEVEL 2:尾递归

当然,更合理的是使用尾递归。

def fac2(n, acc=1):

if n == 0 or n == 1:

return acc

else:

return fac2(n - 1, acc * n)

print(fac2(50, 1))

LEVEL 3:无穷系列--生成器

更加高级的使用生成器的方式,以下代码是打印1~50的所有阶乘,事实上`fac_generator`是一个储存在有限的内存空间的一个无穷序列,里面包含从1到正无穷的所有数的阶乘,这是该实现的好处。

def fac3(n=1):

fac_value = 1

while True:

fac_value *= n

yield fac_value

n += 1

fac_generator3 = fac3()

print([next(fac_generator3) for _ in range(50)])

这种方案的好处是可以做一些惰性运算,我们可以把这个无穷系列里的每个元素乘以2,但不输出出来,需要时再用next取出。

fac_times_2 = (i * 2 for i in fac3())

print([next(fac_times_2) for _ in range(50)])

fac_times_2 = map(lambda x: x * 2, fac3())

print([next(fac_times_2) for _ in range(50)])

LEVEL 4: 无可变变量的无穷系列生成器

当然,如果了解过函数式编程思想的话,会发现上面实现还有一个问题就是i和fac_value是可变变量,会让这些变量有可能有一些让人意想不到的副作用的出现,更加纯粹的实现为

def fac4(n=2, acc=1):

yield acc

yield from fac4(n+1, acc*n)

fac_generator4 = fac4()

print([next(fac_generator4) for _ in range(50)])

=========

手动先给 @江湖危险赶快逃 点个赞后,想这个问题是否还有讨论的可能。(顺便说一句,这是近几年为数不多的回答这么友好但是又不失深度地讨论一个问题的答案流了)。

首先谈谈自己的写这个答案的发想。自己在写这个答案的初衷是看完 @追远·J 的讨论后觉得,讨论一个最简单的写法,不如展示可能的写法。所以就写了基于函数式的几个答案。而 @江湖危险赶快逃 则让这个问题的讨论出现了新的想法。总结而言,就是四点:程序要简单、要更强的推广功能(在参数取值范围层面)、要速度、无穷序列的意义(很多时候更强调的是数据的表达能力而不是实际运算作用)。这些讨论都丰富了单就这个问题如何思考一些更深刻的计算机语言层面的问题。

在这里只是想对简单这个词语做个回应。库恩在The Essential Tension这本论文集中层讨论过科学圈类似「简洁」、「精确」的讨论在很大层面上具有社会历史意义,很大程度上和当时的价值观念有关。很多程序员直觉上理解的简单是代码量小,这不能说是错的,但是这么理解其实也是过度简单化了这个问题,不然的话,上面递归更简单的写法是:

f = lambda x: f(x-1)*x if x > 1 else 1

简单值得讨论的另外一种意涵,是「若无必要,勿增实体」的奥卡姆式的讨论。在具体的项目维护中,这句话更可以理解为「若无必要,勿引入多于的模块/包」,这是对简单的另外一个理解。关于为什么要这么做可以参考几年前轰动一时的NPM删包事件,具体讨论如下:如何看待 Azer Koçulu 删除了自己的所有 npm 库?​www.zhihu.comzhihu-card-default.svg

这件事情发生后,导致大部分依赖此第三方模块的模块,都发生了问题,其中不乏非常大的明星项目。振聋发聩之后,值得反思的是如果不是作为快速验证的项目(譬如数据分析、快速测试),在正式的工程项目中,如果只是一个简单的功能,是否有必要引入第三方模块。

当然,第三方库直接删除这种问题还算少见,更常见可能遇到以下问题:增加更复杂的模块之间不同版本的依赖关系。这会增加部署、维护、升级难度。

增加命名重复重载的风险。譬如同时载入scipy.math.factorial, numpy.math.factorial, math.factorial在载入时就会互相覆盖的风险。

因此,基于这种原则,载入模块的复杂性排序应该为:载入第三方有依赖的库 > 载入第三方库 > 载入自带模块 > 仅用关键字实现。

当然,这个是基于项目维护的考量理解的简单。所以我在上面的答案中,几乎连functools、itertools、math这类模块都没有使用 。这是我对「简单」的另一种解读。

==============

此外,还需纠正一下 @宛洛 对于for和递归的理解上的偏差。for事实上在该问题现今为止(2019-09-23 11:17)的答案中实现的是递推,而不是递归。在这里不做详细的讨论,可以参考以下问题(虽然其中很多人的理解也是错的,譬如 @暴君祥子 ,我下面基于「函数自身调用自身」的方法也同样实现了递推的思想。这个例子就说明,递归并不是「函数自己调用自己」。):递推和递归的区别是什么?​www.zhihu.comzhihu-card-default.svg

用纯函数而不是for语句来实现递推的例子:

def fac5(n, begin=1, acc=1):

if begin == n:

return acc * begin

else:

return fac5(n, begin+1, acc*begin)

print(fac5(50, 1, 1)) # 或者简单地使用fac5(50)

========

2020-02-22 修改

针对 @江湖危险赶快逃 提出的生成器调用复杂的问题,这里,提出一个新的版本,我们可以通过建一个中间函数来简化定义过程。

from itertools import islice

def take_one(n, g):

return list(islice(g, n-1, n))[0]

此函数实现的是将一个生成器g中提取第n个结果。当然,如果自己懒得实现,可以使用more-itertools这个第三方模块里面的take函数,起到类似的作用。此外,需要解释islice的作用是做生成器切片的函数,类似对字符串和列表所做的功能一样。于是,我们这里就实现了第六个和第七个简化调用的版本。

from functools import partial

fac6 = partial(take_one, g=fac3())

### 这个函数相当于

def fac6(n):

return take_one(fac3(), n)

fac7 = partial(take_one, g=fac4())

针对速度,采用递归算数列很慢的原因的,是因为,很多中间值做了重复运算,譬如,要计算fac(5)的话就计算fac(4)和fac(3),而fac(4)又要计算fac(3)一次,这样的话,相当于很多运算重复作过了。一个简单的方法就是写一个字典把这些中间结果存起来,需要时调用字典就行了。而functools中已经实现了一个lru_cache的装饰器来实现这一功能,于是乎,我们又能改写fac1和fac2成第八个和第九个版本。(感谢 @Gallium Wang的指正,我这里描述的是斐波拉切了)

from functools import lru_cache

@lru_cache(100) # 参数指定缓存的数量

def fac8(n):

if n == 0 or n == 1:

return 1

else:

return n * fac8(n - 1)

@lru_cache(100) # 参数指定缓存的数量

def fac9(n, acc=1):

if n == 0 or n == 1:

return acc

else:

return fac9(n - 1, acc * n)

最后我们看看1、2改进后方案的速度,在这里我们用了ipython内建的测速方案%timeit。结果提升了百倍。

%timeit fac1(50)

7.76 µs ± 215 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

%timeit fac8(50)

67.3 ns ± 0.97 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

%timeit fac2(50)

10.2 µs ± 498 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

%timeit fac9(50)

72.2 ns ± 0.856 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值