小甲鱼第45课 魔术方法 简单定制
- 1.课后作业
- 1.1 作业0
- 1.1.1题目理解
- 1.1.2 一点小修改
- 1.1.3 代码1
- 1.1.4 思考和扩展
- 1.2 作业1
- 1.2.1 时间的正确输出[^2]
- 1.2.2 代码2
- 1.2.3 秒改成天(日期输出)
- 参考资料
关于小甲鱼《零基础入门学习 python》第45课 魔术方法 简单定制 的一点小思考1
基础代码在小甲鱼的视频里有讲解,我就不浪费时间了
下面是B站链接,这是小甲鱼大佬自己上传的老版视频,现在fishc论坛应该有最新版的喜欢的可以去支持一下
https://www.bilibili.com/video/BV1Fs411A7HZ?p=45
1.课后作业
1.1 作业0
按照课堂中的程序,如果开始计时的时间为(2022年2月22日16:30:30),停止时间是(2025年1月23日15:30:30),那按照我们用停止时间减去开始时间的计算方式就会出现负数,你应该对此做一些转换
1.1.1题目理解
最主要就是实现
start()、
shop()、
时间计算、
正确输出最终用时
1.1.2 一点小修改
我也是初学者所以讲不了什么模块、什么方法的,以下这只是我的思路分析,大家看看就好。
我在做这个小程序时,一开始也是一头蒙,以前c php java 都了解一点皮毛,就用java的思路做个类,new一下,再做个函数调用什么的。但这是python啊,这么麻烦还学个锤子。于是好好看着视频,跟着理了一遍思路,可能感觉是作弊,但是这个程序做下来,我确实对python编程思路有了大致的理解,不会再被之前思路限制了(我不会说这是之前课后作业没好好做的后果)
好了,接下来书归正传
看到小甲鱼的代码,自己理解之后,我的反应是 还有什么可以提升
就是找bug
#问题一:连续两个start()相当于重新计时(已解决)
#问题二: 当前位不够减, 可能出现负数 (解决)
#问题三: 如何更加精确(在1.2的代码里会有)
ps:个人感觉对于刚接触的新生事物,人们都有最大好奇心,就像是刚开始认知世界的小孩子一样,这个时候人的思维最发散,最跳跃,也最不容易被条条框框限制,我一般在这个时候回去想一些发散性问题。问题不可怕,想办法解决就是了。
1.1.3 代码1
各位看官如果是来对比代码的就先看代码,再上解决思路,也可转到1.1.4先看思路
import time as myti
import math as mymath
class MyTimer: #问题3 如何更加精确
def __init__(self):
self.unit = ["年", "月", "日", "时", "分", "秒"]
self.prompt = "请先启动计时器"
self.lasted = []
self.begin = 0
self.end = 0
def __str__(self):
return self.prompt
__repr__ = __str__
#两个计时器时间和
def __add__(self , other):
prompt = "总共运行了"
result = []
for index in range(6):
result.append(self.lasted[index] + other.lasted[index])
if result[index] :
prompt += (str(result[index]) + self.unit[index])
return prompt
#开始计时
def start(self): #问题1 连续两个start()相当于重新计时(已解决)
if self.begin:
print("计时已经开始,请先调用stop()停止当前计时器")
else:
self.begin = myti.localtime()
self.prompt = "请先调用stop()停止当前计时器"
print("计时开始……")
#停止计时
def stop(self):
if not self.begin:
print( "请先调用start()启动一个计时器")
else:
self.end = myti.localtime()
self._calc()
print("计时结束")
## #内部方法 计算运行时间
def _calc (self):
self.lasted = []
self.prompt = "总共运行了"
tag = 0
temp = []
for index in range(6): #计算
rindex = 5 - index
#是否进位
if tag:
self.lasted.append(self.end[rindex] - self.begin[rindex] - 1)
else :
self.lasted.append(self.end[rindex] - self.begin[rindex])
tag = 0
#取正数
if self.lasted[index] <0 :
if index <= 1:
self.lasted[index] = 60 - self.begin[rindex] + self.end[rindex] - 1
elif index <=2:
self.lasted[index] = 24 - self.begin[rindex] + self.end[rindex] - 1
elif index <=3:
self.lasted[index] = 31 - self.begin[rindex] + self.end[rindex] - 1
else:
self.lasted[index] = 12 - self.begin[rindex] + self.end[rindex] - 1
tag = 1
for index in range(6): #正序输出
rindex = 5 - index
if self.lasted[rindex] : #问题2 当前位不够减可能出现负数 (解决)
self.prompt += (str(self.lasted[rindex]) + self.unit[index])
#为下一轮计时初始化
self.begin = 0
self.end = 0
'''
#测试 么有sleep()需要手动输入
t1 = MyTimer()
t1.start()
t1.stop()
t1
t2 = MyTimer()
t2.start()
t2.stop()
#print(t1 + t2)
'''
1.1.4 思考和扩展
对于问题一 两次连续执行start(),
第二次的时间会覆盖第一次的时间,这是一个不大不小的bug(其实也算不上),我思考的是如果以后真的做成图形化,用户误点击怎么办?
答:很简单,只确认一次就好
方式和stop()的方式相同,就不赘述了
问题二: 当前位不够减, 可能出现负数
小甲鱼参考答案:
(减法思维)是用循环借位的方式,感觉也有些繁琐,
参考答案给的代码是每次算当前位,就要执行一次while()实现”借1“这样时间代价有点大,写代码也要考虑很多
下面是参考答案的 _cacl()
ps:课后题我是先自己做,再看的答案,所以思路有些不同, 建议大家也先自己做
#小甲鱼的参考答案
#https://www.jianshu.com/p/c78d54ad1fce
# 内部方法,计算运行时间
def _calc(self):
self.lasted = []
self.prompt = "总共运行了"
for index in range(6):
temp = self.end[index] - self.begin[index]
# 低位不够减,需要向高位借位
if temp < 0:
# 测试高位是否有得借,没得借的话再向高位借……
i = 1
while self.lasted[index-i] < 1:
self.lasted[index-i] += self.borrow[index-i] - 1
self.lasted[index-i-1] -= 1
i += 1
self.lasted.append(self.borrow[index] + temp)
self.lasted[index-1] -= 1
else:
self.lasted.append(temp)
# 由于高位随时会被借位,所以打印要放在最后
for index in range(6):
if self.lasted[index]:
self.prompt += str(self.lasted[index]) + self.unit[index]
# 为下一轮计算初始化变量
self.begin = 0
self.end = 0
print(self.prompt)
解法2. 我的思路(加法思想)
每位的进制不同,让时间的转换看起来复杂,但受到2进制 8进制计算的摧残这么久,我们可以很容易的想到:其实都一样
(加法思想)通过我从组成原理里面了解不多的电路知识,加法器不就是一种很好的方式吗。
一个加数、一个被加数、一个进位标识符
加法器不需要了解当前位的进位,最终会产生多大的影响,只要有进位我就修改标识tag就好(就好比99999+1)
.
所以我逆向做了一个”减法器“ 。每当当前位需要“借1”时,就修改tag,不管之后”位“怎样计算。
至于可以实现的理由嘛,其实很简单,例如10进制 单位相加(减)最多9+9=18,也就是说进位最多也就是1。那做一个标志位tag,下一“位”计算时多加(减)一个1就好,至于什么进制,并不重要
.
只是一种理解,真正的计算机只有加法器(再加上一些额外电路就可以实现 + - * / ++ or and 之类的)
下面是代码:
## #内部方法 计算运行时间
def _calc (self):
self.lasted = []
self.prompt = "总共运行了"
tag = 0
temp = []
for index in range(6): #计算
rindex = 5 - index #低位到高位计算
#是否借位
if tag:
self.lasted.append(self.end[rindex] - self.begin[rindex] - 1)
else :
self.lasted.append(self.end[rindex] - self.begin[rindex])
tag = 0
#取正数 秒分 时 天 月 (没有年是因为年不可能是负数 自然进不来if语句)
if self.lasted[index] <0 :
if index <= 1:
self.lasted[index] = 60 - self.begin[rindex] + self.end[rindex] - 1
elif index <=2:
self.lasted[index] = 24 - self.begin[rindex] + self.end[rindex] - 1
elif index <=3:
self.lasted[index] = 31 - self.begin[rindex] + self.end[rindex] - 1
else:
self.lasted[index] = 12 - self.begin[rindex] + self.end[rindex] - 1
tag = 1 #只要进来if 那就是产生了借位
for index in range(6): #正序输出
rindex = 5 - index
if self.lasted[rindex] : #问题2 当前位不够减可能出现负数 (解决)
self.prompt += (str(self.lasted[rindex]) + self.unit[index])
#为下一轮计时初始化
self.begin = 0
self.end = 0
关于解法2的一点小探讨:
”时间复杂度和空间复杂度方面“:
我的方法是两个if语句的判断,第二的情况多所以判断要复杂些,但是 if 相对于 while 还是要简单些,cpu执行时 空间复杂度while较有优, 时间复杂度if一般情况下较优(现在对于代码的评判一般先考虑时间复杂度,特殊情况下也要考虑内存和硬盘限制)。
这是建立在大量数据前提下,但是现代的编程软件都会有自己的编译方式,而且while循环层不多,这个程序也不复杂,真正跑起来,两种方法也差不多
.
”算法题方面“
我再写完这段程序之后,突然发觉之前做过类似的题目,忘记是HDOJ还是牛客网了(哎没广告费啊-_-)。仔细一想 解法2的方式确实,方便理解.。如果参加竞赛,是愿意写一个能记住且稍微优秀的方案,还是一个费脑子的方案。(这里只是对于代码的探讨,我个人还是很崇拜甲鱼大大的,网上其他的一些视频真心感觉是在读课本啊)
.
“药不能停”
写到这里,其实都可以看出代码在计算天数转月份时会出错,就像小甲鱼说的如果我们不及时纠正,我们会在错误的道路上越走越远……
所以学习用永无止境啊
1.2 作业1
1.2.1 时间的正确输出2
为毛一个月一定是31天?不知道可能也是30天或者29天吗?(上一题我们的答案是假设一个月31天)。没错,如果要正确得到月份的天数,我们还需要考虑是否闰年,还有每月的最大天数,所以太麻烦了……如果我们不及时就在,我们会在错误的道路上越走越远……
.
原始代码出处
链接:https://www.jianshu.com/p/c78d54ad1fce
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
我做了一点小修改如何解决进制转换问题,这里参考答案给出的方法很暴力,就是直接改成秒,不再计算时天之类的。
但是我感觉”本轮计时共进行了12464862.02秒 “不符合我的阅读习惯(懒得自己算)
所有稍作修改,添加了__StoD()方法改成了天,时,分……
1.2.2 代码2
还是先上代码
import time as myti
class MyTimer: #问题3 如何更加精确(已解决)
def __init__(self):
self.unit = [ "天", "时", "分", "秒","毫秒"]
self.prompt = "请先启动计时器"
self.lasted = []
self.pre_lasted = 0
self.begin = 0
self.end = 0
self.default_timer = myti.perf_counter #perf_counter()返回计时器的精准时间(系统的 运行时间)
#process_time()返回当前进程执行CPU的时间总和
def __str__(self):
return self.prompt
__repr__ = __str__
#两个计时器时间和
def __add__(self , other):
self.prompt = "二者共运行了"
result = self.lasted + other.lasted
tuple2 = self._StoD(result)
for index in range(5): #正序输出
if tuple2[index] :
self.prompt += (str(tuple2[index]) + self.unit[index])
return self.prompt
#开始计时
def start(self): #问题1 连续两个start()相当于重新计时(已解决)
if self.begin:
print("计时已经开始,请先调用stop()停止当前计时器")
else:
self.begin = self.default_timer()
self.prompt = "请先调用stop()停止当前计时器"
print("计时开始……")
#停止计时
def stop(self):
if not self.begin:
print( "请先调用start()启动一个计时器")
else:
self.end = self.default_timer()
print("计时结束")
self._calc()
## #内部方法 计算运行时间
def _calc(self):
self.prompt = "总共运行了"
self.lasted = self.end - self.begin
tuple1 = self._StoD(self.lasted) #问题4 单纯的秒数 阅读不方便(已解决)
for index in range(5): #正序输出
if tuple1[index] :
self.prompt += (str(tuple1[index]) + self.unit[index])
print(self.prompt)
#为下一轮计时初始化
self.begin = 0
self.end = 0
# 内部方法 秒改成天
def _StoD(self,pre_lasted):
str_prelasted = str(pre_lasted) #转换成字符串
str_lasted = str_prelasted.partition(".") #切分
str_lasted_inte = int(str_lasted[0])
str_lasted_decimal = (str_lasted[2][0:2])
m , s = divmod(str_lasted_inte , 60) #divmod(div, mod)
h , m = divmod(m , 60)
d , h = divmod(h , 24)
return (d,h,m,s,str_lasted_decimal)
def set_timer(self, timer):
if timer == "process_time":
self.default_timer = myti.process_time
elif timer == "perf_counter":
self.default_timer = myti.perf_counter
else :
print("输入无效")
#测试
t1 = MyTimer()
t1.set_timer('perf_counter')
t1.start()
myti.sleep(1.1)
t1.stop()
t2 = MyTimer()
t2.set_timer('perf_counter')
t2.start()
myti.sleep(2.0)
t2.stop()
print(t1 + t2)
1.2.3 秒改成天(日期输出)
问题4 阅读习惯 秒改成天
这里我只是将浮点数转化成字符串,再做一个除法,输出了一个时间元组,也算是对于前面知识的一个回顾
# 内部方法 秒改成天
def _StoD(self,pre_lasted):
str_prelasted = str(pre_lasted) #转换成字符串
str_lasted = str_prelasted.partition(".") #切分
str_lasted_inte = int(str_lasted[0]) #整数部分
str_lasted_decimal = (str_lasted[2][0:2]) #小数部分前两位
m , s = divmod(str_lasted_inte , 60) #divmod(div, mod)
h , m = divmod(m , 60)
d , h = divmod(h , 24)
return (d,h,m,s,str_lasted_decimal)
拓展:这样就完美了吗?
没有,在实际运行中,大家可以发现小数点后的位数可能不正确
例如 t1运行1.01s
t2运行2.01s
t1+t2 运行 3.01s
其原因1 self.lasted是一个浮点数,两个相加有四舍五入
2 python里小数点后进行运算的模块有自己的运算方式,容易出现进位不符合实际的问题(C++浮点运算也有类似问题)
(py社区里可能有详细解释,有需要的同学可以去看看)
怎么解决?
方法一:self.lasted + other.lasted 相加前就把它们改成两位小数
方法二:取出小数点后两位改成整型,运算后再拼接到小数点后
伪代码
int_decimal = int(str_lasted[2][0:2]) + 另一个
str_decimal = str(int_decimal)
str_lasted = str_lasted_inte + '.' + str_decimal
#还有一些需要改的其他部分,这里就不赘述了
参考资料
- https://www.bilibili.com/video/BV1Fs411A7HZ?p=45 ↩︎
- https://www.jianshu.com/p/c78d54ad1fce ↩︎