Welcome to Note’s documentation!¶
1_算法¶
ChallengePython¶
OJ 网址¶
看上去好像只支持 Python2.X 的语法, 提交前可到在线编程进行测试
题目 11: 结尾0的个数¶
描述¶
给你一个正整数列表 L, 如 L=[2,8,3,50], 输出L内所有数字的乘积末尾0的个数, 如样例L的结果为2.(提示:不要直接相乘,数字很多,可能溢出)
solve¶
#看了题解:总的来说就是计算除以2和除以5的个数,输出最小的就可以了
def solve( L ) :
num_2 = 0
num_5 = 0
for each in L :
num_5 += count_mod( each,5 )
num_2 += count_mod( each,2 )
return num_2 if num_2 <= num_5 else num_5
def count_mod( number,mod ) :
counts = 0
while number % mod == 0 :
counts += 1
number /= mod
return counts
print( solve( L ) )
题目 12: 结尾非零数的奇偶性¶
描述¶
给你一个正整数列表 L, 如 L=[2,8,3,50], 判断列表内所有数字乘积的最后一个非零数字的奇偶性, 奇数输出1,偶数输出0. 如样例输出应为0
solve¶
#看了题解:分析所有数字的约数中2和5的数量,总有如果n(2)>n(5),则结果必为偶数
def solve( L ) :
num_2 = 0
num_5 = 0
for each in L :
num_5 += count_mod( each,5 )
num_2 += count_mod( each,2 )
return 1 if num_2 <= num_5 else 0
def count_mod( number,mod ) :
counts = 0
while number % mod == 0 :
counts += 1
number /= mod
return counts
print( solve( L ) )
题目 13: 光棍的悲伤¶
描述¶
光棍们对1总是那么敏感,因此每年的11.11被戏称为光棍节。 鄙人光棍几十载,光棍自有光棍的快乐。让我们勇敢面对光棍的身份吧, 现在就证明自己:给你一个整数a,数出a在二进制表示下1的个数,并输出。
solve¶
def solve( num ) :
return bin( num ).count( "1" )
"""
bin为内置函数,将num转换成二进制数(要求num整数或者包含__index__()方法切返回值为integer的类型)
"""
print( solve( a ) )
题目 16: 人民币金额打印¶
描述¶
银行在打印票据的时候,常常需要将阿拉伯数字表示的人民币金额转换为大写表示,现在请你来完成这样一个程序。
在中文大写方式中,0到10以及100、1000、10000被依次表示为:
零壹贰叁肆伍陆柒捌玖拾佰仟万
以下的例子示范了阿拉伯数字到人民币大写的转换规则:
1 壹圆
11 壹拾壹圆
111 壹佰壹拾壹圆
101 壹佰零壹圆
-1000 负壹仟圆
1234567 壹佰贰拾叁万肆仟伍佰陆拾柒圆
现在给你一个整数a(|a| < 100000000
), 打印出人民币大写表示.
注意:由于中文乱码问题,输出时请先decode("utf8")
,例如你要输出ans = "零圆"
, print ans.decode("utf8")
.
Note:数据已于2013-11-19日加强,原来通过的代码可能不能再次通过。
solve¶
num2chinese = {
0: "零",
1: "壹",
2: "贰",
3: "叁",
4: "肆",
5: "伍",
6: "陆",
7: "柒",
8: "捌",
9: "玖",
10: "拾",
100: "佰",
1000: "仟",
10000: "万",
}
def solve(num):
"""
将数字形式的金额打印成人民币金额
:param num: 555
:return: 伍佰伍拾伍圆
"""
result = str()
str_num = str(num)
# 零以及负数处理
if num == 0:
result += "零"
if num < 0:
result += "负"
str_num = str_num[1:]
length = len(str_num)
# 万圆以上
if length > 4:
str_num1 = str_num[: (length - 4)]
str_num = str_num[(length - 4):]
result += fun1(str_num1) # 万左边的数值
result += "万"
result += fun1(str_num, True) # 万右边的数值
else:
result += fun1(str_num)
result += "圆"
return result
def fun1(num, need_to_print_zero=False):
num = num.zfill(4)
result = str()
# 从千位循环至个位
for i in range(4, 0, -1):
temp = int(num) // (10 ** (i - 1))
if temp != 0:
result += num2chinese[temp]
result += num2chinese[10 ** (i - 1)] if i != 1 else str()
need_to_print_zero = True
elif need_to_print_zero:
if int(num) != 0: # 不是最后一个 0
result += num2chinese[0]
need_to_print_zero = False
num = num[1:]
return result
print(solve(a))
题目 17: 公约数的个数¶
描述¶
给你两个正整数a,b, 输出它们公约数的个数。
solve¶
# 思路很简单, 就是计算出最大公约数之后把每个小于公约数的数都遍历一遍
def gcd(x, y):
return x if y == 0 else gcd(y, x % y)
def countGCD(x, y, max_gcd):
counts = 1
for i in range(2, max_gcd + 1):
if x % i == 0 and y % i == 0:
counts += 1
return counts
max_gcd = gcd(a, b)
counts = countGCD(a, b, max_gcd)
print(counts)
题目 18: 逆解最大公约数与最小公倍数¶
描述¶
我们经常遇到的问题是给你两个数,要你求最大公约数和最小公倍数。 今天我们反其道而行之,给你两个数a和b,计算出它们分别是哪两个数的最大公约数和最小公倍数。 输出这两个数,小的在前,大的在后,以空格隔开。若有多组解,输出它们之和最小的那组。 注:所给数据都有解,不用考虑无解的情况。
solve¶
import math
__author__ = '__L1n__w@tch'
def gcd(x, y):
return x if y == 0 else gcd(y, x % y)
def lcm(x, y):
return x * y / gcd(x, y)
def get_the_two_num(number_gcd, number_lcm):
a_list = []
tmp = number_lcm / number_gcd
for a in range(int(math.sqrt(tmp)), 0, -1):
if tmp % a == 0:
b = tmp / a
if gcd(a, b) == 1:
a_list.append(a * number_gcd)
a_list.append(b * number_gcd)
break
return a_list
if __name__ == '__main__':
List = get_the_two_num(a, b)
List.sort()
print("%d %d" % (List[0], List[1]))
题目 20: 信息加密¶
描述¶
给你个小写英文字符串a和一个非负数b(0<=b<26
), 将a中的每个小写字符替换成字母表中比它大b的字母。
这里将字母表的z和a相连,如果超过了z就回到了a。例如a="cagy",b=3
, 则输出 fdjb
solve¶
def transposition(string, num):
answer = ""
for each in string:
answer += chr((ord(each) - ord('a') + num) % 26 + ord('a'))
return answer
if __name__ == '__main__':
string = transposition(a, b)
print(string)
题目 21: 回文子串¶
描述¶
给你一个字符串a和一个正整数n,判断a中是否存在长度为n的回文子串。如果存在,则输出YES,否则输出NO。
回文串的定义:记串str逆序之后的字符串是str1,若str=str1,则称str是回文串,如"abcba".
solve¶
def isHuiWen(string):
return string[::-1] == string
def huiWenStringExist(string, n):
while len(string) >= n:
test_string = string[:n]
if isHuiWen(test_string):
return True
else:
string = string[1:]
return False
if __name__ == '__main__':
if huiWenStringExist(a, n):
print("YES")
else:
print("NO")
题目 22: 时间就是金钱¶
描述¶
给你两个时间st和et(00:00:00<=st <= et<=23:59:59), 请你给出这两个时间间隔的秒数。
如:st="00:00:00", et="00:00:10", 则输出10.
solve¶
#一看这题目就知道可以用时间模块
#PS:有模块的坚决不自己写代码
#主要参考题解的两段代码,一段是如何用datatime,另一段是利用map和zip的
#这一段代码略精湛啊,暂时不学了
"""
et = map(int, et.split(":"))
st = map(int, st.split(":"))
dif = 0
for e, s in zip(et, st):
dif = dif * 60 + (e - s)
print dif
"""
# 最终提交
import datetime
st = datetime.datetime.strptime(st, "%H:%M:%S")
et = datetime.datetime.strptime(et, "%H:%M:%S")
print((et - st).seconds)
参考资料¶
题目 23: 365 Or 366?¶
描述¶
一年有多少天,这是个大问题,很值得思考。
现在给你一个年份year(year为四位数字的字符串,如"2008","0012"), 你输出这一年的天数。如year="2013", 则输出365。
solve¶
#自己一看的思路就是判断年份是不是闰年,闰年是366剩下的都是365
#先按自己的写一遍再看下题解吧
def is_leap_year(year):
year = int(year)
if year % 400 == 0 or (year % 4 == 0 and year % 100 != 0 ):
return True
else:
return False
if __name__ == '__main__':
if is_leap_year(year):
print("366")
else:
print("365")
"""
题解的datetime库解决法:
def year(years):
import datetime,time
y1 = datetime.datetime(years,1,1)
y2 = datetime.datetime(years+1,1,1)
print str(y2 - y1).split(' ')[0]
学习了,实际上,y2 - y1 返回的是datetime 的类型 timedelta 时间差
直接改成 (y2 - y1). days 就好了
"""
题目 25: 格式化时间¶
描述¶
给你一个时间t(t是一个字典,共有六个字符串key(year,month,day,hour,minute,second),值为每个值为数字组成的字符串,如 t={'year':'2013','month':'9','day':'30','hour':'16','minute':'45','second':'2'}
请将其按照以下格式输出, 格式:XXXX-XX-XX XX:XX:XX。如上例应该输出: 2013-09-30 16:45:02。
solve¶
#虽然可以直接看题解的,不过我还是自己学一下datetime模块后再去看题解吧。
#自己学了一下,有date来表示年月日的,以及time来表示时分秒的
import datetime
#自己实验打印出来明明长得一样,提交上去就是不对我也无语了
#好像自己是因为空格问题,自己手打了个空格后就对了。
if __name__ == '__main__':
# t={'year':'2013','month':'9','day':'30','hour':'16','minute':'45','second':'2'}
test = datetime.date(int(t['year']), int(t['month']), int(t['day']))
test_2 = datetime.time(int(t['hour']), int(t['minute']), int(t['second']))
print(str(test) + " " + str(test_2))
"""
看下题解的写法:
from datetime import datetime
for k, v in t.items():
t[k] = int(v) #这个转换的方法得学下,要不然每个都得加个int累死了
#下面的datetime居然能有这么多,学习了
dt = datetime(t['year'], t['month'], t['day'], t['hour'], t['minute'], t['second'])
print dt.strftime("%Y-%m-%d %X")
datetime.strptime(date_string, format):将格式字符串转换为datetime对象;
"""
题目 26: 序列判断¶
solve¶
# 最终提交
if sorted(L)==L:
print "UP"
elif sorted(L)[::-1]==L:
print "DOWN"
else:
print "WRONG"
# -----上面的答案比我的简洁好多------
#没想到好办法,自己遍历着来判断一下吧
#如果题解有更好的方法自己再学习吧
def isUpList(List):
new_List = List.copy() #2.X版本提示说没有copy这个方法,所以得用List[:]来复制
new_List.sort()
if new_List == List:
return True
else:
return False
def isDownList(List):
new_List = List.copy() #copy.deepcopy(L),这个方法也是可以的
new_List.sort(reverse = True)
if new_List == List:
return True
else:
return False
if __name__ == '__main__':
L = [3, 2]
if isUpList(L):
print("UP")
elif isDownList(L):
print("DOWN")
else:
print("WRONG")
题目 27: 加油站¶
描述¶
一个环形的公路上有n个加油站,编号为0,1,2,...n-1,每个加油站加油都有一个上限,保存在列表limit中,即limit[i]为第i个加油站加油的上限,而从第i个加油站开车开到第(i+1)%n个加油站需要cost[i]升油,cost为一个列表。
现在有一辆开始时没有油的车,要从一个加油站出发绕这个公路跑一圈回到起点。
给你整数n,列表limit和列表cost,你来判断能否完成任务。
如果能够完成任务,输出起始的加油站编号,如果有多个,输出编号最小的。
如果不能完成任务,输出-1。
solve¶
#遍历一下就出来答案了吧这题看着。。。
#交上去一次性成功了。。。没测试答案心里虚虚的。。
def canFinishTheWork(n, limit, cost, i):
bus_gas = 0
order = i
while True:
bus_gas += limit[i]
if bus_gas < cost[i]:
return False
else:
bus_gas -= cost[i]
i = (i + 1) % n
if i == order:
break
return True
if __name__ == '__main__':
# n = 5
# limit = [1, 2, 3, 4, 5]
# cost = [1, 2, 3, 4, 5]
List = []
for i in range(n):
if canFinishTheWork(n, limit, cost, i):
List.append(i)
List.sort()
if len(List) <= 0:
print("-1")
else:
print(List[0])
题目 28: 相同数字¶
描述¶
给你一个整数列表L,判断L中是否存在相同的数字,若存在,输出YES,否则输出NO。
solve¶
#一上来两个思路,一个是转换成set集合再比较长度,另一个是遍历一遍
def hasSameNum(List):
return len(set(List)) != len(List)
if __name__ == '__main__':
# L = [1, 2, 3]
if hasSameNum(L):
print("YES")
else:
print("NO")
题目 29: 判断三角形¶
solve¶
#三角形两边之和大于第三边,两边之差小于第三边
#一次性AC
"""
关于判定定理的问题,这里有两种说法:
legend_001 评论于 2014-07-12 11:33:30
先sort,然后保证前两个的和大于第三个就好,因为sort之后就保证了前两个的差小于第三个
ylgkger 评论于 2014-12-19 19:10:27
简化 a+b+c-max(a,b,c)*2>0即可
"""
def canBeTriangle(a, b, c):
List = [a, b, c]
List.sort()
return (List[0] + List[1] > List[2]) and (List[2] - List[0] < List[1])
if __name__ == '__main__':
# a, b, c = 3, 4, 5
if canBeTriangle(a, b, c):
print("YES")
else:
print("NO")
题目 31: 山峰的个数¶
描述¶
十一假期,小P出去爬山,爬山的过程中每隔10米他都会记录当前点的海拔高度(以一个浮点数表示),这些值序列保存在一个由浮点数组成的列表h中。回到家中,小P想研究一下自己经过了几个山峰,请你帮他计算一下,输出结果。
例如:h=[0.9,1.2,1.22,1.1,1.6,0.99], 将这些高度顺序连线,会发现有两个山峰,故输出一个2(序列两端不算山峰)
solve¶
#自己的思路是,遍历一遍,看每个数(除第一个和最后一个以外)是否比两边的数都大
#是的话即为山峰
def countNumOfPeaks(List):
counts = 0
for i in range(1, len(List) - 1):
if List[i] > List[i - 1] and List[i] > List[i + 1]:
counts += 1
return counts
if __name__ == '__main__':
# h = [0.9, 1.2, 1.22, 1.1, 1.6, 0.99]
print(countNumOfPeaks(h))
题目 32: 三角形形状¶
solve¶
#自己想到的思路是用余弦定理
#baidu了一下,发现是用最大边所对应的角来计算
"""
若最大边的平方等于另两边的平方和,则它为直角三角形;
若最大边的平方大于另两边的平方和,则它为钝角三角形;
若最大边的平方小于另两边的平方和,则它为锐角三角形.
"""
def canBeTriangle(a, b, c):
List = [a, b, c]
List.sort()
if (List[0] + List[1] > List[2]) and (List[2] - List[0] < List[1]):
return True
else:
return False
def getTypeOfTriangle(a, b, c):
List = [i ** 2 for i in [a, b, c]]
List.sort()
result = List[2] - List[1] - List[0]
if result == 0:
return "Z"
elif result < 0:
return "R"
elif result > 0:
return "D"
if __name__ == '__main__':
# a, b, c = 3, 4, 5
if canBeTriangle(a, b, c) == False:
print("W")
else:
print(getTypeOfTriangle(a, b, c))
题目 33: 大幂次运算¶
描述:¶
给你两个正整数a(0 < a < 100000
)和n(0 <= n <=100000000000
),计算(a^n) % 20132013
并输出结果
solve¶
def fastExpMod(b, e, m):
result = 1
while e != 0:
if (e&1) == 1:
# ei = 1, then mul
result = (result * b) % m
e >>= 1
# b, b^2, b^4, b^8, ... , b^(2^n)
b = (b*b) % m
return result
# a, n = 5, 6
print(fastExpMod(a, n, 20132013))
======================上面也是快速幂取模,跑起来对的!!!=========================
solve2¶
#之前ACM有做过,就是求模公式呗
"""
翻一下acm笔记:
(a * a) % m = ( a % m * a % m ) % m
"""
#看了下题解,说是快速幂(之前ACM刷斐波那契的时候应该遇到过了,就是二分矩阵快速求幂)
#直接看题解报告了,自己写出算法是不太可能的。。。
#题解。。。。牛过头了:print(pow(a, n, 20132013))
"""
以下说是快速幂取模:
a^b mod c = ((a^2)^(b / 2)) mod c, b是偶数
a^b mod c = ((a^2)^(b / 2) * a) mod c, b是奇数
#下面的代码,在自己的机子上跑着是错的,但是在网上跑的确是对的?python版本的问题我估计!
def PowerMod(a, n, ret):
if n == 0:
return ret
if n % 2:
ret = ret * a % 20132013
return PowerMod( a * a % 20132013, n / 2, ret)
if __name__ == '__main__':
a, n = 5, 6
ret = 1
print(PowerMod(a, n, ret))
"""
run time error¶
"""
version 1
说是算法超时了...
O(n)算法说我超时了,好吧,看来只能看题解了
def solve(a, n):
result = 0
if n == 0:
result = 1
else:
result = a % 20132013
n -= 1
while n > 0:
n -= 1
result = (result * (a % 20132013)) % 20132013
return result
if __name__ == '__main__':
a, n = 5, 5
print(solve(a, n))
"""
题目 35: 最大连续子序列¶
描述:¶
给你一个整数list L, 如 L=[2,-3,3,50], 求L的一个连续子序列,使其和最大,输出最大子序列的和。
例如,对于L=[2,-3,3,50], 输出53(分析:很明显,该列表最大连续子序列为[3,50]).
solve¶
#ACM老师讲过的一道题(比这难得多的),PPT里有,动态规划
#查看了一下课件,得到状态方程:b[j]=max( b[j-1]+a[j], 0+a[j] ), 1<=j<=n
def getLargestSum(List):
b = 0
largest_sum = 0
for each in List:
if b > 0:
b += each
else:
b = each
largest_sum = max(b, largest_sum)
return largest_sum
if __name__ == '__main__':
# L = [2, -3, 3, 50]
print(getLargestSum(L))
题目 36: 最大非连续子序列¶
描述:¶
给你一个整数list L, 如 L=[2,-3,3,50], 求L的一个非连续子序列,使其和最大,输出最大子序列的和。
这里非连续子序列的定义是,子序列中任意相邻的两个数在原序列里都不相邻。
例如,对于L=[2,-3,3,50], 输出52(分析:很明显,该列表最大非连续子序列为[2,50]).
solve¶
#个人的思路是依旧利用动态规划,状态方程为:
#b[j] = max(b[j - 2] + List[j], b[j - 1], List[j])
def solve(List):
L = List[:] # 没有 List.copy(), 用 List[:] 替代了
if len(List) > 1:
L[1] = max(List[0], List[1])
for i in range(2, len(List)):
L[i] = max(L[i - 2] + List[i], L[i - 1], List[i])
return L[-1]
if __name__ == '__main__':
# L = [8, -3, 2, -3, 3, 60, -100, 50]
print(solve(L))
"""
看下题解报告的:
---version 1---
n = len(L)
f = list(L)
for i in range(n):
if i > 0:
f[i] = max(f[i],f[i-1])
if i > 1:
f[i] = max(f[i],f[i-2]+L[i])
print(f[n-1])
其中f[i]表示0-i的最大非连续子序列和
---version 2---
for i in range(2, len(L)):
L[i] = max(max(L[0:i-1]), 0) + L[i]
print max(L)
"""
题目 37: 简单题之勾股定理¶
solve¶
#勾股定理的公式不就是c^2 = a^2 + b^2么
#print '%.3f' %(a**2+b**2)**0.5
def getC(a, b):
c = a ** 2 + b ** 2
return c ** 0.5
if __name__ == '__main__':
# a, b = 3, 4
answer = getC(a, b)
print(format(answer, '.3f'))
题目 38: 简单题之列表转换¶
solve¶
#题目要求一行代码,吓~~~
# L=['abc','d','efg']
print(''.join([str(i) for i in L])) #这句好了,在2.X版本下
#题解更简单:
print(''.join(L))
#下面这句在python3.X版本倒是对了,2.X自己报语法错误了
[print(i, end = "") for i in L]
题目 39: 简单题之输出格式练习¶
描述¶
给你一个字符串列表L,用一行代码顺序输出L中的元素,元素之间以一个空格隔开,注意行尾不要有空格,输出单独占一行。 如L=['abc','d','efg'], 则输出abc d efg。
solve¶
#又是一行代码,吓~~
#思路跟上一题一样呗,2.X版本下用join函数, 3.X版本下print(i, end=' ')
# L = ['abc', 'd', 'efg']
print(' '.join(L))
题目 40: 整数解¶
描述¶
给你两个整数a和b(-10000<a,b<10000
),请你判断是否存在两个整数,他们的和为a,乘积为b。
若存在,输出Yes,否则输出No
例如:a=9,b=15
, 此时不存在两个整数满足上述条件,所以应该输出No。
solve¶
#看到a和b那么小,一下子就想遍历了。。。
#看version 1的遍历,一下子就超时了,说明不行
#x + y = a, x * y = b ==> x * ( a - x ) = b,这样就只有一层遍历了
#看题解居然使用解方程的方法
#题解的时间复杂度为O(1)啊,醉了:
#两个数应该为方程x^2 - a*x + b = 0 的解,判断delta即可
def isExist(a, b):
for i in range(-9999, 10000):
if i * ( a - i ) == b:
return True
return False
if __name__ == '__main__':
# a, b = 9, 15
if isExist(a, b):
print("Yes")
else:
print("No")
"""
---version 2---
题解:
from math import sqrt
delta = a*a - 4*b
if delta > 0 and int(sqrt(delta)) == sqrt(delta):
print "Yes"
else:
print "No"
"""
"""
---version 1---
def isExist(a, b):
for i in range(-9999, 10000):
for j in range(-9999, 10000):
if i + j == a and i * j == b:
return True
return False
"""
题目 41: Py数¶
描述¶
Py从小喜欢奇特的东西,而且天生对数字特别敏感,一次偶然的机会,他发现了一个有趣的四位数2992,这个数,它的十进制数表示,其四位数字之和为2+9+9+2=22,它的十六进制数BB0,其四位数字之和也为22,同时它的十二进制数表示1894,其四位数字之和也为22,啊哈,真是巧啊。
Py非常喜欢这种四位数,由于他的发现,所以这里我们命名其为Py数。
现在给你一个十进制4位数n,你来判断n是不是Py数,若是,则输出Yes,否则输出No。
如n=2992,则输出Yes; n = 9999,则输出No。
solve¶
http://www.68idc.cn/help/jiabenmake/qita/2014042492096.html
#需要用到十二进制,所以得自己写一个10进制转n进制的转换函数
"""
看了下题解,自愧不如啊,果然我傻逼了。。。代码重复性太高了我自己的
def conv(n,m):
temp = []
while n != 0:
temp.append(n % m)
n = n / m
return sum(temp)
print conv(n,16) == conv(n,12) == conv(n,10) and 'Yes' or 'No' # 居然利用短路逻辑了
"""
def decimal2n(num, n):
'采用除n取余法'
answer = ""
while True:
answer = str(num % n) + answer
num = num // n
if num <= 0:
break
return answer
def isPyNum(num):
dec_sum = sum([int(i) for i in str(num)])
if isSixteenPyNum(num, dec_sum) and isTwelvePyNum(num, dec_sum):
return True
else:
return False
def isSixteenPyNum(num, total):
num = hex(num)[2:]
hex_sum = sum([int(i, 16) for i in str(num)])
return hex_sum == total
def isTwelvePyNum(num, total):
num = decimal2n(num, 12)
twelve_sum = sum([int(i, 12) for i in str(num)])
return twelve_sum == total
if __name__ == '__main__':
# num = 2992
if isPyNum(n):
print("Yes")
else:
print("No")
题目 42: 分拆素数和¶
描述¶
把一个偶数拆成两个不同素数的和,有几种拆法呢?
现在来考虑考虑这个问题,给你一个不超过10000的正的偶数n,计算将该数拆成两个不同的素数之和的方法数,并输出。
如n=10,可以拆成3+7,只有这一种方法,因此输出1.
solve¶
#个人思路,遍历一遍10000以内的素数。
#然后我就得先有个10000以内的素数表
#自己的方法虽然过了,但是个人觉得时间复杂度应该会高些,毕竟是O(n)算法
"""
以下看下题解吧:
看了题解,比较喜欢的是这个(利用哈希表而不是列表这样时间复杂度快了N多):
def sushuDic(n):
dic = {x: True for x in range(2, n)}
for x in dic.keys():
if dic[x]:
i = x * 2
while i < n:
dic[i] = False
i = i + x
return dic
def divideCnt(n):
dic = sushuDic(n)
cnt = 0
for i in range(2, n // 2):
if dic[i] and dic[n-i]:
cnt = cnt + 1
return cnt
print divideCnt(n)
用哈希表快速求素数。
"""
import math
def createPrimeList(num):
List = []
for i in range(2, num):
prime = True
for j in range(2, int(math.sqrt(i)) + 1):
if i % j == 0:
prime = False
break
if prime:
List.append(i)
return List
def countWays(num, prime_list):
counts = 0
for each in prime_list:
if each >= num:
break
elif each != (num - each) and (num - each) in prime_list:
counts += 1
return counts // 2
if __name__ == '__main__':
prime_list = createPrimeList(10000)
# n = 10
ways = countWays(n, prime_list)
print(ways)
题目 43: 斐波那契数列¶
描述¶
斐波那契数列为1,1,2,3,5,8...。数列从第三项起满足,该项的数是其前面两个数之和。
现在给你一个正整数n(n < 10000
), 请你求出第n个斐波那契数取模20132013的值(斐波那契数列的编号从1开始)。
solve¶
#这道题我还是搬ACM的题解吧,毕竟之前用那个方法(C++下的)过了2ms的题
#所以算法时间复杂度绝对是可以的
#以下:f[n] = f[n - 1] + f[n - 2]
#附算法网址:http://www.acmerblog.com/fibonacci-3395.html
"""
题解用的迭代:
def count(n):
x = [0,1]
for i in range(2,n+1):
x.append(x[i-1] + x[i-2])
return x[n]
print count(n)%20132013
"""
def multiply(c, a, b, mod):
tmp = [ a[0][0] * b[0][0] + a[0][1] * b[1][0],
a[0][0] * b[0][1] + a[0][1] * b[1][1],
a[1][0] * b[0][0] + a[1][1] * b[1][0],
a[1][0] * b[0][1] + a[1][1] * b[1][1] ]
c[0][0] = tmp[0] % mod
c[0][1] = tmp[1] % mod
c[1][0] = tmp[2] % mod
c[1][1] = tmp[3] % mod
def fibonacci(n, mod):
if n == 0:
return 0
elif n <= 2:#这里表示第0项为0,第1,2项为1
return 1
a = [[1, 1], [1, 0]]
result = [[1, 0], [0, 1]]
n -= 2
while n > 0:
if n % 2 == 1:
multiply(result, result, a, mod)
multiply(a, a, a, mod)
n //= 2 #注意这里不能写n /= 2,得用地板除法
s = (result[0][0] + result[0][1]) % mod
return s
if __name__ == '__main__':
mod = 20132013
answer = fibonacci(n, mod)
print(answer)
题目 44: 超级楼梯¶
描述¶
有一楼梯共n级,刚开始时你在第一级,若每次只能跨上一级或二级,要走上第n级,共有多少种走法?
现在给你一个正整数n(0<n<40
),请你输出不同的走法数。
如n=2
,则输出1(你只有一种走法,走一步,从第一级到第二级)
solve¶
#这道题不就是斐波那契么,= =我觉得是
#f[n] = f[n - 1] + f[n - 2]
#因为n比较少,所以我用迭代吧
"""
另一种迭代的表达形式:
a = 1
b = 2
for i in range(1, n-1):
c = a + b
a = b
b = c
print a
"""
def fibonacci(n):
f = [0, 1]
for i in range(2, n + 1):
f.append(f[i - 1] + f[i - 2])
return f[-1]
if __name__ == '__main__':
# n = 4
print(fibonacci(n))
题目 45: 砝码问题¶
描述¶
有一组砝码,重量互不相等,分别为m1、m2、m3……mn;它们可取的最大数量分别为x1、x2、x3……xn。
现要用这些砝码去称物体的重量,问能称出多少种不同的重量。
现在给你两个正整数列表w和n, 列表w中的第i个元素w[i]表示第i个砝码的重量,列表n的第 i 个元素 n[i] 表示砝码i的最大数量。i从0开始,请你输出不同重量的种数。
如:w=[1,2], n=[2,1], 则输出5(分析:共有五种重量:0,1,2,3,4)
solve¶
#为啥我看到这种问题,第一眼想到的是背包问题。
#不浪费时间,果断看题解
#喵了一遍题解,DP+遍历的说?
#自己的思路有三种循环。
#一次性过了,简述一下思路吧,第一个循环表示每一个砝码来一次,第二个循环表示
#该砝码从1个取到n[i]个,第三个循环表示上一个的所有情况每个都取一下然后再来+现在的这个
#感觉自己的代码时间复杂度很大,不是一般的大!
def solve(w, n):
total = set()
total.add(0)
length = len(w)
for i in range(length):
tmp_set = set()
for j in range(1, n[i] + 1):
for each in total:
tmp = each + j * w[i]
# print("i = {0}, j = {1}, each = {2}, tmp = {3}"\
# .format(i, j, each, tmp))
if tmp not in total:
tmp_set.add(tmp)
total.update(tmp_set)
return total
if __name__ == '__main__':
# w = [1, 2]
# n = [2, 1]
print(len(solve(w, n)))
"""
题解的:
---version 1---
total_w=0
length_n=len(w)
for i in range(length_n):
total_w+=w[i]*n[i]
S=set([])
S.add(total_w)
for i in range(length_n):
temp_S=S.copy()
for s in temp_S:
j=1
while j<=n[i]:
S.add(s-j*w[i])
j+=1
print len(S)
---version 2---
若m1能被称出,则total_w-m1亦能被称出,如此,可以稍作优化:
total_w=0
length_n=len(w)
for i in range(length_n):
total_w+=w[i]*n[i]
S=set([])
S.add(total_w)
for i in range(length_n):
temp_S=S.copy()
for s in temp_S:
j=1
while j<=n[i]:
if s-j*w[i]>=total_w/2:
S.add(s-j*w[i])
S.add(total_w-s+j*w[i])
j+=1
else:
break
print S
print len(S)
"""
题目 46: 取石子游戏¶
描述¶
有两堆石子,数量任意,可以不同。游戏开始由两个人轮流取石子。游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在两堆中同时取走相同数量的石子。最后把石子全部取完者为胜者。
现在给出初始的两堆石子的数目a和b,如果轮到你先取,假设双方都采取最好的策略,问最后你是胜者还是败者。
如果你是胜者,输出Win,否则输出Loose。
例如,a=3,b=1, 则输出Win(你先在a中取一个,此时a=2,b=1,此时无论对方怎么取,你都能将所有石子都拿走).
solve¶
#之前在ACM课上听过,这是博弈论的东西
#查了一下,是威佐夫博奕
#ACM解题报告:http://blog.csdn.net/csust_acm/article/details/7957180
import math
def solve(a, b):
'面对非奇异局势,先拿者必胜;反之,则后拿者取胜。'
a, b = min(a, b), max(a, b) #奇异局势下,bk > ak
k = b - a #奇异局势下,bk = ak + k
m = int((math.sqrt(5.0) + 1) * k / 2) #奇异局势下,ak = 这个公式
return m != a #若m == a,则为奇异局势,先拿者必败
if __name__ == '__main__':
#a, b = 3, 1
#a, b = 2, 1
#a, b = 8, 4
#a, b = 4, 7
if solve(a, b):
print("Win")
else:
print("Loose")
"""
题解用到了奇异态势,并不好理解。
相关分析参考:http://blog.renren.com/share/251405117/755224137
推出一个概念:奇异态势。有以下特点:
1.无法从一个奇异态势一步走到另一个奇异态势;
2.任何非奇异态势可以一步走到某一个奇异态势。
于是可构建以下奇异态势,前几组分别为(0,0),(1,2),(3,5),(4,7),(6,10),(8,13)....满足:1)各奇异态势间无重复元素;2)步长(|a-b|)递增,保证各不相同
以此可以计算出(a,b)以下的奇异态势,若(a,b)不在其中,则必赢。
题解代码:
m,n=max(a,b),min(a,b)
small_num=0
large_num=0
step=0
S=set([])
S.add(small_num)
S.add(large_num)
Win=True
while small_num<n and large_num<m:
while small_num in S:
small_num+=1
step+=1
large_num=small_num+step
S.add(small_num)
S.add(large_num)
if small_num==n and large_num==m:
Win=False
break
# print '('+str(small_num)+','+str(large_num)+')'
if Win:
print 'Win'
else:
print 'Loose'
"""
题目 47: 杨辉三角¶
描述:¶
还记得中学时候学过的杨辉三角吗?具体的定义这里不再描述,你可以参考以下的图形:
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
..............
先在给你一个正整数n,请你输出杨辉三角的前n层
注意:层数从1开始计数,每层数字之间用一个空格隔开,行尾不要有空格。
如n=2
,则输出:
1
1 1
solve¶
def getNum(row, col, num):
if col == 0 or col == row:
num[row][col] = 1
if num[row][col] == -1:
num[row][col] = getNum(row - 1, col - 1, num) + \
getNum(row - 1, col, num)
return num[row][col]
def solve(n, num_array):
for i in range(n):
for j in range(i):
print(getNum(i, j, num_array)),
print(getNum(i, i, num_array))
def createNumArray(n):
num = []
for i in range(n):
num.append([-1 for i in range(n)])
return num
if __name__ == "__main__":
num_array = createNumArray(n)
solve(n, num_array)
题目 48: 砝码问题II¶
描述¶
有一组砝码,重量互不相等,分别为m1、m2、m3……mn;每种砝码的数量有无限个。
现要用这些砝码去称物体的重量,给你一个重量n,请你判断有给定的砝码能否称出重量n。
现在给你一个正整数列表w和一个正整数n,列表w中的第i个元素w[i]表示第i种砝码的重量,n 表示要你判断的重量。如果给定砝码能称出重量n,输出Yes,否则输出No。
例如,w=[2,5,11], n=9,则输出Yes(取两个2,一个5)。
solve¶
#悲催了,用别人的也是超时了,改进一下之前的吧,不求出全部了,一求到那个就返回True
#这下成功了
def getWeightList(w, amount, n):
total = set()
total.add(0)
length = len(w)
for i in range(length):
tmp_set = set()
for j in range(1, amount[i] + 1):
for each in total:
tmp = each + j * w[i]
if tmp == n:
return True
# print("i = {0}, j = {1}, each = {2}, tmp = {3}"\
# .format(i, j, each, tmp))
if tmp not in total:
tmp_set.add(tmp)
total.update(tmp_set)
return False
def getMaxAmount(w, n):
amount = []
for each in w:
amount.append(n // each)
return amount
if __name__ == "__main__":
# w = [2, 5, 11]
# n = 9
amount = getMaxAmount(w, n)
if getWeightList(w, amount, n):
print("Yes")
else:
print("No")
"""
题解:
---version 1---
完全背包:
L=len(w)
s=[-1 for i in range(n+1)]
s[0]=0
for i in range(L):
for j in range(1,n+1):
if j-w[i]>=0 and s[j-w[i]]!=-1:
s[j]=1
if s[n]!=-1:
print 'yes'
else:
print "no"
"""
题目 49: 进制转换¶
solve¶
#基本思路是除b取余法
#搞了半天错误的原因是没处理负数情况,醉了。。。
def solve(num, n):
character = "0123456789ABCDEF"
answer = ""
if num < 0:
symbol = "-"
num = -num
else:
symbol = ""
while num > 0:
answer += character[num % n]
num //= n
return symbol + answer[::-1]
if __name__ == "__main__":
# a, b = -100, 16
print(solve(a, b))
"""
题解:
---version 1---
d = '0123456789ABCEDFGHIJKLMNOPQRSTUVWXYZ'
def f(x,y):
s = []
while x>=y:
s.append(x%y)
x /=y
s.append(x)
return ''.join([d[c] for c in s[::-1]])
print (a<0 and '-' or '')+f(abs(a),b)
"""
题目 50: Py扔铅球¶
描述¶
Py不但是编程大牛,而且是运动健将。比如说扔铅球,1000m,现在Py参加校园扔铅球比赛,给你Py的身高a(双精度数),球落地点与Py头部的连线与水平线的夹角 b(弧度),要你编写一个程序计算Py扔铅球的水平距离。
a,b都是浮点数,注意b是弧度,其中, 140 < a < 200, 0 < b < 1.5
.
输出你求出的水平距离,保留到小数点后三位。
如,a = 165.5, b = 1.1, 则输出 84.234
solve¶
#思路:画一下图,易得a/x = tanb(θ)
#format函数参考:http://pyformat.info/
import math
def solve(a, b):
answer = a / math.tan(b) #注意math.tan(b)本来就要求弧度制而不是角度!
return answer
if __name__ == "__main__":
a, b = 165.5, 1.1
print('{:.3f}'.format(solve(a, b)))
题目 51: 降序排序¶
描述¶
给你一个list L, 如 L=[2,8,3,50], 对L进行降序排序并输出,如样例L的结果为[50,8,3,2]
solve¶
if __name__ == "__main__":
# L = [2,8,3,50]
L.sort(reverse = True)
print(L)
题目 52: 因子平方和¶
描述¶
6 的因子有 1, 2, 3 和 6, 它们的平方和是 1 + 4 + 9 + 36 = 50
.
如果 f(N) 代表正整数 N 所有因子的平方和, 那么 f(6) = 50.
现在令 F 代表 f 的求和函数, 亦即 F(N) = f(1) + f(2) + .. + f(N)
, 显然 F 一开始的 6 个值是: 1, 6, 16, 37, 63 和 113.
那么对于任意给定的整数 N (1 <= N <= 10^8
), 输出 F(N) 的值.
solve¶
#目测自己的算法会超时
#思路:一个函数用于求因子,一个函数用于求因子的和f(N),一个函数用于求F(N)
#果然超时了。
"""
附题解2个,自己还没解决出来:
---version 1---
这道题直接做回超时。
技巧一:F(N)可以看作N个1**2,N/2个2**2,N/3个3**2,,,,,以此类推。即便这样,还会超时。
技巧二:对称。N个1**2对称1个N**2,N/2个2**2对称2个N/2**2,,,,,,. 但是,值得注意的是对称并不是一对一的,而是一对多的。比如N个1**2对称的是1个 (N/2**2,,,,,,,(N-1)**2,N**2]。依次类推。
import math
def sumsqr(m, n):
if m >= n:
return 0
return n*(n+1)*(2*n+1)/6-m*(m+1)*(2*m+1)/6
def F(n):
i=1
seg=[]
sum1=0
high = n;
while i*i <=n:
sum1 += (n/i)*i**2
low = max(n/(i+1), i);
sum1 += i*sumsqr(low,high)
i += 1
high = low
return sum1
F(N)
计算清楚每个N**2序列的个数即可
def func(N):
result=0
n=N+1
a,b=0,0
while n>1:
n=N/(N/n+1)
a,b=N/n,a
print n
result+=(a-b)*n*(n+1)*(2*n+1)/6
print func(N)
sorry,贴错了~更新下:
def func(N):
result=0
n=N+1
a,b=0,0
while n>1:
n=N/(N/n+1)
a,b=N/n,a
result+=(a-b)*n*(n+1)*(2*n+1)/6
return result
print func(N)
---version 2---
#N = 2
def cal(x):
return x * (x + 1) * (2 * x + 1) / 6
ans = 0
for i in range(1, N / 10 + 1):
ans += (N / i) * i * i
for i in range(9, 0, -1):
ans += i * (cal(N / i) - cal(N / (i + 1)))
print ans
"""
def getDivisors(num):
divisors = []
for i in range(1, num + 1):
if num % i == 0:
divisors.append(i)
return divisors
def getDivisorsSum(num):
divisors = getDivisors(num)
answer = 0
for each in divisors:
answer += each ** 2
return answer
def getTotalDivisorsSum(num):
answer = 0
for i in range(num):
answer += getDivisorsSum(i + 1)
return answer
if __name__ == "__main__":
for N in range(1, 8):
print(getTotalDivisorsSum(N))
题目 56: 切西瓜¶
solve¶
#通过n条直线,最多可将一个平面分割成1+(1+n)n/2
#后面才看到这应该是三维平面里的公式才能:Y = (X ** 3 +5 * X + 6) / 6
"""
题解还有另外一个公式:
C(n,0)+C(n,1)+C(n,2)+C(n,3)
"""
def spaceSubdivide(n):
return int((n ** 3 + 5 * n + 6) / 6)
if __name__ == "__main__":
# n = 1
print(spaceSubdivide(n))
题目 67: 回文数 I¶
描述¶
若一个数(首位不为0)从左到右读与从右到左读都是一样,这个数就叫做回文数,例如12521就是一个回文数。
给定一个正整数,把它的每一个位上的数字倒过来排列组成一个新数,然后与原数相加,如果是回文数则停止,如果不是,则重复这个操作,直到和为回文数为止。给定的数本身不为回文数。
例如:87则有:
STEP1: 87+78=165
STEP2: 165+561=726
STEP3: 726+627=1353
STEP4: 1353+3531=4884
现在给你一个正整数M(12 <= M <= 100
),输出最少经过几步可以得到回文数。如果在8步以内(含8步)不可能得到回文数,则输出0。
例如:M=87,则输出4.
solve¶
#感觉是遍历一遍就出来了
def countTimes(num):
counts = 0
while counts <= 8:
counts += 1
num = int(str(num)[::-1]) + num
if isHuiWen(num):
return counts
else:
return 0
def isHuiWen(num):
return str(num)[::-1] == str(num)
if __name__ == "__main__":
# M = 87
print(countTimes(M))
"""
题解
---version 1---
flag = 0
for i in range(8):
M = M + int(str(M)[::-1])
if str(M) == str(M)[::-1]:
flag = i + 1
break
print flag
---version 2---
fg=0
while fg<8:
S=int(str(M)[::-1])
D=S+M
fg += 1
if D==int(str(D)[::-1]):
print fg
break
else:
M=D
else:
print 0
"""
题目 1: just print a+b¶
描述¶
give you two var a and b, print the value of a+b, just do it!!
提示¶
挑战python栏目的所有题目,题目中所给变量使用前不用声明,也不用赋值,系统自动赋值。 如本题,只需一行代码即可: print a + b 系统会自动为a和b赋值,并检查代码执行结果和标准答案是否相同。
solve¶
print(a + b)
题目 2: list排序¶
描述¶
给你一个list L, 如 L=[2,8,3,50], 对L进行升序排序并输出, 如样例L的结果为[2,3,8,50]
solve¶
L.sort()
print( L )
题目 6: 求解100以内的所有素数¶
solve¶
def prime(n):
'''
傻瓜法
'''
prime = []
for i in range(2, n+1):
if is_prime(i):
prime.append(i)
return prime
def is_prime(n):
if n == 2:
return True
for i in range(2, n):
if n % i == 0:
return False
return True
第2种解法 将素数判断的遍历长度缩减到 sqrt(n)
def prime(n):
'''
sqrt n 法
'''
prime = []
for i in range(2, n+1):
if is_prime(i):
prime.append(i)
return prime
def is_prime(n):
if n == 2:
return True
for i in range(2, floor(sqrt(n))+1):
if n % i == 0:
return False
return True
第3种方法 埃拉托斯特尼筛法
def prime_3(n):
'''
普通筛法 埃拉托斯特尼筛法
'''
check = np.ones(n+1)
prime = []
for i in range(2, n+1):
if check[i]:
prime.append(i)
# 此循环也可以和上层 if 并列 但这样效率高一些
for j in range(2*i, n+1, i):
check[j] = 0
return prime
第4种方法 线性筛法 欧拉筛法
def prime_4(n):
'''
线性筛法 欧拉筛法
'''
check = np.ones(n+1)
prime = []
tot = 0
for i in range(2, n+1):
if check[i]:
prime.append(i)
tot += 1
for j in range(tot):
if i * prime[j] > n:
break
check[i * prime[j]] = 0
if i % prime[j] == 0:
break
return prime
题目 8: 求中位数¶
描述¶
给你一个list L, 如 L=[0,1,2,3,4], 输出L的中位数(若结果为小数,则保留一位小数)。
solve¶
#若有n个数,n为奇数,则选择第(n+1)/2个为中位数,若n为偶数,则中位数是(n/2以及n/2+1)的平均数
#这道题感觉有问题,因为按ac的这种写法,算5.5的话,得到的结果是3位小数,打印出来也是3位小数,不合题意啊
#主要是学一下浮点运算/2.0,其他就算了吧
L.sort()
length = len( L )
if length % 2 != 0 :
num = L[ length // 2 ]
if isinstance( num,int ):
print( num )
else :
print( "%.1f" % num )
else :
num1 = L[ ( length // 2 ) - 1 ]
num2 = L[ ( length // 2 ) ]
num = num1 + num2
print( num / 2.0 )
剑指Offer¶
题目4-6¶
4 二维数组中的查找¶
题目描述
在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
[
[ 1, 5, 9],
[10, 11, 13],
[12, 13, 15]
]
解题思路
从右上角或左下角开始查找,假设从右上角开始查找,如果待查找的数小于当前位置的数,那么一定在当前位置的左边,即当前位置的下边不可能存在比自己小的数。 同理,如果待查找的数大于当前位置的数,那么一定在当前位置的下边,即当前位置的左边不可能存在比自己大的数。这一原则也就意味着不能从左上角或右下角开始查找, 因为查找的过程中无法做到精准无误的排除不可能区域。
选择从右上角开始查找
class Solution
{
public:
bool Find(vector<vector<int> > array,int target)
{
bool res = false;
int row = array.size( );
int col = array[0].size( );
// 我们从右上角的元素找起来
// 如果查找的元素比当前位置元素小, 就向左走
// 如果查找的元素比当前位置元素大, 就向下走
for(int i = 0, j = col -1;
(i >=0 && i < row) && (j >= 0 && j < col);)
{
if(target == array[i][j])
{
res = true;
break;
}
else if(target < array[i][j]) // 小的元素在当前位置左侧
{
j--;
}
else
{
i++;
}
}
return res;
}
};
利用 python 实现一下
# -*- coding:utf-8 -*-
class Solution:
# array 二维列表
def Find(self, array, target):
row = len(array)
col = len(array[0])
res = False
x = 0
y = col -1
while 0 <= x < row and 0 <= y < col:
if array[x][y] == target:
res = True
break
elif array[x][y] < target:
x += 1
else:
y -= 1
return res
牛客上利用 python 字符串方法实现的查找
# -*- coding:utf-8 -*-
class Solution:
# array 二维列表
def Find(self, target, array):
# write code here
n=len(array)
flag='false'
for i in range(n):
if target in array[i]:
flag='true';
break
return flag
while True:
try:
S=Solution()
# 字符串转为list
L=list(eval(raw_input()))
array=L[1]
target=L[0]
print(S.Find(target, array))
except:
break
5 空格替换¶
题目描述
请实现一个函数,将一个字符串中的空格替换成“%20”。 例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
We Are Happy
We%20Are%20Happy
解题思路
思路分为两种,第一是不在原来的字符串上替换,直接新开一个数组然后一次赋值,这个很简单,但我想可能不是出题人希望考察的;第二是在原来的字符串上进行替换 那么因为空格和“%20”的长度不同,就相当于存在一个插入操作。
一种思路是,依次遍历,发现空格就增加字符串长度,然后填入 “%20” 后面的字符全部向前移两位,这样的话如果遇到连续的空格那么就可能会做无用功
另一种思路是,先遍历一遍字符串,统计出空格的数目,这样就知道了替换后字符串的总长度,然后再对字符串进行替换,避免了无用的移动工作。
之所以选择从后往前遍历,是因为如果空格足够多的话,这样的遍历方式在一开始一段时间内是不需要移动的,只需要复制。
/*
一个偷懒的写法,利用 string 的 find 方法实现,最后再转换回 char*
*/
class Solution
{
public:
void replaceSpace(char *str, int length)
{
string a(str);
int pos = 0;
while (pos != string::npos)
{
pos = a.find(" ", pos);
if (pos != string::npos)
{
a.replace(pos, 1, "%20");
}
}
cout << "fuck" << endl;
//最后将 string 转换为 char*
strcpy(str, a.data());
}
};
/*
笨拙型
*/
#include <iostream>
#include <Windows.h>
using namespace std;
#define __tmain main
class Solution
{
public:
void replaceSpace(char *str,int length)
{
int i = length - 1, j;
int len = length;
// 从后往前遍历
for(i = length - 1; i >= 0; i--)
{
//cout <<str[i] <<endl;
// 如果当前字符是空格
if(str[i] == ' ')
{
// 从空格变成%20长度增加了2
len += 2;
for(j = len - 1; j > i + 2; j--)
{
str[j] = str[j - 2];
}
str[j--] = '0';
str[j--] = '2';
str[j--] = '%';
}
//cout <<str <<endl;
}
str[len] = '\0';
}
};
int __tmain( )
{
char str[10 + 1] = "a b c d";
Solution solu;
solu.replaceSpace(str, 10);
cout <<str <<endl;
system("pause");
return 0;
}
// 优化型
#include <iostream>
#include <Windows.h>
using namespace std;
#define __tmain main
class Solution
{
public:
void replaceSpace(char *str, int length)
{
int i, j;
int count = 0;
int len = length;
for(int i = 0; i < length; i++)
{
if(str[i] == ' ')
{
count++;
}
}
len = length + count * 2;
for(i = length - 1, j = len - 1;
i >= 0 && j >= 0;)
{
if(str[i] == ' ')
{
str[j--] = '0';
str[j--] = '2';
str[j--] = '%';
i--;
}
else
{
str[j--] = str[i--];
}
}
str[len] = '\0';
}
};
int __tmain( )
{
char str[10 + 1] = "a b c d";
Solution solu;
solu.replaceSpace(str, 10);
cout << str << endl;
system("pause");
return 0;
}
python 实现
# -*- coding:utf-8 -*-
class Solution:
# s 源字符串
def replaceSpace(self, s):
# write code here
s = list(s)
count=len(s)
for i in range(0,count):
if s[i]==' ':
s[i]='%20'
return ''.join(s)
6 从尾到头打印链表¶
题目描述
输入一个链表,从尾到头打印链表每个节点的值。
输入描述:
输入为链表的表头
输出描述:
输出为需要打印的 vector
解题思路
首先我们想到的就是反转链表了,甚至可以利用 reverse 方法,这样再次遍历的时候就相当于从尾到头打印了。
但是修改输入数据真的可行么?在面试时候,如果我们打算修改输入的数据,最好先问问面试官是不是允许修改 通常打印只是一个只读操作,我们肯定不希望输入时候修改链表的内容
1.利用栈的先进后出特性,每经过一个结点的时候,把该结点放到一个栈中。当遍历完整个链表后,再从栈顶开始逐个输出结点的值,此时输出的结点的顺序已经反转过来了。
struct ListNode
{
public:
int val;
struct ListNode *next;
/*ListNode(int x) :
val(x), next(NULL)
{
}*/
};
class Solution
{
public:
vector<int> printListFromTailToHead(struct ListNode* head)
{
ListNode *node = head;
stack<int> st;
int count = 0;
while (node != NULL)
{
dout << node->val << " in stack" << endl;
st.push(node->val);
count++;
node = node->next;
}
vector<int> res(count);
dout << "count = " << count << endl;
for (int i = 0; i < count && st.empty() != true; i++)
{
dout << st.top() << " in vector" << endl;
//res.push_back(st.top( ));
res[i] = st.top();
st.pop();
}
return res;
}
};
2.利用递归。因为递归本来就是一个函数压栈的过程,所以很自然的适合此题的场景
class Solution
{
public:
//利用 递归的实现
vector<int> res; //因为利用了递归,所以不能定义在函数内部
vector<int> printListFromTailToHead_0(struct ListNode* head)
{
if(head != NULL)
{
printListFromTailToHead(head->next);
res.push_back(head->val);
}
return res;
}
};
- 不使用 栈 也不使用 递归,利用链表的头插法的逆序特性
但此方法会改变原链表
class Solution
{
public:
//利用头插法实现
vector<int> printListFromTailToHead(struct ListNode* head)
{
ListNode *Head = new ListNode;//头节点 即 -1 节点
Head->next = NULL;
ListNode *cur = head;
for (; cur != NULL;)
{
ListNode* temp = cur->next;
cur->next = Head->next;
Head->next = cur;
cur = temp;
}
Head = Head->next;
vector<int> array;
for (; Head != NULL;Head = Head->next)
{
array.push_back(Head->val);
}
return array;
}
};
套题¶
网易2019实习生招聘编程题集合¶
1 牛牛找工作¶
3¶
为了找到自己满意的工作,牛牛收集了每种工作的难度和报酬。牛牛选工作的标准是在难度不超过自身能力值的情况下,牛牛选择报酬最高的工作。在牛牛选定了自己的工作后,牛牛的小伙伴们来找牛牛帮忙选工作,牛牛依然使用自己的标准来帮助小伙伴们。牛牛的小伙伴太多了,于是他只好把这个任务交给了你。
输入描述:
每个输入包含一个测试用例。
每个测试用例的第一行包含两个正整数,分别表示工作的数量N(N<=100000)和小伙伴的数量M(M<=100000)。
接下来的N行每行包含两个正整数,分别表示该项工作的难度Di(Di<=1000000000)和报酬Pi(Pi<=1000000000)。
接下来的一行包含M个正整数,分别表示M个小伙伴的能力值Ai(Ai<=1000000000)。
保证不存在两项工作的报酬相同。
输出描述:
对于每个小伙伴,在单独的一行输出一个正整数表示他能得到的最高报酬。一个工作可以被多个人选择。
输入例子1:
3 3
1 100
10 1000
1000000000 1001
9 10 1000000000
输出例子1:
100
1000
1001
自测 通过
# -*- coding: utf-8 -*-
while True:
try:
jobs = []
N, M = map(int, input().split())
for i in range(N):
hard, sal = map(int, input().split())
jobs.append((hard, sal))
sorted(jobs)
friends = list(map(int, input().split()))
for friend in friends:
sal = jobs[0][1]
for job in jobs:
if job[0] <= friend:
if job[1] >= sal:
sal = job[1]
else:
break
print(sal)
except:
break
2 被 3 整除¶
小Q得到一个神奇的数列: 1, 12, 123,...12345678910,1234567891011...。并且小Q对于能否被3整除这个性质很感兴趣。小Q现在希望你能帮他计算一下从数列的第l个到第r个(包含端点)有多少个数可以被3整除。
输入描述:
输入包括两个整数l和r(1 <= l <= r <= 1e9), 表示要求解的区间两端。
输出描述:
输出一个整数, 表示区间内能被3整除的数字个数。
输入例子1:
2 5
输出例子1:
3
例子说明1:
12, 123, 1234, 12345...
其中12, 123, 12345能被3整除。
解题思路
这题只能找规律了 能否被 3 整除符合 0 1 1 0 1 1 的规律
def func(n):
timer = n // 3
mod = n % 3
if mod == 0:
return 2 * timer
else:
return 2 * timer + (0 if mod == 1 else 1)
while True:
try:
l, r = map(int, input().split())
print(func(r) - func(l-1))
except:
break
牛客 AC
3 安置路灯¶
小Q正在给一条长度为n的道路设计路灯安置方案。为了让问题更简单,小Q把道路视为n个方格,需要照亮的地方用'.'表示, 不需要照亮的障碍物格子用'X'表示。小Q现在要在道路上设置一些路灯, 对于安置在pos位置的路灯, 这盏路灯可以照亮pos - 1, pos, pos + 1这三个位置。小Q希望能安置尽量少的路灯照亮所有'.'区域, 希望你能帮他计算一下最少需要多少盏路灯。
输入描述:
输入的第一行包含一个正整数t(1 <= t <= 1000), 表示测试用例数
接下来每两行一个测试数据, 第一行一个正整数n(1 <= n <= 1000),表示道路的长度。
第二行一个字符串s表示道路的构造,只包含'.'和'X'。
输出描述:
对于每个测试用例, 输出一个正整数表示最少需要多少盏路灯。
输入例子1:
2
3
.X.
11
...XX....XX
输出例子1:
1
3
解题思路
利用栈,栈空则 X 不入栈。栈满三个就清空,路灯计数 +1
牛客 AC
N = int(input())
for i in range(N):
n = int(input())
road = input()
stack = []
sum = 0
for ch in road:
if not stack:
if ch == 'X':
continue
else:
stack.append(ch)
else:
stack.append(ch)
if len(stack) == 3:
sum += 1
stack = []
if len(stack):
sum += 1
print(sum)
4 迷路的牛牛¶
牛牛去犇犇老师家补课,出门的时候面向北方,但是现在他迷路了。虽然他手里有一张地图,但是他需要知道自己面向哪个方向,请你帮帮他。
输入描述:
每个输入包含一个测试用例。
每个测试用例的第一行包含一个正整数,表示转方向的次数N(N<=1000)。
接下来的一行包含一个长度为N的字符串,由L和R组成,L表示向左转,R表示向右转。
输出描述:
输出牛牛最后面向的方向,N表示北,S表示南,E表示东,W表示西。
输入例子1:
3
LRR
输出例子1:
E
解题思路
构造一个环形 buff 区,利用余数实现。
牛客 AC
ls = ['N', 'E', 'S', 'W']
while True:
try:
n = int(input())
seq = input()
stat = 0
for ch in seq:
if ch == 'L':
stat = (stat-1) % 4
if ch == 'R':
stat = (stat+1) % 4
print(ls[stat])
except:
break
5 数对¶
链接:https://www.nowcoder.com/questionTerminal/bac5a2372e204b2ab04cc437db76dc4f
牛牛以前在老师那里得到了一个正整数数对(x, y), 牛牛忘记他们具体是多少了。
但是牛牛记得老师告诉过他x和y均不大于n, 并且x除以y的余数大于等于k。牛牛希望你能帮他计算一共有多少个可能的数对。
输入描述:
输入包括两个正整数n,k(1 <= n <= 10^5, 0 <= k <= n - 1)。
输出描述:
对于每个测试用例, 输出一个正整数表示可能的数对数量。
示例1
输入
5 2
输出
7
说明
满足条件的数对有(2,3),(2,4),(2,5),(3,4),(3,5),(4,5),(5,3)
解题思路
对于每一个 y 考虑 x 从 0 到 n 的所有可能,可分为 余数 从 0 到 y-1 的很多组还有最后一组零头,另外如果 k=0 考虑去掉 x 为 0 的计数(虽说 x y 都为正整数,但考虑 0 是为了方便)
while True:
try:
n, k = map(int, input().split())
res = 0
for y in range(k+1, n+1):
a = (n+1)// y
b = (n+1)%y
res += a * (y-k) + ((b-k) if b >=k else 0)
if k == 0:
res -= n-k
print(res)
except:
break
排序算法¶
注意事项¶
大常数
在求近似时,如果低级项的常数系数很大,那么近似的结果就是错误的。
缓存
计算机系统会使用缓存技术来组织内存,访问数组相邻的元素会比访问不相邻的元素快很多。
对最坏情况下的性能的保证
在核反应堆、心脏起搏器或者刹车控制器中的软件,最坏情况下的性能是十分重要的。
随机化算法
通过打乱输入,去除算法对输入的依赖。
均摊分析
将所有操作的总成本所以操作总数来将成本均摊。例如对一个空栈进行 N 次连续的 push() 调用需要访问数组的元素为 N+4+8+16+...+2N=5N-4(N 是向数组写入元素,其余的都是调整数组大小时进行复制需要的访问数组操作),均摊后每次操作访问数组的平均次数为常数。
1.5 希尔排序¶
对于大规模的数组,插入排序很慢,因为它只能交换相邻的元素,如果要把元素从一端移到另一端,就需要很多次操作。
希尔排序的出现就是为了改进插入排序的这种局限性,它通过交换不相邻的元素,使得元素更快的移到正确的位置上。
希尔排序使用插入排序对间隔 h 的序列进行排序,如果 h 很大,那么元素就能很快的移到很远的地方。通过不断减小 h,最后令 h=1,就可以使得整个数组是有序的。

public class Shell {
public static void sort(Comparable[] a) {
int N = a.length;
int h = 1;
while (h < N / 3) {
h = 3 * h + 1;// 1, 4, 13, 40, ...
}
while (h >= 1) {
for (int i = h; i < N; i++) {
for (int j = i; j >= h && less(a[j], a[j - h]); j -= h) {
exch(a, j, j - h);
}
}
h = h / 3;
}
}
}
希尔排序的运行时间达不到平方级别,使用递增序列 1, 4, 13, 40, ... 的希尔排序所需要的比较次数不会超过 N 的若干倍乘于递增序列的长度。后面介绍的高级排序算法只会比希尔排序快两倍左右。
2 归并排序¶
归并排序的思想是将数组分成两部分,分别进行排序,然后归并起来。

2.1 归并方法¶
public class MergeSort {
private static Comparable[] aux;
private static void merge(Comparable[] a, int lo, int mid, int hi) {
int i = lo, j = mid + 1;
for (int k = lo; k <= hi; k++) {
aux[k] = a[k]; // 将数据复制到辅助数组
}
for (int k = lo; k <= hi; k++) {
if (i > mid) a[k] = aux[j++];
else if (j > hi) a[k] = aux[i++];
else if (aux[i].compareTo(a[j]) < 0) a[k] = aux[i++]; // 先进行这一步,保证稳定性
else a[k] = aux[j++];
}
}
}
2.2 自顶向下归并排序¶
public static void sort(Comparable[] a) {
aux = new Comparable[a.length];
sort(a, 0, a.length - 1);
}
private static void sort(Comparable[] a, int lo, int hi) {
if (hi <= lo) return;
int mid = lo + (hi - lo) / 2;
sort(a, lo, mid);
sort(a, mid + 1, hi);
merge(a, lo, mid, hi);
}


很容易看出该排序算法的时间复杂度为 O(NlgN)。
因为小数组的递归操作会过于频繁,因此使用插入排序来处理小数组将会获得更高的性能。
2.3 自底向上归并排序¶
先归并那些微型数组,然后成对归并得到的子数组。

public static void busort(Comparable[] a) {
int N = a.length;
aux = new Comparable[N];
for (int sz = 1; sz < N; sz += sz) {
for (int lo = 0; lo < N - sz; lo += sz + sz) {
merge(a, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, N - 1));
}
}
}
3. 快速排序¶
3.1 基本算法¶
归并排序将数组分为两个子数组分别排序,并将有序的子数组归并使得整个数组排序;快速排序通过一个切分元素将数组分为两个子数组,左子数组小于等于切分元素,右子数组大于等于切分元素,将这两个子数组排序也就将整个数组排序了。

public class QuickSort {
public static void sort(Comparable[] a) {
shuffle(a);
sort(a, 0, a.length - 1);
}
private static void sort(Comparable[] a, int lo, int hi) {
if (hi <= lo) return;
int j = partition(a, lo, hi);
sort(a, lo, j - 1);
sort(a, j + 1, hi);
}
}
3.2 切分¶
取 a[lo] 作为切分元素,然后从数组的左端向右扫描直到找到第一个大于等于它的元素,再从数组的右端向左扫描找到第一个小于等于它的元素,交换这两个元素,并不断继续这个过程,就可以保证左指针的左侧元素都不大于切分元素,右指针 j 的右侧元素都不小于切分元素。当两个指针相遇时,将切分元素 a[lo] 和左子数组最右侧的元素 a[j] 交换然后返回 j 即可。

private static int partition(Comparable[] a, int lo, int hi) {
int i = lo, j = hi + 1;
Comparable v = a[lo];
while (true) {
while (less(a[++i], v)) if (i == hi) break;
while (less(v, a[--j])) if (j == lo) break;
if (i >= j) break;
exch(a, i, j);
}
exch(a, lo, j);
return j;
}
3.3 性能分析¶
快速排序是原地排序,不需要辅助数组,但是递归调用需要辅助栈。
快速排序最好的情况下是每次都正好能将数组对半分,这样递归调用次数才是最少的。这种情况下比较次数为 CN=2CN/2+N,也就是复杂度为 O(NlgN)。
最坏的情况下,第一次从最小的元素切分,第二次从第二小的元素切分,如此这般。因此最坏的情况下需要比较 N2/2。为了防止数组最开始就是有序的,在进行快速排序时需要随机打乱数组。
3.4 算法改进¶
因为快速排序在小数组中也会调用自己,对于小数组,插入排序比快速排序的性能更好,因此在小数组中可以切换到插入排序。
最好的情况下是每次都能取数组的中位数作为切分元素,但是计算中位数的代价很高。人们发现取 3 个元素并将大小居中的元素作为切分元素的效果最好。
对于有大量重复元素的数组,可以将数组切分为三部分,分别对应小于、等于和大于切分元素。
三向切分快速排序对于只有若干不同主键的随机数组可以在线性时间内完成排序。

public class Quick3Way {
public static void sort(Comparable[] a, int lo, int hi) {
if (hi <= lo) return;
int lt = lo, i = lo + 1, gt = hi;
Comparable v = a[lo];
while (i <= gt) {
int cmp = a[i].compareTo(v);
if (cmp < 0) exch(a, lt++, i++);
else if (cmp > 0) exch(a, i, gt--);
else i++;
}
sort(a, lo, lt - 1);
sort(a, gt + 1, hi);
}
}
4. 优先队列¶
优先队列主要用于处理最大元素。
4.1 堆¶
定义:一颗二叉树的每个节点都大于等于它的两个子节点。
堆可以用数组来表示,因为堆是一种完全二叉树,而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2,而它的两个子节点的位置分别为 2k 和 2k+1。这里我们不使用数组索引为 0 的位置,是为了更清晰地理解节点的关系。

public class MaxPQ<Key extends Comparable<Key> {
private Key[] pq;
private int N = 0;
public MaxPQ(int maxN) {
pq = (Key[]) new Comparable[maxN + 1];
}
public boolean isEmpty() {
return N == 0;
}
public int size() {
return N;
}
private boolean less(int i, int j) {
return pq[i].compareTo(pq[j]) < 0;
}
private void exch(int i, int j) {
Key t = pq[i];
pq[i] = pq[j];
pq[j] = t;
}
}
4.2 上浮和下沉¶
在堆中,当一个节点比父节点大,那么需要交换这个两个节点。交换后还可能比它新的父节点大,因此需要不断地进行比较和交换操作。把这种操作称为上浮。
private void swim(int k) {
while (k > 1 && less(k / 2, k)) {
exch(k / 2, k);
k = k / 2;
}
}
类似地,当一个节点比子节点来得小,也需要不断的向下比较和交换操作,把这种操作称为下沉。一个节点有两个子节点,应当与两个子节点中最大那么节点进行交换。
private void sink(int k) {
while (2 * k <= N) {
int j = 2 * k;
if (j < N && less(j, j + 1)) j++;
if (!less(k, j)) break;
exch(k, j);
k = j;
}
}
4.4 删除最大元素¶
从数组顶端删除最大的元素,并将数组的最后一个元素放到顶端,并让这个元素下沉到合适的位置。
public Key delMax() {
Key max = pq[1];
exch(1, N--);
pq[N + 1] = null;
sink(1);
return max;
}
4.5 堆排序¶
由于堆可以很容易得到最大的元素并删除它,不断地进行这种操作可以得到一个递减序列。如果把最大元素和当前堆中数组的最后一个元素交换位置,并且不删除它,那么就可以得到一个从尾到头的递减序列,从正向来看就是一个递增序列。因此很容易使用堆来进行排序,并且堆排序是原地排序,不占用额外空间。
堆排序要分两个阶段,第一个阶段是把无序数组建立一个堆;第二个阶段是交换最大元素和当前堆的数组最后一个元素,并且进行下沉操作维持堆的有序状态。
无序数组建立堆最直接的方法是从左到右遍历数组,然后进行上浮操作。一个更高效的方法是从右至左进行下沉操作,如果一个节点的两个节点都已经是堆有序,那么进行下沉操作可以使得这个节点为根节点的堆有序。叶子节点不需要进行下沉操作,因此可以忽略叶子节点的元素,因此只需要遍历一半的元素即可。

public static void sort(Comparable[] a){
int N = a.length;
for(int k = N/2; k >= 1; k--){
sink(a, k, N);
}
while(N > 1){
exch(a, 1, N--);
sink(a, 1, N);
}
}
4.6 分析¶
一个堆的高度为 lgN,因此在堆中插入元素和删除最大元素的复杂度都为 lgN。
对于堆排序,由于要对 N 个节点进行下沉操作,因此复杂度为 NlgN。
堆排序时一种原地排序,没有利用额外的空间。
现代操作系统很少使用堆排序,因为它无法利用缓存,也就是数组元素很少和相邻的元素进行比较。
5. 应用¶
排序算法的比较¶

快速排序时最快的通用排序算法,它的内循环的指令很少,而且它还能利用缓存,因为它总是顺序地访问数据。它的运行时间增长数量级为 ~cNlgN,这里的 c 比其他线性对数级别的排序算法都要小。使用三向切分之后,实际应用中可能出现的某些分布的输入能够达到线性级别,而其它排序算法仍然需要线性对数时间。
基于切分的快速选择算法¶
快速排序的 partition() 方法,会将数组的 a[lo] 至 a[hi] 重新排序并返回一个整数 j 使得 a[lo..j-1] 小于等于 a[j],且 a[j+1..hi] 大于等于 a[j]。那么如果 j=k,a[j] 就是第 k 个数。
该算法是线性级别的,因为每次正好将数组二分,那么比较的总次数为 (N+N/2+N/4+..),直到找到第 k 个元素,这个和显然小于 2N。
public static Comparable select(Comparable[] a, int k) {
int lo = 0, hi = a.length - 1;
while (hi > lo) {
int j = partion(a, lo, hi);
if (j == k) return a[k];
else if (j > k) hi = j - 1;
else lo = j + 1;
}
return a[k];
}
插入排序¶
将一个元素插入到已排序的数组中,使得插入之后的数组也是有序的。插入排序从左到右插入每个元素,每次插入之后左部的子数组是有序的。

/*
插入过程采用两种实现,一种是反复交换相邻项,另一种是传统的移动插入
理论上采用第二种操作方式应该会快一些,但这种差别也可能被编译器优化掉变得微乎其微
*/
class Insertion
{
public:
vector<int>& sort(vector<int>& array)
{
int len = array.size();
for (int i = 1; i < len; i++)
{
for (int j = i; j>0 && array[j] < array[j-1];)
{
int t = array[j];
array[j] = array[j - 1];
array[j - 1] = t;
j--;
}
}
return array;
}
vector<int>& sort_1(vector<int>& array)
{
int len = array.size();
for (int i = 1; i < len; i++)
{
int temp = array[i];
int j;
for (j = i; j>0 && temp < array[j-1];)
{
array[j] = array[j - 1];
j--;
}
array[j] = temp;
}
return array;
}
};
插入排序的复杂度取决于数组的初始顺序,如果数组已经部分有序了,那么插入排序会很快。平均情况下插入排序需要 ~N2/4 比较以及 ~N2/4 次交换,最坏的情况下需要 ~N2/2 比较以及 ~N2/2 次交换,最坏的情况是数组是逆序的;而最好的情况下需要 N-1 次比较和 0 次交换,最好的情况就是数组已经有序了。
插入排序对于部分有序数组和小规模数组特别高效。
选择排序和插入排序的比较
对于随机排序的无重复主键的数组,插入排序和选择排序的运行时间是平方级别的,两者之比是一个较小的常数。
选择排序¶
找到数组中的最小元素,然后将它与数组的第一个元素交换位置。然后再从剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。不断进行这样的操作,直到将整个数组排序。

class Selection
{
public:
vector<int>& sort(vector<int>& array)
{
int len = array.size();
for (int i = 0; i < len; i++)
{
int minindex = i;
//遍历寻找最小元素
for (int j = i + 1; j < len; j++)
{
if (array[j] < array[minindex])
{
minindex = j;
}
}
//交换 第 i 个元素和其后部分的最小元素
int t = array[i];
array[i] = array[minindex];
array[minindex] = t;
}
return array;
}
};
牛客华为机试¶
题目1-3¶
1 字符串最后一个单词的长度¶
题目描述
计算字符串最后一个单词的长度,单词以空格隔开。
输入描述:
一行字符串,非空,长度小于5000。
输出描述:
整数N,最后一个单词的长度。
示例1
输入
hello world
输出
5
python AC
while True:
try:
str = input()
print(len(str.split()[-1]))
except:
break
C++ AC
#include <iostream>
#include <string>
using namespace std;
int main(){
string s;
while (cin >> s);
cout << s.size();
return 0;
}
2 计算字符个数¶
题目描述
写出一个程序,接受一个有字母和数字以及空格组成的字符串,和一个字符,然后输出输入字符串中含有该字符的个数。不区分大小写。
输入描述:
输入一个有字母和数字以及空格组成的字符串,和一个字符。
输出描述:
输出输入字符串中含有该字符的个数。
示例1
输入
ABCDEF A
输出
1
示例应该给错了,是
ABCDEF
A
python
inputstr = input()
char = input()
cnt = inputstr.count(char.lower()) + inputstr.count(char.upper())
print(cnt)
lower 和 upper 是字符串方法,也可以将字符串和字符都转换为小写再 count
C++
#include <iostream>
#include <string>
using namespace std;
int main(){
string str,s;
getline(cin,str);
getline(cin,s);
char u,l;
u = toupper(s[0]);
l = tolower(u);
int cnt=0;
for (int i=0; i-str.size(); i++){
if (str[i]==u||str[i]==l)
cnt++;
}
cout << cnt;
return 0;
}
3 明明的随机数¶
题目描述
明明想在学校中请一些同学一起做一项问卷调查,为了实验的客观性,他先用计算机生成了N个1到1000之间的随机整数(N≤1000),对于其中重复的数字,只保留一个,把其余相同的数去掉,不同的数对应着不同的学生的学号。然后再把这些数从小到大排序,按照排好的顺序去找同学做调查。请你协助明明完成“去重”与“排序”的工作。
Input Param
n 输入随机数的个数
inputArray n个随机整数组成的数组
Return Value
OutputArray 输出处理后的随机整数
注:测试用例保证输入参数的正确性,答题者无需验证。测试用例不止一组。
输入描述:
输入多行,先输入随机整数的个数,再输入相应个数的整数
输出描述:
返回多行,处理后的结果
示例1
输入
11
10
20
40
32
67
40
20
89
300
400
15
输出
10
15
20
32
40
67
89
300
400
python
解题思路:
题目明说了 数字在 1 到 1000 之间,那么这题就很简单了,用空间换时间。维护一个记录数组,然后按顺序检测并输出。
import sys
while True:
try:
ls = [0 for i in range(1001)]
n = int(sys.stdin.readline())
for i in range(n):
num = int(sys.stdin.readline())
ls[num] = 1
for i in range(1000):
if ls[i+1]:
print(i+1)
except:
break
当然 如果是 用 set 和 sort 也一样可以解决
#include <iostream>
#include <vector>
using namespace std;
int main(){
int N;
while(cin>>N){
vector <int> vec(1000,0);
for (int i=0;i-N;i++){
int a=0;
cin >> a;
vec[a-1]=1;
}
for (int i=0;i-1000;i++){
if (vec[i])
cout << i+1 << endl;
}
}
return 0;
}
2_语言¶
CCpp¶
C++ How To¶
1反转字符串¶
1.利用algorithm 的 reverse 方法
//就地操作
void reverse(string& str){
reverse(str.begin(),str.end());
}
2.自己实现, 1/2 遍历
//就地操作
void reverse(string& str){
int len=str.size();
//这个 for 循环写的很有意思
for(int i=0,j=len-1;i<j;i++,j--){
char temp=str[i];
str[i]=str[j];
str[j]=temp;
}
}
3.利用递归(尽量少用)
string reverse(string& str) {
if (str.size()<2)
return str;
else return reverse(str.substr(1)) + str.substr(0,1);
}
利用 # ifdef 等编译指令切换调试模式¶
for 循环和 while 循环¶
可以相互转换,嵌套 for 循环其实也不需要真的嵌套,详见下面的例子
能用 for 还是尽量用 for 循环
#include <iostream>
#include <vector>
#include <Windows.h>
using namespace std;
void display(vector<int> vec);
#define __main main
class Solution
{
public:
bool Find(vector<vector<int> > array, int target)
{
bool res = false;
int row = array.size();
int col = array[0].size();
// 我们从右上角的元素找起来
// 如果查找的元素比当前位置元素小, 就向左走
// 如果查找的元素比当前位置元素大, 就向下走
for (int i = 0, j = col - 1;
(i >= 0 && i < row) && (j >= 0 && j < col);)
{
if (target == array[i][j])
{
res = true;
break;
}
else if (target < array[i][j]) // 小的元素在当前位置左侧
{
#ifdef __tmain
display(array);
#endif // __tmain
j--;
}
else
{
#ifdef __tmain
display(array);
#endif // __tmain
i++;
}
}
return res;
}
};
void display(vector<int> vec)
{
int len = vec.size();
for (int i = 0; i<len; i++)
{
cout << vec[i] << ' ';
}
cout << endl;
}
int __main()
{
int a1[] = { 1, 2, 8, 9, };
int a2[] = { 2, 4, 9, 12, };
int a3[] = { 4, 7, 10, 13, };
int a4[] = { 6, 8, 11, 15, };
vector<vector<int>> array;
array.push_back(vector<int>(a1, a1 + 4));
array.push_back(vector<int>(a2, a2 + 4));
array.push_back(vector<int>(a3, a3 + 4));
array.push_back(vector<int>(a4, a4 + 4));
Solution solu;
cout << solu.Find(array, 7) << endl;
system("pause");
return 0;
}
C++ 中几种类型相互转换¶
1.string 转 int 等¶
(1) 利用 stringstream¶
// string to long
long str2long(string& str)
{
stringstream ss;
long i;
ss << str;
ss >> i;
return i;
}
(2) 利用 C++ 函数 stoi() 等¶
//string 2 loog
long str2long(string& str)
{
long l = stoi(str);
return l;
}
//str2float
float str2float(string& str)
{
float f = stof(str);
return f;
}
2. int 等转换为 string¶
(1) 利用 stringstream¶
string long2str(long l)
{
string str;
stringstream ss;
ss << l;
str = ss.str();
//ss >> str;
return str;
}
(2) 利用 C++ 11 新特性 to_string()
函数¶
string long2str(long l)
{
string str = to_string(1);
return str;
}
3. string 转换为 C 字符串¶
(1) 利用 stringstream¶
void* string2chars(string& str, char* ch)
{
stringstream ss;
ss << str;
ss >> ch;
}
//char 数组长度不做考虑
(2) 利用 string 类的成员函数 c_str()¶
注意不能直接用 const char* 对 char* 类型进行初始化或赋值
void string2chars(string& str, char* ch)
{
//char* ch = str.c_str();
//无法将 用 const char* 对 char* 实例进行初始化
strcpy(ch, str.c_str());
}
4. C 字符串转换为 string¶
(1) C++ 已对 string 进行了重载 可以在定义时直接初始化,或者赋值¶
此时 C 字符串分为两种,一种是 "abc" 这种 const char*
, 另一种是 char 数组,即 char*
//对于 const char*
string chars2string(const char* ch)
{
string str(ch);
//string str = ch;
//string str;
//str = ch;
return str;
}
//对于 char*
string chars2string(char* ch)
{
string str(ch);
//string str = ch;
//string str;
//str = ch;
return str;
}
实际上区别就是 没有从 const char*
到 char*
的自动转换
(2) 也可使用 stringstream¶
5. C 字符串转换为 int 等¶
(1) 利用 stringstream¶
long chars2long(char* ch)
{
stringstream ss;
long l;
ss << ch;
ss >> l;
return l;
}
(2) 利用 C 函数 atoi 等¶
long chars2long(char* ch)
{
int i = atoi(ch);
return i;
}
(3) 利用 sscanf¶
long chars2long(char* ch)
{
int i;
sscanf("17","%D",&i); // 17
//sscanf("17","%X",&i); // 23
//sscanf("0X17","%X",&i); // 23
return i;
}
(4) 利用 C 函数 strtol¶
原型
long int strtol (const char* str, char** endptr, int base);
strtol() 函数用来将字符串转换为长整型数(long).
str 为要转换的字符串,endstr 为第一个不能转换的字符的指针,base 为字符串 str 所采用的进制。
运行原理:strtol() 会将参数 str 字符串根据参数 base 来转换成长整型数(long)。参数 base 范围从2 至36,或0。参数base 代表 str 采用的进制方式,如base 值为10 则采用10 进制,若base 值为16 则采用16 进制等。
strtol() 会扫描参数 str 字符串,跳过前面的空白字符(例如空格,tab缩进等,可以通过 isspace() 函数来检测),直到遇上数字或正负符号才开始做转换,再遇到非数字或字符串结束时('\0')结束转换,并将结果返回。
注意:
当 base 的值为 0 时,默认采用 10 进制转换,但如果遇到 '0x' / '0X' 前置字符则会使用 16 进制转换,遇到 '0' 前置字符则会使用 8 进制转换。 若endptr 不为NULL,则会将遇到的不符合条件而终止的字符指针由 endptr 传回;若 endptr 为 NULL,则表示该参数无效,或不使用该参数。
返回值:
返回转换后的长整型数;如果不能转换或者 str 为空字符串,那么返回 0(0L);如果转换得到的值超出 long int 所能表示的范围,函数将返回 LONG_MAX 或 LONG_MIN(在 limits.h 头文件中定义),并将 errno 的值设置为 ERANGE。
实际上:
ANSI C 规范定义了 stof()、atoi()、atol()、strtod()、strtol()、strtoul() 共6个可以将字符串转换为数字的函数。另外在 C99 / C++11 规范中又新增了5个函数,分别是 atoll()、strtof()、strtold()、strtoll()、strtoull()。
long chars2long(char* ch, int base)
{
char* end = nullptr;
long l = strtol(ch, &end, base);
return l;
}
6. int 等转换为 C 字符串¶
(1) 利用 stringstream¶
void int2chars(int i, char* ch)
{
stringstream ss;
ss << i;
ss >> ch;
}
(2) 利用 sprintf¶
void int2chars(int i, char* ch)
{
sprintf(ch,"%d", i);
}
注:¶
1.stringstream 总体来说是不够智能的,例如
stringstream ss;
int i;
ss << "hello world";
ss >> i;
这样的代码是可以无异常运行的,如果想做出改变可以选择使用 Boost 库中的转换方法,不过一般情况下 Boost 太过复杂并不是一个很好的选择。
虽然 stringstream 在某些情况下不安全,但至少在内存管理方面是相对安全的,不像 atoi atol 或者 printf sprintf 等函数一样,不对内存进行任何安全检测
2.将 const 字符串 转换为 字符串
使用 sprintf()
int sprintf ( char * str, const char * format, ... );
sprintf 在打印字符串时,会自动在后一内存块上补 '\0'
,但是并不会对这块内存是否溢出进行检测。
此时可以使用 sprintf 的安全版本,sprintf_s
int sprintf_s(char *buffer, size_t sizeOfBuffer, const char *format [,argument]...);
通过指定缓冲区长度来避免sprintf()存在的溢出风险
参考:
5 迭代器的理解¶
就拿 string 类举例来说,其
string str("abcdefg");
//删除第一个字符
str.erase(str.begin());
//删除第一个字符
str.erase(str.begin(),str.begin()+1);
//删除全部字符
str.erase(str.begin(),str.end());
//保留最后一个字符
str.erase(str.begin(),str.end()-1);
从以上的代码,我们可以对迭代器做这样的理解,迭代器不同于普通的下标,而是一种类似于“隔板”一样的东西,str.begin() 返回的迭代器位于第一个字符前面,而 str.end() 返回的迭代器位于最后一个字符的后面,所以叫做”超尾迭代器“,如果成员函数的参数是首尾两个迭代器,那么删除的部分就位于这两个"隔板“之间。
注: 另外,string 类中的erase可以用int pos 作为参数,而其他的 STL 容器的 erase 好像只能用迭代器作为参数
6 NULL 和 nullptr¶
首先看一下 NULL 的定义
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
也就是说 NULL 在 C++ 中被 define 为 0. 而在 C 中 被 define 为 (void *)0。 这在某些情况下是会出问题的
void f(int i){
cout << "take integer" << endl;
}
void f(int *p){
cout << "take pointer" << endl;
}
这两个重载函数,如果通过f(NULL)来调用 将调用第一个以 int 为参数的函数,而如果通过f(nullptr)来调用则会调用第二个函数 nullptr 是一个关键字 不存在上述问题
7 关于 cin 输入流¶
首先 关于 循环 while(cin) 的终止, 似乎只能通过 EOF 也就是键盘上的 Ctrl + Z 来实现,如果你想对一段输入按空格拆分的时候如果是自己用键盘输入还好,可以输入 Ctrl + Z ,但是如果是测试题,那就没有机会输入 EOF 了。
解决方法是
while(cin){
cin >> str;
if (cin.get()=='\n')
break;
}
cin.get() 和 cin.getline() 的区别是 cin.get 不会丢弃换行符而是留在输入流里,而 cin.getline 会读取并丢弃换行符。
8 const 和指针¶
const 只要不和指针混在一起,那么其表达的意思是很清楚的,但是只要和指针混在一块,通常对于 const 到底是修饰指针本身还是指针所指向的对象就要费一番周折了。
实际上,判断 const 修饰的对象最简单的方法就是看 const 位于 * 的左边还是右边。如果 const 位于星号的左侧,则 const 就是用来修饰指针所指向的变量,即指针指向为常量;如果 const 位于星号的右侧,const 就是修饰指针本身,即指针本身是常量。
int b = 500;
const int* a = &b; //[1]
int const *a = &b; //[2]
int* const a = &b; //[3]
const int* const a = &b; //[4]
[1]和[2]的情况相同,都是指针所指向的内容为常量(const放在变量声明符的位置无关).[3]为指针本身是常量,而指针所指向的内容不是常量.[4]为指针本身和指向的内容均为常量。
C++ 派生类中的函数覆盖和重载¶
首先,访问类中的成员函数有以下几种方式:
- 通过基类和派生类的对象名直接访问
- 通过指向基类对象的基类指针和指向派生类对象的派生类指针访问
- 通过指向派生类对象的基类指针访问(不存在指向基类的派生类指针)
下面分别总结一下
1. 通过对象名访问¶
此时和 virtual 关键字没有关系
基类无需多言,派生类 主要看同名函数在派生类中有没有定义
1. 有定义¶
那么和基类中的同名函数就没有关系了,则只使用派生类中的函数(即使需要进行类型转换,如无法转换则编译报错,也不会使用基类中的函数),派生类内部也可以进行重载,甚至可以手动实现一个和基类一模一样的同名函数
2. 没有定义¶
使用基类中的函数
2. 通过正确类型的指针访问¶
此时和 virtual 关键字也没有关系此种情形和通过对象名访问无异
3. 通过指向派生类对象的基类指针访问¶
只有在此种情况下 virtual 关键字才起作用
1. 基类函数有 virtual 关键字¶
派生类中的同名同参数函数可以对基类函数进行覆盖,对于这些函数则使用派生类中的定义(如果有的话),即使派生类中可能存在其他更符合参数类型的重载,也只使用派生类中和基类参数类型一致的那个覆盖函数
2. 基类函数没有 virtual 关键字¶
无论派生类中的情况如何都没有关系,因为指向派生类的基类指针只能访问基类的成员函数以及其中带有 virtual 关键字的成员函数在派生类中的实现(覆盖)
注:¶
- virtual 关键字只在通过指向派生类对象的基类指针调用函数时起作用,这是 virtual 关键字唯一的用武之地
- virtual 关键字对类本身不会起什么作用,主要是决定了其子类中的同名同参数函数能否对基类函数进行覆盖
- 高质量C++/C 编程指南 中指出的类继承中所谓的隐藏规则大致是正确的,只是没有正确理解 virtual 关键字的作用情况,并区分对派生类的调用方式
验证代码:
#include <iostream>
using namespace std;
class Base
{
public:
virtual void f(float x) { cout << "Base::f(float) " << x << endl; }
virtual void g(float x) { cout << "Base::g(float) " << x << endl; }
void h(float x) { cout << "Base::h(float) " << x << endl; }
void j(float x) { cout << "Base::j(float) " << x << endl; }
};
class Derived : public Base
{
public:
virtual void f(float x) { cout << "Derived::f(float) " << x << endl; }
virtual void g(float x) { cout << "Derived::g(float) " << x << endl; }
virtual void g(int x) { cout << "Derived::g(int) " << x << endl; }
void h(float x) { cout << "Derived::h(float) " << x << endl; }
void j(int x) { cout << "Derived::j(int) " << x << endl; }
void k(int x) { cout << "Derived::k(int) " << x << endl; }
};
void main(void)
{
Base b;
Derived d;
Base* pb = &b;
Derived* pd = &d;
Base *ppb = &d;
b.f(3.14f);
d.f(3.14f);
pb->f(3.14f);
pd->f(3.14f);
ppb->f(3.14f);
cout << endl;
b.g(3.14f);
d.g(3.14f);
pb->g(3.14f);
pd->g(3.14f);
ppb->g(3);
cout << endl;
b.h(3.14f);
d.h(3.14f);
pb->h(3.14f);
pd->h(3.14f);
ppb->h(3.14f);
cout << endl;
b.j(3.14f);
d.j(3.14f);
pb->j(3.14f);
pd->j(3.14f);
ppb->j(3.14f);
cout << endl;
cin.get();
}
cin cin.get cin.getline getline 的区别¶
先看原型
cin.get() istream 流¶
//single character (1)
int get();
istream& get (char& c);
//c-string (2)
istream& get (char* s, streamsize n);
istream& get (char* s, streamsize n, char delim);
//stream buffer (3)
istream& get (streambuf& sb);
istream& get (streambuf& sb, char delim);
第一个函数返回读取到的 char 或者 EOF,其他函数返回 *this 也就是一个 istream&
cin.getline() istream 流¶
istream& getline (char* s, streamsize n );
istream& getline (char* s, streamsize n, char delim );
函数返回 *this 即 istream&
getline() string 流¶
//(1)
istream& getline (istream& is, string& str, char delim);
//(2)
istream& getline (istream& is, string& str);
函数返回 *this 即 istream&
cin¶
根据后面变量的类型读取数据。
输入结束条件:遇到Enter、Space、Tab键.
对结束符的处理 :丢弃缓冲区中使得输入结束的结束符(Enter、Space、Tab)。
如果不想跳过空格 可以通过 noskipws 流控制符来控制
cin >> noskipws >> str;
get 和 getline 的区别。¶
get 不会读取输入流中的 delim 字符,即 delim 字符还留在输入流中,需要手动读取并丢弃。而 getline 会读取并丢弃 delim 即输入流中不再有 delim 字符,且读取字符串中也没有 delim 字符。
cin.get
和 cin.getline
都只能读取到 char*
中,而且为了安全必须指定 streamsize。
string 流中的 getline 就可以将 istream 流中的输入读取到 string 中。
因为 cin.get() 返回一个 istream 引用,所以手动丢弃 delim 时可以这么写
cin.get(ch, 10).get();
除此以外,C 中还有一些输入函数,比如 getchar scanf 等就不讨论了。
参考:
http://www.cplusplus.com/reference/iostream/istream/get/
http://www.cplusplus.com/reference/iostream/istream/getline/
http://www.cplusplus.com/reference/string/getline/
C 中的内存对齐¶
从一个例子开始,下面这段代码,一样的结构体占用的空间大小却不一样。究其原因就是内存对齐。
内存对齐主要是因为
- 有些机器无法访问任意位置的内存
- 内存分块存取有利于提升效率
#include <iostream>
using namespace std;
struct st1
{
char a;
int b;
short c;
};
struct st2
{
short c;
char a;
int b;
};
int main()
{
cout << "sizeof(st1) is " << sizeof(st1) << endl;
cout << "sizeof(st2) is " << sizeof(st2) << endl;
system("pause");
return 0;
}
程序输出 分别为 12 8
内存对齐主要有两条原则
- 前面的地址(总和)必须是后面地址的整数倍
- 整个 struct 的地址必须是最大字节的整数倍
那么对于 st1 char(1) int(4),所以 char 后需添加 3 Byte,再然后 short(2) ,前面 int(4) 是其整数倍。
整个 st1 10 Byte,不是最大长度 int(4) 整数倍,故添加 3 Byte
对于 st2 第一个 short(2),第二个 char(1) 符合公式1,再然后的 int(4),那么前面必须额外添加 1 Byte 使前面有 4 Byte。
整个 st2 8 Byte 是最大长度 int(4) 的整数倍
一些好的习惯¶
1.对于一些简单的运算,可以直接 return 或者 放在 if 判断中
//No
int a=c+d;
return a;
//Yes
return c+d;
2.判断是否为0,可以不用 == 或者!=
//No
if (a==0)
if(a!=0)
//Yes
if(!a)
if(a)
3.最好在定义的时候初始化,而不要定义了之后再赋值
//No
int a;
a=0;
int* b=new int;
*b=1;
//Yes
int a=0;
int a(0);
int* b = new int(1);
int* c = new int[3]{1,2,3};
4.对于普通的 for 循环(即不存在跳跃的情况),将判断条件由<之类的改为 ==0 的形式
//Yes
for (int i=0;i-N;i++){
}
//No
for (int i=0;i<N;i++){
}
5.使用基于范围的 for 循环
//Yes
vector <int> s{1,2,3,4,5};
for (int i:s){
cout << i << endl;
}
string s("fuckyou");
for (char i : s) {
cout << i << endl;
}
//No
vector <int> s{1,2,3,4,5};
for (int i=0;i-s.size();i++){
cout << s[i] << endl;
}
string s("fuckyou");
for (int i=0;i-s.size();i++) {
cout << s[i] << endl;
}
6.使用 ?: 运算符
//Yes
return a<b ? a:b;
//No
if (a<b)
return a;
else
return b;
6.定义函数时尽可能使用按引用传递参数,而不是按值传递参数
//Yes
int sum (int& a, int& b){
return a+b;
}
//No
int sum (int& a, int& b){
return a+b;
}
//Update
//返回引用的函数,return 的必须为左值
int& sum(int& a, int& b) {
int c = a + b;
return c;
}
7.在不是特别强调性能的情况下,使用C++ 中的新特性 比如说,C++ 中的新容器,vector queue stack string 等等。
8.使用 nullptr 而不是 NULL
//Yes
int* s = nullptr;
//No
int* s - NULL;
Handy Function¶
1.string 转 int
int strtoi(string& str){
int n=atoi(&str);
return n;
}
注意 atoi 的 原型
int atoi (const char * str);
如果只想转换 string 中单个数字,必须
int strtoi2(string& str){
char ch=str[0];
int n=atoi(&ch);
return n;
}
atoi(&str[0]) 会转换整个字符串
- int 转换为 string
string itostr(int& n){
stringstream ss;
string str;
ss << n;
ss >> str;
return str;
}
3.整数按位相加,即每一位上的数字加起来
// n 为进制
int getsum(int& m,int& n){
int sum=0;
while(m){
sum+= m % n;
m/=n;
}
return sum;
}
上面那样写当然没什么大问题,但是 getsum 函数没法这样调用
getsum(139, 10)
因为数字不是引用,必须这样
int a = 139;
int b = 10;
getsum(a, b);
我想这不是自己希望看到的,实际上,对于 int 这样的内置类型,编译器早就做了很多优化,使用引用传递基本上是多此一举。
从两个内存泄漏的例子说起¶
例一¶
//Ver 1
#include <iostream>
#include <Windows.h>
using namespace std;
struct st
{
int a;
~st()
{
cout << "the destructor is called" << endl;
};
};
st* test()
{
st mystru;
mystru.a = 10;
return &mystru;
}
int main()
{
st* p = test();
//此时的 p 应该已经算野指针了
cout << p->a << endl; //10
//重新 new 一个 st,对其 a 赋值
st* pp = new st;
pp->a = 11;
//再输出 p 指向内存处的 a
cout << p->a << endl; //4
system("pause");
return 0;
}
在第一个例子中,从 结构体的析构函数的执行情况可以看出,mystur 确实被析构了,此时 p 是野指针,但释放并不彻底,程序只是不再拥有对那块内存的所有权,但内存上的内容却没有清空,就导致在 main 中依然可以 通过 p 访问那里的 int a 的数值,然而如果下面通过 new 在堆区创建一个 新的 st 实例,再次输出 p 处的 int a 数值就改变了,这证明了 p 是野指针。
也可以不在堆上新建变量,而在栈上。总之让内存发生改变即可
//Ver 2
#include <iostream>
#include <Windows.h>
using namespace std;
struct st
{
int a;
~st()
{
cout << "the destructor is called" << endl;
};
};
st* test()
{
st mystru;
mystru.a = 10;
return &mystru;
}
int main()
{
st* p = test();
//此时的 p 应该已经算野指针了
cout << p->a << endl; //10
st pp;
pp.a = 11;
//再输出 p 指向内存处的 a
cout << p->a << endl; //4
system("pause");
return 0;
}
例二¶
#include <cstdio>
#include <Windows.h>
void test()
{
char c[] = "ChenHuiHui";
}
int main()
{
printf("" - 13);//这个数字可能和平台有关
system("pause");
return 0;
}
这个例子,展示了 静态常量区的内存泄漏 如果使用 iostream 头文件,此问题不存在
关于二维 vector 的坑¶
我们知道,像 vector 或者 string 这些 STL,如果调用 erase 方法将其中的某个值删除的话,后序的元素会自动补上前面的空位
但是对于二维 vector 来说,这样的机制仿佛并不存在,或许是因为 复制和移动那么多数据的消耗太大?对于二维 vector 来说,如果你删除了第一行,那么整个二维矩阵的大小将保持不变,只是第一行的元素不在存在了而已。
关于结构体和类的构造函数¶
首先,如果在创建结构体或者类的时候不提供自定义的构造函数(constructor),编译器会提供一个默认的构造函数,即什么都不做;
但是一旦自己提供了自定义的构造函数的话,那么久必须同时提供一个显式的默认构造函数,否则在定义和初始化的时候将必须使用自定义的构造函数而没有默认构造函数可用。
比如
struct stock {
int val;
int num;
};
stock* s = new stock;
stock* s1 = new stock();
stock* s2 = new stock[3];
都是可以的,这三条语句都将调用编译器提供的默认构造函数 但是假如提供了自定义的构造函数
struct stock {
int val;
int num;
stock(int a, int b) {
val = a;
num = b;
}
};
stock* s = new stock;
stock* s1 = new stock();
stock* s2 = new stock[3];
stock* s3 = new stock(1, 2);
前三条语句将会出错,系统会提示 “stock 没有默认构造函数”,只有第四条调用了自定义构造函数的语句可以成功定义并初始化一个 stock 结构体。
但是我们只要给 stock 显式的提供一个默认构造函数 stock(); 就可以重新使用默认构造函数了
struct stock {
int val;
int num;
stock(int a, int b) {
val = a;
num = b;
}
stock(){};
};
stock* s = new stock;
stock* s1 = new stock();
stock* s2 = new stock[3];
stock* s3 = new stock(1, 2);
这样,四条初始化语句就都是合法的了
关于解除引用运算符的优先级¶
note that ++ 的优先级比 解除引用运算符 * 要强,但是 ++ 运算符必须等上一操作结束了才会执行
int * ptr = new int[8]{ 1,3,5,7,9,11,13,15 };
int a = *ptr++; //a=1,ptr->3
cout << a << endl << *ptr++ << endl; //ptr->5
int b = *(ptr++); //b=5,ptr->7
cout << b << endl << *(ptr++) << endl; //ptr->9
int c = *ptr + 1; //c=10,ptr->9
cout << c << endl << *ptr + 1 << endl; //ptr->9
int d = *(ptr + 1); //d=11,ptr->9
cout << d << endl << *(ptr + 1) << endl;
// output: 1 3 5 7 10 10 11 11
总结,i++ 的优先级很高,但是必须保证在上一操作结束后执行这一点上有无括号并不影响,若是使用 ++i 结果又不一样。
Python¶
DaraFrame 的赋值和切片¶
tricky
>>> df = pd.DataFrame(np.random.randn(4, 4),index = range(4), columns = list('ABCD'))
>>> df
A B C D
0 -0.461282 0.535102 1.979702 -0.782241
1 -1.823763 0.769289 -1.246549 -0.718638
2 -0.028946 0.629081 0.242340 0.885860
3 -1.087284 0.363438 -0.309762 -0.989745
采用 [] index 的访问方法可以进行两列的交换
>>> df[['A','B']] = df[['B','A']]
>>> df
A B C D
0 0.535102 -0.461282 1.979702 -0.782241
1 0.769289 -1.823763 -1.246549 -0.718638
2 0.629081 -0.028946 0.242340 0.885860
3 0.363438 -1.087284 -0.309762 -0.989745
然而使用 loc 和 iloc 进行相似的操作都是不行的
df.loc[:,['A','B']] = df.loc[:,['B','A']]
df
A B C D
0 0.535102 -0.461282 1.979702 -0.782241
1 0.769289 -1.823763 -1.246549 -0.718638
2 0.629081 -0.028946 0.242340 0.885860
3 0.363438 -1.087284 -0.309762 -0.989745
这和 pandas 里对于 columns 和数据的绑定机制有关。此时应该这样
df.loc[:,['B', 'A']] = df[['A', 'B']].values
df
A B C D
0 -0.461282 0.535102 1.979702 -0.782241
1 -1.823763 0.769289 -1.246549 -0.718638
2 -0.028946 0.629081 0.242340 0.885860
3 -1.087284 0.363438 -0.309762 -0.989745
对于 DataFrame 类型,作为左值和作为右值也不太一样,这和 numpy 中切片操作返回的 view 还是不一样的,numpy 中的 view 就是引用,无论作为 左值还是右值,都作为引用存在。前面已经演示了作为左值的情况,看看作为右值
作为右值的时候似乎返回一个数据副本
slc = df.loc[:,['A','B']]
slc
A B
0 -0.461282 0.535102
1 -1.823763 0.769289
2 -0.028946 0.629081
3 -1.087284 0.363438
slc.loc[:,['A']] = 1
slc
A B
0 1.0 0.535102
1 1.0 0.769289
2 1.0 0.629081
3 1.0 0.363438
df
A B C D
0 -0.461282 0.535102 1.979702 -0.782241
1 -1.823763 0.769289 -1.246549 -0.718638
2 -0.028946 0.629081 0.242340 0.885860
3 -1.087284 0.363438 -0.309762 -0.989745
此时不影响 df 本身,但是不能直接对 df 赋值,否则会这样
df = 1
df
1
对所有元素赋值应该采用全部切片
df = pd.DataFrame(np.random.randn(4, 4),index = range(4), columns = list('ABCD'))
df.iloc[:] = 1
df
A B C D
0 1.0 1.0 1.0 1.0
1 1.0 1.0 1.0 1.0
2 1.0 1.0 1.0 1.0
3 1.0 1.0 1.0 1.0
另外,对 DataFrame 的操作最好不要使用链式选择
# not advised
df['A', 'B']['A']
#RIGHT
df['A']
Python HowTos¶
1.统计字符串中每个字符的个数¶
- str 内置 count 方法
s = 'abcdabc'
print(s.count('a'))
>>> 2
- collections 模块中的 Counter
from collections import Counter
s = 'abcdabc'
cnt = Counter(s)
print(cnt['a'])
>>> 2
- 正则表达式匹配
import re
count = len(re.findall('a', 'abcdabc'))
print(count)
>>> 2
2.Python 写入文件时有中文出现乱码问题的解决¶
打开文件的时候指定 encoding 参数即可
with open("index.text", "w", encoding="utf-8") as f:
f.write("我今年十八岁")
另外 str 也有 encode decode 方法
3.Python 获取对象的大小¶
使用 sys.getsizeof()
不过因为 python 中的对象其实都是类,就连字符串也不例外,所所以很多时候这一方法就显得不那么尽如人意了
那么要获取字符串的大小,对于一般无中文的字符串,使用 len() 函数即可,返回的长度即字符串占用的内存(仅仅是字符串,不包括类代码),如果有中文,中文占用 3或4 个字节(utf-8),情况就变得复杂起来了。
4.Python 中实现栈与队列¶
collections 模块中 有 deque 可以轻松模拟 stack 和 queue,实际上 Python 中也有专门的队列和栈的模块。
queue 模块中的 Queue 就是一个 FIFO 的数据结构也即 queue,常用方法
- class queue.Queue(maxsize=0) 构造函数
- Queue.qsize() 返回 队列 size 的估计
- Queue.empty() 判断是否为空
- Queue.full() 判断是否为满
- Queue.put(item, block=True, timeout=None) 入队
- Queue.get(block=True, timeout=None) 移除队头元素并返回此元素
可能 stack 真的没有必要用什么模块去实现了,因为 list 就有 pop 和 append 方法,这和 stack 是完全一样的。
matplotlib 画图相关¶
参考:
对 matplotlib 中 figure, axes 和 subplots 的理解¶
区别
A figure in matplotlib means the whole window in the user interface. Within this figure there can be subplots. While subplot positions the plots in a regular grid, axes allows free placement within the figure. Both can be useful depending on your intention. We've already worked with figures and subplots without explicitly calling them. When we call plot, matplotlib calls gca() to get the current axes and gca in turn calls gcf() to get the current figure. If there is none it calls figure() to make one, strictly speaking, to make a subplot(111).
figure 是指整个用户界面窗口,figure 内可以有 subplots。subplots 在 figure 内的位置是比较固定或者说规整的,而 axes 可以在 figure 内随意指定位置。两种对象都很有用,取决于你具体的意图,当我们调用 plt 的时候,matplotlib 调用 gca() 函数以获取当前的 axes 然后 gca() 再调用 gcf() 来获取当前 figure。如果当前不存在 figure 那么 gcf() 会调用 figure() 创建一个,更具体的,创建一个 subplot(111)
figure
A figure is the windows in the GUI that has "Figure #" as title. Figures are numbered starting from 1 as opposed to the normal Python way starting from 0. This is clearly MATLAB-style. There are several parameters that determine what the figure looks like.
Subplots
With subplot you can arrange plots in a regular grid. You need to specify the number of rows and columns and the number of the plot. Note that the gridspec command is a more powerful alternative.
Axes
Axes are very similar to subplots but allow placement of plots at any location in the figure. So if we want to put a smaller plot inside a bigger one we do so with axes.
注
在 figure 中 add_axes 进行绘图默认是不显示 axis 的
两种 利用 matplotlib 画动态图的方法¶
- 1 利用 animation 模块
- 2 打开交互模式 plt.ion()
利用 animation 模块进行动态图绘制¶
matplotlib.animation.FuncAnimation
class matplotlib.animation.FuncAnimation(fig, func, frames=None, init_func=None, fargs=None, save_count=None, **kwargs)
其中 blit 控制是否优化 图形绘制,默认为 False,即重新绘制不变图像。设置为 True 时,将只重新绘制变化部分。
BUG:当 interval 设置过小时,比如 interval = 1,开启 blit = True 将导致坐标轴部分显示为黑屏。
看一个利用 animation 模拟下雨过程的脚本
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# No toolbar
matplotlib.rcParams['toolbar'] = 'None'
# New figure with white background
fig = plt.figure(figsize=(6,6), facecolor='white')
# New axis over the whole figureand a 1:1 aspect ratio
# ax = fig.add_axes([0,0,1,1], frameon=False, aspect=1)
ax = fig.add_axes([0.005,0.005,0.990,0.990], frameon=True, aspect=1)
# Number of ring
n = 50
size_min = 50
size_max = 50*50
# Ring position
P = np.random.uniform(0,1,(n,2))
# Ring colors
C = np.ones((n,4)) * (0,0,0,1)
# Alpha color channel goes from 0 (transparent) to 1 (opaque)
C[:,3] = np.linspace(0,1,n)
# Ring sizes
S = np.linspace(size_min, size_max, n)
# Scatter plot
scat = ax.scatter(P[:,0], P[:,1], s=S, lw = 0.5,
edgecolors = C, facecolors='None')
# Ensure limits are [0,1] and remove ticks
ax.set_xlim(0,1), ax.set_xticks([])
ax.set_ylim(0,1), ax.set_yticks([])
def update(frame):
global P, C, S
# Every ring is made more transparent
C[:,3] = np.maximum(0, C[:,3] - 1.0/n)
# Each ring is made larger
S += (size_max - size_min) / n
# Reset ring specific ring (relative to frame number)
i = frame % 50
P[i] = np.random.uniform(0,1,2)
S[i] = size_min
C[i,3] = 1
# Update scatter object
scat.set_edgecolors(C)
scat.set_sizes(S)
scat.set_offsets(P)
return scat,
animation = FuncAnimation(fig, update, interval=10)
# animation.save('../figures/rain.gif', writer='imagemagick', fps=30, dpi=72)
plt.show()
animation 的实现就是通过 FuncAnimation 反复调用回调函数 update
实际上 animation = FuncAnimation(fig, update, interval=10) 一句中,还有 frame 关键字参数,用以提供 frame 的具体生成形式,例如 frame = (i in range(100)) 这样的,因为 update 函数接受 frame 参数嘛。
打开交互模式¶
plt.ion()
此时 plt.show() 不再阻塞程序的运行,所以 figure 就可以一直更新了
import numpy as np
import matplotlib.pyplot as plt
plt.axis([0, 100, 0, 1])
plt.ion()
for i in range(100):
y = np.random.random()
plt.autoscale()
plt.scatter(i, y)
plt.pause(0.01)
再探 subplots¶
之前提到过,subplots 在 figure 内对位置的控制能力较弱,而 axes 对位置的控制能力是很强的,下面简单展示下分别利用 axes 和 subplots 画多个子图
import numpy as np
import matplotlib.pyplot as plt
X = np.linspace(-np.pi, np.pi, 256, endpoint=True)
C,S = np.cos(X), np.sin(X)
plt.figure(1, figsize = (15, 6))
for i in range(6):
plt.subplot(2,3,i+1)
plt.plot(X, C, label = 'cos')
plt.plot(X, S, label = 'sin')
plt.xlabel('angle')
plt.ylabel('sin or cos')
plt.title('triangle functions')
plt.legend()
plt.show()
以上是 一直用 plt 在进行画图,其实也就是不断的调用 gca 和 gcf
下面再利用 axes 对象重画一遍,这次只画一个子图,因为位置不好调。。。
import numpy as np
import matplotlib.pyplot as plt
X = np.linspace(-np.pi, np.pi, 256, endpoint=True)
C,S = np.cos(X), np.sin(X)
fig = plt.figure(1, figsize = (15, 6))
ax = fig.add_axes([0.005,0.005,0.990,0.990], frameon=True, aspect=1)
ploter1 = ax.plot(X, C, label = 'cos')
ploter2 = ax.plot(X, S, label = 'sin')
legend = ax.legend(loc='upper left', shadow=True, fontsize='x-large')
xlabel = ax.set_xlabel('angle')
ylabel = ax.set_ylabel('sin or cos')
title = ax.set_title('triangle functions')
plt.show()
以上两个例子大致说明了 利用 plt 直接画图 和利用 axes 或 subplot 对象进行绘图的区别。实际上第一个实现就是用的 subplot 进行绘图的,只不过 通过 plt 对 gca 的调用隐去了对 subplt 的调用
换一种写法
fig = plt.figure()
ax = fig.add_subplot(2,3,1)
# 或者另一种惯用写法
fig, ax = subplots() # 在不指定参数的情况下默认是 111,如果指定行列,返回的 ax 是一个 list
一些别的东西¶
设置坐标轴的 最大最小值
plt.xlim()
清空坐标轴的刻度显示
plt.set_xticks([])
指定坐标轴刻度显示的位置
plt.xticks(loc,label)
一个自动设置 plt 比例的函数
plt.autoscale()
简单的多项式拟合
fp = np.polyfit(x,y,3)
f = np.poly1d(fp)
画直线
plt.vlines(x,y_min,y_max)
Python 标准库 collections¶
参考:
Python 的内置数据数据类型包括 str, int, list, tuple, set, dict 等,有时这些数据类型满足不了我们的需求。不过标准库的 collections 模块在这些内置数据类型的基础上,提供了几个额外的数据类型:
- namedtuple 命名元组,使用名字访问元素 New in version 2.6.
- deque 双端队列,可以快速的从头/尾两端添加或删除元素 New in version 2.4.
- Counter 计数器,用于对某项数据进行计数 New in version 2.7.
- OrderedDict 有序字典,保持插入顺序 New in version 2.7.
- defaultdict 带有默认值的字典 New in version 2.5.
- ChainMap 合并多个 map(dict),但保持原数据结构 New in version 3.3
- UserDict 将字典包装起来使得创建字典的子类更容易
- UserList 列表对象的包装器
- UserString 字符串对象的包装器
namedtuple¶
可以使用名称来访问元素的数据对象,返回一个 带 name fields 的子类
collections.namedtuple(typename, field_names, *, verbose=False, rename=False, module=None)
field_names 用于指定数据对象的元素,可以是一个 str,例如 'x y' 或者 'x, y',也可以是一个包含字符串的序列类型,像 ['x', 'y']
常用方法
- _asdict() 将 namedtuple 转换为一个 OrderedDict
- _fields() 返回 name fields 中的 key
- _replace() 类似于 str 的 replace 方法
- _make() 通过一个序列或可迭代对象创建
>>> Point = namedtuple('Point', ['x', 'y', 'z'])
>>> p = Point(10, 11, 12)
>>> p
Point(x=10, y=11, z=12)
>>> p.x
10
>>> p.x + p.y + p.z
33
>>> p2 = Point(11, y=22, z=33)
>>> p2
Point(x=11, y=22, z=33)
>>> d = p2._asdict() # 转换为字典
>>> d
OrderedDict([('x', 11), ('y', 22), ('z', 33)])
>>> p2._replace(y=100) # 替换元素值
Point(x=11, y=100, z=33)
>>> p._fields # 查看对象字段
('x', 'y', 'z')
>>> Point._make(range(3)) # 通过一个序列或者可迭代对象创建一个对象
Point(x=0, y=1, z=2
deque¶
deque 是 double-ended queue 的缩写,即双端队列。List 存储数据的优势在于按索引查找元素会很快,但是插入和删除元素就很慢了,因为 list 是基于数组实现的。deque 是为了高效实现插入和删除操作的双向列表,list存储数据的优势在于按索引查找元素会很快,但是插入和删除元素就很慢了,因为list是基于数组实现的。deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈,而且线程安全。
collections.deque([iterable[, maxlen]])
deque 具有 list 的所有方法,此外还有 appendleft/popleft ,插入元素的复杂度 O(1), 而 list 是 O(n)
maxlen 用于指定 deque 的最大长度,当 deque 的长度达到最大时,先加入的元素会按加入顺序被移出 deque ,如果不指定 maxlen 则为无限长。
常用方法
- append
- extend
- appendleft
- appendright
- popleft
- popright
- rotate deque 内元素按顺序移动一位,最后一位移动到第一位
>>> dq = deque()
>>> dq
deque([])
>>> dq.extend([1, 2, 3])
>>> dq.extend([1, 2, 3])
>>> dq
deque([1, 2, 3, 1, 2, 3])
>>> dq = deque(maxlen=2)
>>> dq
deque([], maxlen=2)
>>> dq.append(1)
>>> dq.append(1)
>>> dq
deque([1, 1], maxlen=2)
>>> dq.append(3)
>>> dq
deque([1, 3], maxlen=2)
>>> dq.append(4)
>>> dq
deque([3, 4], maxlen=2)
>>> dq.appendleft(5)
>>> dq
deque([5, 3], maxlen=2
跑马灯程序
import sys
import time
from collections import deque
fancy_loading = deque('>--------------------')
while True:
print('\r%s' % ''.join(fancy_loading),)
fancy_loading.rotate(1)
sys.stdout.flush()
time.sleep(0.08)
Counter¶
Counter 用来统计相关元素的出现次数,返回一个 Counter 对象,类似于字典
collections.Counter([iterable-or-mapping])
常用方法
- update 追加元素
- most_common(n) 获取出现次数最多的 n 个元素
>>> c = Counter('abracadabra')
>>> c
Counter({'a': 5, 'r': 2, 'b': 2, 'd': 1, 'c': 1})
>>> c.update('zzzbbe') # 追加元素
>>> c
Counter({'a': 5, 'b': 4, 'z': 3, 'r': 2, 'e': 1, 'c': 1, 'd': 1})
>>> c.most_common(3) # 获取出现频率最高的前 3 个字符
[('a', 5), ('b', 4), ('z', 3)]
OrderedDict¶
OrderedDict 是 dict 的一个子类,支持所有 dict 的方法,保持 dict 的有序性
collections.OrderedDict([items])
常用方法
- popitem(last = True)
- move_to_end(key, last = True) 以上两个方法的 last 关键字参数都是控制从头还是尾弹出对象的
>>> d = OrderedDict({'banana': 3, 'apple': 4, 'pear': 1, 'orange': 2})
>>> d
OrderedDict([('banana', 3), ('apple', 4), ('pear', 1), ('orange', 2)])
>>> d['cherry'] = 8
>>> d
OrderedDict([('banana', 3), ('apple', 4), ('pear', 1), ('orange', 2), ('cherry', 8)])
>>> for k, v in d.items():
... print(k, v)
...
...
banana 3
apple 4
pear 1
orange 2
cherry 8
>>> d.keys()
odict_keys(['banana', 'apple', 'pear', 'orange', 'cherry'])
>>> d.pop('apple')
4
>>> d
OrderedDict([('banana', 3), ('pear', 1), ('orange', 2), ('cherry', 8)])
defaultdict¶
在普通的 dict 之上添加了 default_factory,使得 key 不存在时会自动生成相应类型的 value,default_factory 参数可以指定成 list, set, int 等各种合法类型
作用类似于 dict 中的 set_default 方法,代码更优雅一些
collections.defaultdict([default_factory[, …]])
>>> d = defaultdict(lambda: None)
>>> d
defaultdict(<function <lambda> at 0x1115ef620>, {})
>>> d['a']
>>> print(d['b'])
None
>>> d
defaultdict(<function <lambda> at 0x1115ef620>, {'a': None, 'b': None})
>>> d = defaultdict(int)
>>> d["hello"]
0
>>> d
defaultdict(<class 'int'>, {'hello': 0})
ChainMap¶
合成多个 dict 类似于 update 方法,只不过不是就地实现,且效率更高
collections.ChainMap(*maps)
>>> dict1 = { 'a' : 1, 'b' : 2 }
>>> dict2 = { 'b' : 3, 'c' : 4 }
>>> chain = ChainMap(dict1, dict2)
>>> chain
ChainMap({'a': 1, 'b': 2}, {'b': 3, 'c': 4})
>>> chain.maps
[{'a': 1, 'b': 2}, {'b': 3, 'c': 4}]
>>> chain.keys()
KeysView(ChainMap({'a': 1, 'b': 2}, {'b': 3, 'c': 4}))
>>> list(chain.keys())
['a', 'b', 'c']
>>> chain['b'] # 获取的是第一个字典中的值
2
>>> chain['a']
1
>>> chain['c']
4
>>> chain['d'] = 5
>>> chain['e'] = 6 # 被添加到了第一个字典中
>>> chain
ChainMap({'a': 1, 'd': 5, 'b': 2, 'e': 6}, {'b': 3, 'c': 4})
>>> chain['b'] = 8 # 修改了第一个字典中的值
>>> chain
ChainMap({'a': 1, 'd': 5, 'b': 8, 'e': 6}, {'b': 3, 'c': 4})
>>> m = chain.new_child() # 复制一个 ChainMap 对象
>>> m['g'] = 10
>>> m
ChainMap({'g': 10}, {'a': 1, 'd': 5, 'b': 8, 'e': 6}, {'b': 3, 'c': 4})
>>> chain
ChainMap({'a': 1, 'd': 5, 'b': 8, 'e': 6}, {'b': 3, 'c': 4})
>>> dict3 = { 'h' : 5 }
>>> new_chain = chain.new_child(dict3) # 添加新字典
>>> new_chain
ChainMap({'h': 5}, {'a': 1, 'd': 5, 'b': 8, 'e': 6}, {'b': 3, 'c': 4})
有时用 ChainMap 替代 update 是个不错的选择,但是 ChainMap 的绝大多数方法都只是在第一个字典上进行操作
UserDict UserList UserString¶
这三个类是分别对 dict、list、str 三种数据类型的包装,其主要是为方便用户实现自己的数据类型。在 Python2 之前,这三个类分别位于 UserDict、UserList、UserString 三个模块中,需要用类似于 from UserDict import UserDict 的方式导入。在 Python3 之后则被挪到了 collections 模块中。这三个类都是基类,如果用户要扩展这三种类型,只需继承这三个类即可。
Python 标准库 constraint¶
参考: - [python-constraint 1.3.1](https://pypi.python.org/pypi/python-constraint)constraint 库是 Python 中一个用来解决 CSPs (Constraint Solving Problems) 约束补偿问题的第三方库。
constraint 库提供了一个在有限空间内解决 CSP 问题的方案,
基本操作
>>> from constraint import *
>>> problem = Problem()
>>> problem.addVariable("a", [1,2,3])
>>> problem.addVariable("b", [4,5,6])
>>> problem.getSolutions()
[{'a': 3, 'b': 6}, {'a': 3, 'b': 5}, {'a': 3, 'b': 4},
{'a': 2, 'b': 6}, {'a': 2, 'b': 5}, {'a': 2, 'b': 4},
{'a': 1, 'b': 6}, {'a': 1, 'b': 5}, {'a': 1, 'b': 4}]
>>> problem.addConstraint(lambda a, b: a*2 == b,
("a", "b"))
>>> problem.getSolutions()
[{'a': 3, 'b': 6}, {'a': 2, 'b': 4}]
>>> problem = Problem()
>>> problem.addVariables(["a", "b"], [1, 2, 3])
>>> problem.addConstraint(AllDifferentConstraint())
>>> problem.getSolutions()
[{'a': 3, 'b': 2}, {'a': 3, 'b': 1}, {'a': 2, 'b': 3},
{'a': 2, 'b': 1}, {'a': 1, 'b': 2}, {'a': 1, 'b': 3}]
例子:Rooks Problem
# classical Eight Rooks problem
>>> problem = Problem()
>>> numpieces = 8
>>> cols = range(numpieces)
>>> rows = range(numpieces)
>>> problem.addVariables(cols, rows)
>>> for col1 in cols:
... for col2 in cols:
... if col1 < col2:
... problem.addConstraint(lambda row1, row2: row1 != row2,
... (col1, col2))
>>> solutions = problem.getSolutions()
>>> solutions
>>> solutions
[{0: 7, 1: 6, 2: 5, 3: 4, 4: 3, 5: 2, 6: 1, 7: 0},
{0: 7, 1: 6, 2: 5, 3: 4, 4: 3, 5: 2, 6: 0, 7: 1},
{0: 7, 1: 6, 2: 5, 3: 4, 4: 3, 5: 1, 6: 2, 7: 0},
{0: 7, 1: 6, 2: 5, 3: 4, 4: 3, 5: 1, 6: 0, 7: 2},
...
{0: 7, 1: 5, 2: 3, 3: 6, 4: 2, 5: 1, 6: 4, 7: 0},
{0: 7, 1: 5, 2: 3, 3: 6, 4: 1, 5: 2, 6: 0, 7: 4},
{0: 7, 1: 5, 2: 3, 3: 6, 4: 1, 5: 2, 6: 4, 7: 0},
{0: 7, 1: 5, 2: 3, 3: 6, 4: 1, 5: 4, 6: 2, 7: 0},
{0: 7, 1: 5, 2: 3, 3: 6, 4: 1, 5: 4, 6: 0, 7: 2},
...]
例子:Magic squares
>>> problem = Problem()
>>> problem.addVariables(range(0, 16), range(1, 16 + 1))
>>> problem.addConstraint(AllDifferentConstraint(), range(0, 16))
>>> problem.addConstraint(ExactSumConstraint(34), [0, 5, 10, 15])
>>> problem.addConstraint(ExactSumConstraint(34), [3, 6, 9, 12])
>>> for row in range(4):
... problem.addConstraint(ExactSumConstraint(34),
[row * 4 + i for i in range(4)])
>>> for col in range(4):
... problem.addConstraint(ExactSumConstraint(34),
[col + 4 * i for i in range(4)])
>>> solutions = problem.getSolutions()
库支持的一些特性
The following solvers are available:
Backtracking solver
Recursive backtracking solver
Minimum conflicts solver
Predefined constraint types currently available:
FunctionConstraint
AllDifferentConstraint
AllEqualConstraint
ExactSumConstraint
MaxSumConstraint
MinSumConstraint
InSetConstraint
NotInSetConstraint
SomeInSetConstraint
SomeNotInSetConstraint
functools 模块¶
参考:
- functools — Higher-order functions and operations on callable objects
- Python functools 模块
- Python 模块简介 -- functools
functools 是 Python 函数式编程一个重要的内置库,但是内容实在不多。
主要的模块就几个
- cmp_to_key
- partial partialmethod
- reduce
- total_ordering
- update_wrapper
- wraps
cmp_to_key¶
在 list.sort 和 内建函数 sorted 中都有一个 key 参数,这个参数用来指定取元素的什么值进行比较,例如按字符串元素的长度进行比较
>>> x = ['hello','abc','iplaypython.com']
>>> x.sort(key=len)
>>> x
['abc', 'hello', 'iplaypython.com']
也就是说排序时会先对每个元素调用 key 所指定的函数,然后再排序。同时,sorted 和 list.sort 还提供了 cmp 参数来指定如何比较两个元素,但是在 Python 3 中该参数被去掉了。cmp_to_key 函数就是用来将老式的比较函数转化为 key 函数。用到 key 参数的函数还有 sorted(), min(), max(), heapq.nlargest(), itertools.groupby() 等
partial¶
本质上是一个装饰器,返回一个新的 partial 对象,当被调用时就和 func 一样,只不过通常参数会少于原 func 也就是提前给定原函数的一部分参数,再把它封装成另一个函数
原理大致如下
def partial(func, *args, **keywords):
def newfunc(*fargs, **fkeywords):
newkeywords = keywords.copy()
newkeywords.update(fkeywords)
return func(*args, *fargs, **newkeywords)
newfunc.func = func
newfunc.args = args
newfunc.keywords = keywords
return newfunc
例子
>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18
partialmethod¶
Return a new partialmethod descriptor which behaves like partial except that it is designed to be used as a method definition rather than being directly callable.
一般用于方法定义,而不是直接调用
>>> class Cell(object):
... def __init__(self):
... self._alive = False
... @property
... def alive(self):
... return self._alive
... def set_state(self, state):
... self._alive = bool(state)
... set_alive = partialmethod(set_state, True)
... set_dead = partialmethod(set_state, False)
...
>>> c = Cell()
>>> c.alive
False
>>> c.set_alive()
>>> c.alive
True
reduce¶
在 Python2 中等同于内建函数 reduce,但是在 Python3 中内建的 reduce 函数被移除,只能使用 functools.reduce
简单的说就是 reduce the sequence to a single value, function 必须是接受两个参数的,对一个序列一直执行,直直到最后变为一个值
其执行原理大概如下
def reduce(function, iterable, initializer=None):
it = iter(iterable)
if initializer is None:
value = next(it)
else:
value = initializer
for element in it:
value = function(value, element)
return value
例子,注意 reduce 还有一个默认参数 initializer
from functools import reduce
>>> function = lambda x, y: x+y
>>> iterable = [1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> reduce(function, iterable)
45
>>> reduce(function, iterable, 10)
55
total_ordering¶
这是一个类装饰器,给定一个类,这个类定义了一个或多个比较排序方法,这个类装饰器将会补充其余的比较方法,减少了自己定义所有比较方法时的工作量。
被修饰的类必须至少定义 lt(), le(), gt() 或 ge() 中的一个,同时,被修饰的类还应该提供 eq() 方法。
@total_ordering
class Student:
def __eq__(self, other):
return ((self.lastname.lower(), self.firstname.lower()) ==
(other.lastname.lower(), other.firstname.lower()))
def __lt__(self, other):
return ((self.lastname.lower(), self.firstname.lower()) <
(other.lastname.lower(), other.firstname.lower()))
update_wrapper¶
用 partial 包装的函数是没有 name 和 doc,这使得依赖于这些属性的代码可能无法正常工作。update_wrapper 可以拷贝被包装函数的 name、module、doc 和 dict 属性到新的封装函数中去,其实现也非常简单
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')
WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper,
wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
for attr in assigned:
setattr(wrapper, attr, getattr(wrapped, attr))
for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
return wrapper
update_wrapper 主要用在装饰器函数中,以确保被装饰的保留原理的属性,调用 update_wrapper 需要至少两个参数,用起来不是很方便这可能是 wraps 封装出现的原因之一
def wrap(func):
def call_it(*args, **kwargs):
print "calling", func.__name__
return func(*args, **kwargs)
return call_it
@wrap
def hello():
print "hello"
from functools import update_wrapper
def wrap2(func):
def call_it(*args, **kwargs):
print "calling", func.__name__
return func(*args, **kwargs)
return update_wrapper(call_it, func)
@wrap2
def hello2():
print "hello2"
>>> print hello.__name__
call_it
>>> print hello2.__name__
hello2
wraps¶
wraps 函数是为了在装饰器中方便的拷贝被装饰函数的签名,而对 update_wrapper 做的一个包装,其实现如下:
def wraps(wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
示例
from functools import wraps
def wrap3(func):
@wraps(func)
def call_it(*args, **kwargs):
print "calling", func.__name__
return func(*args, **kwargs)
return call_it
@wrap3
def hello3(func):
print "hello3"
print hello3.__name__ # hello3
itertools 模块¶
参考:
更多方便灵活的迭代器工具
无限迭代器¶
itertools.repeat¶
repeat(object [,times])
创建一个迭代器,重复生成 object,times(如果已提供)指定重复计数,如果未提供 times,将无止尽返回该对象
函数式工具¶
itertools.ifilter、itertools.reduce、itertools.imap、itertools.izip
与内建函数 filter(), reduce(), map(), zip() 有同样的功能,只是返回一个迭代器而不是一个序列。在 Python3 中被移除,因为默认的内建函数就是返回一个迭代器。
accumulate¶
itertools.accumulate(iterable[, func])
生成一个累积和序列
accumulate([1,2,3,4,5]) --> 1 3 6 10 15
filterfalse¶
filterfalse(function or None, sequence)
生成 sequence 中 function(item) 为 False 的项
>>> for elem in itertools.filterfalse(lambda x: x > 5, [2, 3, 5, 6, 7]):
>>> print(elem)
2
3
5
zip_longest¶
zip_longest(iter1 [,iter2 [...]], [fillvalue=None])
与 zip 类似,但不同的是它会把最长的 iter 迭代完才结束,其他 iter 如果有缺失值则用 fillvalue 填充
>>> for item in itertools.zip_longest('abcd', '12', fillvalue='-'):
>>> print(item)
('a', '1')
('b', '2')
('c', '-')
('d', '-')
starmap¶
starmap(function, sequence)
对序列 sequence 的每个元素作为 function 的参数列表执行,即 function(*item), 返回执行结果的迭代器
>>> for item in itertools.starmap(lambda x,y:(x, y, x*y), seq):
>>> print(item)
(0, 5, 0)
(1, 6, 6)
(2, 7, 14)
(3, 3, 9)
(3, 8, 24)
(4, 9, 36)
dropwhile¶
dropwhile(predicate, iterable)
丢弃在 predicate 第一次为 False 之前的所有项,其后的所有项都保留
>>> for item in itertools.dropwhile(lambda x: x<1, [ -1, 0, 1, 2, 3, 4, 1, -2 ]):
>>> print(item)
1
2
3
4
1
-2
takewhile¶
takewhile(predicate, iterable)
保留 predicate 为 True 的项,只要 predicate 一为 False 迭代就立即停止
>>> for item in itertools.takewhile(lambda x: x < 2, [ -1, 0, 1, 2, 3, 4, 1, -2 ]):
>>> print(item)
-1
0
1
组合工具¶
itertools.chain¶
chain(*iterables)
把一组迭代对象串联起来,形成一个更大的迭代器
for c in itertools.chain('ABC', 'XYZ'):
print(c)
A
B
C
X
Y
Z
itertools.product¶
product(*iterables, repeat=1)
创建一个迭代器,生成多个迭代器集合的笛卡尔积,repeat 参数用于指定重复生成序列的次数
for elem in itertools.product((1, 2), ('a', 'b')):
print(elem)
(1, 'a')
(1, 'b')
(2, 'a')
(2, 'b')
itertools.permutations¶
permutations(iterable[, r])
返回 iterable 中任意取 r 个元素做排列的元组的迭代器,如果不指定 r,那么序列的长度与 iterable 中的项目数量相同。
>>> for elem in itertools.permutations('abc', 2):
>>> print(elem)
('a', 'b')
('a', 'c')
('b', 'a')
('b', 'c')
('c', 'a')
('c', 'b')
itertools.combinations¶
combinations(iterable, r)
与 permutations 类似,但组合不分顺序,即如果 iterable 为 “abc”,r 为 2 时,ab 和 ba 则视为重复,此时只放回 ab.
>>> for elem in itertools.combinations('abc', 2):
>>> print(elem)
('a', 'b')
('a', 'c')
('b', 'c')
itertools.combinations_with_replacement¶
combinations_with_replacement(iterable, r)
与 combinations 类似,但允许重复值,即如果 iterable 为 “abc”,r 为 2 时,会多出 aa, bb, cc.
>>> for elem in itertools.combinations_with_replacement('abc', 2):
>>> print(elem)
('a', 'a')
('a', 'b')
('a', 'c')
('b', 'b')
('b', 'c')
('c', 'c')
其他工具¶
itertools.compress¶
'''Python compress(data, selectors)
bool 选取,只有当 selectors 对应位置的元素为 true 时,才保留 data 中相应位置的元素,否则移除
'''Python
>>> list(itertools.compress('abcdef', [1, 1, 0, 1, 0, 1]))
['a', 'b', 'd', 'f']
itertools.groupby¶
groupby(iterable[, keyfunc])
对 iterable 中的元素进行分组。keyfunc 是分组函数,用于对 iterable 的连续项进行分组,如果不指定,则默认对 iterable 中的连续相同项进行分组,返回一个 (key, sub-iterator) 的迭代器。
>>> data = ['a', 'bb', 'cc', 'ddd', 'eee', 'f']
>>> for key, value_iter in itertools.groupby(data, len):
print(key, list(value_iter))
1 ['a']
2 ['bb', 'cc']
3 ['ddd', 'eee']
1 ['f']
itertools.islice¶
islice(iterable, [start,] stop [, step])
对迭代器进行切片选择,start 是开始索引,stop 是结束索引,step 是步长,start 和 step 可选。
list(itertools.islice([10, 6, 2, 8, 1, 3, 9], 5))
[10, 6, 2, 8, 1]
itertools.tee¶
tee(iterable, n=2)
从 iterable 创建 n 个独立的迭代器,以元组的形式返回。默认为 2 个:
>>> for item in itertools.tee("abcedf"):
>>> for i in item:
>>> print(i)
a
b
c
e
d
f
a
b
c
e
d
f
Numpy Quick Start¶
参考:
https://docs.scipy.org/doc/numpy/user/quickstart.html
https://docs.scipy.org/doc/numpy-dev/reference/arrays.ndarray.html
https://github.com/familyld/learnpython/blob/master/Numpy_Learning.md
Numpy 中的数组类叫做 ndarray,顾名思义就是是 n 维数组。其中一些重要的属性
ndarray.ndim 维度
ndarray.shape 形状 比如 一个 3x3 的 2 维数组 返回 (3, 3)
ndarray.size ndarray 中的元素总数
ndarray.dtype 数据类型 比如 numpy.int32, numpy.int16, numpy.float64
ndarray.itemsize 每一个元素的 字节数 等价于 ndarray.dtype.itemsize.
ndarray.data 实际存储 ndarray 内容的内存 一般不使用
ndarray.T 返回转置
flat 返回一个数组的迭代器,对此迭代器赋值将导致整个数组元素被覆盖
real/imag 返回复数数组的实部/虚部数组
nbytes 数组占用的字节数
ndarray.base base array 如果是其他 array 的 view
ndarray.flags 关于 array 内存的一些信息
>>> import numpy as np
>>> a = np.arange(15).reshape(3, 5)
>>> a
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]])
>>> a.shape
(3, 5)
>>> a.ndim
2
>>> a.dtype.name
'int64'
>>> a.itemsize
8
>>> a.size
15
>>> type(a)
<type 'numpy.ndarray'>
>>> b = np.array([6, 7, 8])
>>> b
array([6, 7, 8])
>>> type(b)
<type 'numpy.ndarray'>
>>> b.flat
<numpy.flatiter at 0x20456dd3670>
>>> b.flat = 1
>>> b
array([1, 1, 1])
>>> b.flat = [1,2]
>>> b
array([1, 2, 1])
Basics¶
数组创建¶
创建数组有与多种不同的方法
从 python list 或 tuple 利用 array 创建,数据格式会自动推断
>>> import numpy as np
>>> a = np.array([2,3,4])
>>> a
array([2, 3, 4])
>>> a.dtype
dtype('int64')
>>> b = np.array([1.2, 3.5, 5.1])
>>> b.dtype
dtype('float64')
不要用多个数字来调用 array
>>> a = np.array(1,2,3,4) # WRONG
>>> a = np.array([1,2,3,4]) # RIGHT
array 自动将 二维或多维序列 转换为多维数组
>>> b = np.array([(1.5,2,3), (4,5,6)])
>>> b
array([[ 1.5, 2. , 3. ],
[ 4. , 5. , 6. ]])
另外可以手动指定数组元素的类型
>>> c = np.array( [ [1,2], [3,4] ], dtype=complex )
>>> c
array([[ 1.+0.j, 2.+0.j],
[ 3.+0.j, 4.+0.j]])
很多时候,数组的元素是未知的,但数组的大小已知,numpy 提供了很多创建带初始值数组的函数。这可以最小化改变数组大小的操作,因为那样很慢。
比如 ones,zeros 还有 empty,empty 创建的数组内所包含的数是随机的,取决于内存块当前的状态。默认的 dtype 是 float64
>>> np.zeros( (3,4) )
array([[ 0., 0., 0., 0.],
[ 0., 0., 0., 0.],
[ 0., 0., 0., 0.]])
>>> np.ones( (2,3,4), dtype=np.int16 ) # dtype can also be specified
array([[[ 1, 1, 1, 1],
[ 1, 1, 1, 1],
[ 1, 1, 1, 1]],
[[ 1, 1, 1, 1],
[ 1, 1, 1, 1],
[ 1, 1, 1, 1]]], dtype=int16)
>>> np.empty( (2,3) ) # uninitialized, output may vary
array([[ 3.73603959e-262, 6.02658058e-154, 6.55490914e-260],
[ 5.30498948e-313, 3.14673309e-307, 1.00000000e+000]])
Numpy 提供了一个类似于 range 的函数,arange 同样接受 start,stop,step 参数
>>> np.arange( 10, 30, 5 )
array([10, 15, 20, 25])
>>> np.arange( 0, 2, 0.3 ) # it accepts float arguments
array([ 0. , 0.3, 0.6, 0.9, 1.2, 1.5, 1.8])
值得注意的是,当 arange 接受 float 参数的时候,由于浮点数固有的精度限制,有时候结果往往并不让人满意,为了改进这一点,一个更好的办法是使用 linspace 函数
>>> from numpy import pi
>>> np.linspace( 0, 2, 9 ) # 9 numbers from 0 to 2
array([ 0. , 0.25, 0.5 , 0.75, 1. , 1.25, 1.5 , 1.75, 2. ])
>>> x = np.linspace( 0, 2*pi, 100 ) # useful to evaluate function at lots of points
>>> f = np.sin(x)
see also:
array, zeros, zeros_like, ones, ones_like, empty, empty_like, arange, linspace, numpy.random.rand, numpy.random.randn, fromfunction, fromfile
打印数组¶
打印数组的时候,显示方式基本上和 nested list 一样,但是会做一些调整以展示数组的维度
>>> a = np.arange(6) # 1d array
>>> print(a)
[0 1 2 3 4 5]
>>>
>>> b = np.arange(12).reshape(4,3) # 2d array
>>> print(b)
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]
>>>
>>> c = np.arange(24).reshape(2,3,4) # 3d array
>>> print(c)
[[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]]
如果 数组过大,自动跳过中间部分
如果想要强制打印整个数组,可以使用 set_printoptions.
>>> np.set_printoptions(threshold=np.nan)
基本操作¶
对数组进行的一些数学操作,会自动转换为 对每一个元素的操作,之后产生一个新的 array
但是在 numpy 中 * 是元素乘法,如果项进行矩阵乘法,需要使用 dot
>>> A = np.array( [[1,1],
... [0,1]] )
>>> B = np.array( [[2,0],
... [3,4]] )
>>> A*B # elementwise product
array([[2, 0],
[0, 4]])
>>> A.dot(B) # matrix product
array([[5, 4],
[3, 4]])
>>> np.dot(A, B) # another matrix product
array([[5, 4],
[3, 4]])
+= *= 之类的操作 会就地操作原数组,而不是产生一个新的数组,这很好理解。
很多 array 层面的操作,比如求和等被当作 ndarray 的方法实现。 例如 sum min max 等
这些操作默认是 array 层面的,但是你可以手动指定 axis 参数来对行或者列进行操作。
>>> b = np.arange(12).reshape(3,4)
>>> b
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>>
>>> b.sum(axis=0) # sum of each column
array([12, 15, 18, 21])
>>>
>>> b.min(axis=1) # min of each row
array([0, 4, 8])
>>>
>>> b.cumsum(axis=1) # cumulative sum along each row
array([[ 0, 1, 3, 6],
[ 4, 9, 15, 22],
[ 8, 17, 27, 38]])
通用函数¶
有很多函数比如 sin cos exp sqrt add,这被称作 ufunc,numpy 中这样的函数是 elementwise 的,返回一个数组。
See also:
all, any, apply_along_axis, argmax, argmin, argsort, average, bincount, ceil, clip, conj, corrcoef, cov, cross, cumprod, cumsum, diff, dot, floor, inner, inv, lexsort, max, maximum, mean, median, min, minimum, nonzero, outer, prod, re, round, sort, std, sum, trace, transpose, var, vdot, vectorize, where
index slice 和 iterating¶
一维数组的 index slice 还有 iterating 和 python list 并没有什么区别。
多维数组的每一维度都有一个 index,以元祖形式传递。当有些维度的 index 没有给出的时候,认为是全部 :.
>>> def f(x,y):
... return 10*x+y
...
>>> b = np.fromfunction(f,(5,4),dtype=int)
>>> b
array([[ 0, 1, 2, 3],
[10, 11, 12, 13],
[20, 21, 22, 23],
[30, 31, 32, 33],
[40, 41, 42, 43]])
>>> b[2,3]
23
>>> b[0:5, 1] # each row in the second column of b
array([ 1, 11, 21, 31, 41])
>>> b[ : ,1] # equivalent to the previous example
array([ 1, 11, 21, 31, 41])
>>> b[1:3, : ] # each column in the second and third row of b
array([[10, 11, 12, 13],
[20, 21, 22, 23]])
>>> b[-1] # the last row. Equivalent to b[-1,:]
array([40, 41, 42, 43])
b[i] 也可以写成 b[i,...], ... 可以智能的代表任意个 :
对多维数组的迭代,被认为是对第一维度的迭代。
如果想对整个数组的所有元素进行迭代,可以使用 flat 属性,flat 返回一个原数组的扁平化迭代器。
>>> for element in b.flat:
... print(element)
...
0
1
2
3
10
11
12
13
20
21
22
23
30
31
32
33
40
41
42
43
See also:
Indexing, Indexing (reference), newaxis, ndenumerate, indices
形状操作 shape manipulation¶
改变数组的形状,以下三个命令都返回一个新的 array 而不会改变原数组
>>> a.ravel() # returns the array, flattened
array([ 2., 8., 0., 6., 4., 5., 1., 1., 8., 9., 3., 6.])
>>> a.reshape(6,2) # returns the array with a modified shape
array([[ 2., 8.],
[ 0., 6.],
[ 4., 5.],
[ 1., 1.],
[ 8., 9.],
[ 3., 6.]])
>>> a.T # returns the array, transposed
array([[ 2., 4., 8.],
[ 8., 5., 9.],
[ 0., 1., 3.],
[ 6., 1., 6.]])
>>> a.T.shape
(4, 3)
>>> a.shape
(3, 4)
ravel 返回的是原 array 的一个 view,不会占用内存,但 view 的核心数据改变会影响原 array,flatten 返回一个副本
reshape 函数返回一个调整后的数组,而 resize 函数则原地操作数组本身 由于 resize 是 inplace 操作,所以有一个 reference check 机制,可以用 refcheck = False 取消。
如果其中一个维度参数给的是 -1,那么 numpy 会自动计算维数
See also:
ndarray.shape, reshape, resize, ravel
组合数组¶
可以使用 vstack 和 hstack 在不同方向组合不同的数组。
>>> a = np.floor(10*np.random.random((2,2)))
>>> a
array([[ 8., 8.],
[ 0., 0.]])
>>> b = np.floor(10*np.random.random((2,2)))
>>> b
array([[ 1., 8.],
[ 0., 4.]])
>>> np.vstack((a,b))
array([[ 8., 8.],
[ 0., 0.],
[ 1., 8.],
[ 0., 4.]])
>>> np.hstack((a,b))
array([[ 8., 8., 1., 8.],
[ 0., 0., 0., 4.]])
column_stack 把 1D array 作为列组合成一个 2D array。对于 2D arrays 是和 hstack 一样的。
>>> from numpy import newaxis
>>> np.column_stack((a,b)) # with 2D arrays
array([[ 8., 8., 1., 8.],
[ 0., 0., 0., 4.]])
>>> a = np.array([4.,2.])
>>> b = np.array([3.,8.])
>>> np.column_stack((a,b)) # returns a 2D array
array([[ 4., 3.],
[ 2., 8.]])
>>> np.hstack((a,b)) # the result is different
array([ 4., 2., 3., 8.])
>>> a[:,newaxis] # this allows to have a 2D columns vector
array([[ 4.],
[ 2.]])
>>> np.column_stack((a[:,newaxis],b[:,newaxis]))
array([[ 4., 3.],
[ 2., 8.]])
>>> np.hstack((a[:,newaxis],b[:,newaxis])) # the result is the same
array([[ 4., 3.],
[ 2., 8.]])
另一方面 row_stack 对任何输入数组来说都和 vstack 一样。简单的说,hstack 按第二 index 来拼接,而 vstack 按第一轴拼接。concatenate 可以用来指定需要沿第几轴拼接。
>>> a = np.array([[1, 2], [3, 4]])
>>> b = np.array([[5, 6]])
>>> np.concatenate((a, b), axis=0)
array([[1, 2],
[3, 4],
[5, 6]])
>>> np.concatenate((a, b.T), axis=1)
array([[1, 2, 5],
[3, 4, 6]])
注意:
r_ 和 c_ 在构建数组的时候也很有用,默认行为类似于 vstack 和 hstack,但是可以可选参数指定沿哪一轴拼接。
>>> np.r_[1:4,0,4]
array([1, 2, 3, 0, 4])
See also:
hstack, vstack, column_stack, concatenate, c_, r_
拆分数组¶
使用 hsplit,可以沿水平轴拆分数组,或者指定需要返回几个数组,也可以指定在哪些列进行拆分。
>>> a = np.floor(10*np.random.random((2,12)))
>>> a
array([[ 9., 5., 6., 3., 6., 8., 0., 7., 9., 7., 2., 7.],
[ 1., 4., 9., 2., 2., 1., 0., 6., 2., 2., 4., 0.]])
>>> np.hsplit(a,3) # Split a into 3
[array([[ 9., 5., 6., 3.],
[ 1., 4., 9., 2.]]), array([[ 6., 8., 0., 7.],
[ 2., 1., 0., 6.]]), array([[ 9., 7., 2., 7.],
[ 2., 2., 4., 0.]])]
>>> np.hsplit(a,(3,4)) # Split a after the third and the fourth column
[array([[ 9., 5., 6.],
[ 1., 4., 9.]]), array([[ 3.],
[ 2.]]), array([[ 6., 8., 0., 7., 9., 7., 2., 7.],
[ 2., 1., 0., 6., 2., 2., 4., 0.]])]
vsplit 沿着垂直轴进行拆分,array_split 允许指定沿着哪个轴拆分。
复制和 view¶
如下的赋值是不会有任何的深度复制的,python 通过引用传递可变对象,函数调用也不会复制。
>>> a = np.arange(12)
>>> b = a # no new object is created
>>> b is a # a and b are two names for the same ndarray object
True
>>> b.shape = 3,4 # changes the shape of a
>>> a.shape
(3, 4)
>>> def f(x):
... print(id(x))
...
>>> id(a) # id is a unique identifier of an object
148293216
>>> f(a)
148293216
view 和浅复制
不同的数组可以共享数据,view 方法产生一个数组核心数据的引用。改变 view 的属性值不改变原数组的属性,但改变核心数据会影响原 array。
>>> c = a.view()
>>> c is a
False
>>> c.base is a # c is a view of the data owned by a
True
>>> c.flags.owndata
False
>>>
>>> c.shape = 2,6 # a's shape doesn't change
>>> a.shape
(3, 4)
>>> c[0,4] = 1234 # a's data changes
>>> a
array([[ 0, 1, 2, 3],
[1234, 5, 6, 7],
[ 8, 9, 10, 11]])
对 array 进行切片返回一个 view
>>> s = a[ : , 1:3] # spaces added for clarity; could also be written "s = a[:,1:3]"
>>> s[:] = 10 # s[:] is a view of s. Note the difference between s=10 and s[:]=10
>>> a
array([[ 0, 10, 10, 3],
[1234, 10, 10, 7],
[ 8, 10, 10, 11]])
深复制
copy 方法返回一个完全的拷贝。
>>> d = a.copy() # a new array object with new data is created
>>> d is a
False
>>> d.base is a # d doesn't share anything with a
False
>>> d[0,0] = 9999
>>> a
array([[ 0, 10, 10, 3],
[1234, 10, 10, 7],
[ 8, 10, 10, 11]])
函数和方法总览¶
Array Creation
arange, array, copy, empty, empty_like, eye, fromfile, fromfunction, identity, linspace, logspace, mgrid, ogrid, ones, ones_like, r, zeros, zeros_like
Conversions
ndarray.astype, atleast_1d, atleast_2d, atleast_3d, mat
Manipulations
array_split, column_stack, concatenate, diagonal, dsplit, dstack, hsplit, hstack, ndarray.item, newaxis, ravel, repeat, reshape, resize, squeeze, swapaxes, take, transpose, vsplit, vstack
Questions
all, any, nonzero, where
Ordering
argmax, argmin, argsort, max, min, ptp, searchsorted, sort
Operations
choose, compress, cumprod, cumsum, inner, ndarray.fill, imag, prod, put, putmask, real, sum
Basic Statistics
cov, mean, std, var
Basic Linear Algebra
cross, dot, outer, linalg.svd, vdot
进阶¶
Broadcasting Rules¶
就是只能转换为 elementwise 操作
index tricks¶
第一种方法:用 array 作为 index
>>> a = np.arange(12)**2 # the first 12 square numbers
>>> i = np.array( [ 1,1,3,8,5 ] ) # an array of indices
>>> a[i] # the elements of a at the positions i
array([ 1, 1, 9, 64, 25])
>>>
>>> j = np.array( [ [ 3, 4], [ 9, 7 ] ] ) # a bidimensional array of indices
>>> a[j] # the same shape as j
array([[ 9, 16],
[81, 49]])
甚至可以用 两个 array 作为 index 实现双重选择
>>> a = np.arange(12).reshape(3,4)
>>> a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> i = np.array( [ [0,1], # indices for the first dim of a
... [1,2] ] )
>>> j = np.array( [ [2,1], # indices for the second dim
... [3,3] ] )
>>>
>>> a[i,j] # i and j must have equal shape
array([[ 2, 5],
[ 7, 11]])
>>>
>>> a[i,2]
array([[ 2, 6],
[ 6, 10]])
>>>
>>> a[:,j] # i.e., a[ : , j]
array([[[ 2, 1],
[ 3, 3]],
[[ 6, 5],
[ 7, 7]],
[[10, 9],
[11, 11]]])
一个例子
>>> time = np.linspace(20, 145, 5) # time scale
>>> data = np.sin(np.arange(20)).reshape(5,4) # 4 time-dependent series
>>> time
array([ 20. , 51.25, 82.5 , 113.75, 145. ])
>>> data
array([[ 0. , 0.84147098, 0.90929743, 0.14112001],
[-0.7568025 , -0.95892427, -0.2794155 , 0.6569866 ],
[ 0.98935825, 0.41211849, -0.54402111, -0.99999021],
[-0.53657292, 0.42016704, 0.99060736, 0.65028784],
[-0.28790332, -0.96139749, -0.75098725, 0.14987721]])
>>>
>>> ind = data.argmax(axis=0) # index of the maxima for each series
>>> ind
array([2, 0, 3, 1])
>>>
>>> time_max = time[ind] # times corresponding to the maxima
>>>
>>> data_max = data[ind, range(data.shape[1])] # => data[ind[0],0], data[ind[1],1]...
>>>
>>> time_max
array([ 82.5 , 20. , 113.75, 51.25])
>>> data_max
array([ 0.98935825, 0.84147098, 0.99060736, 0.6569866 ])
>>>
>>> np.all(data_max == data.max(axis=0))
True
也可以把 array indexed 数组作为赋值对象,但是如果 index 重复出现则以最后一次为准,注意 array 在 python 中的 += 方法可能会出现意想不到的结果。
>>> a = np.arange(5)
>>> a[[0,0,2]]+=1
>>> a
array([1, 1, 3, 3, 4])
虽然 index 0 出现了两次,但是只会增加一次,因为 a+=1 等同于 a = a + 1.
第二种方法:用 boolean 选择器作为 index,确保长度不要越界
>>> a = np.arange(12).reshape(3,4)
>>> b1 = np.array([False,True,True]) # first dim selection
>>> b2 = np.array([True,False,True,False]) # second dim selection
>>>
>>> a[b1,:] # selecting rows
array([[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>>
>>> a[b1] # same thing
array([[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>>
>>> a[:,b2] # selecting columns
array([[ 0, 2],
[ 4, 6],
[ 8, 10]])
>>>
>>> a[b1,b2] # a weird thing to do
array([ 4, 10])
一个产生 mandelbrot set 的例子
>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> def mandelbrot( h,w, maxit=20 ):
... """Returns an image of the Mandelbrot fractal of size (h,w)."""
... y,x = np.ogrid[ -1.4:1.4:h*1j, -2:0.8:w*1j ]
... c = x+y*1j
... z = c
... divtime = maxit + np.zeros(z.shape, dtype=int)
...
... for i in range(maxit):
... z = z**2 + c
... diverge = z*np.conj(z) > 2**2 # who is diverging
... div_now = diverge & (divtime==maxit) # who is diverging now
... divtime[div_now] = i # note when
... z[diverge] = 2 # avoid diverging too much
...
... return divtime
>>> plt.imshow(mandelbrot(400,400))
>>> plt.show()
ix_() 函数¶
ix_() 用以组合 不同的 array 以产生 任意 n-uplet 的结果,也就是说在几个 array 里分别取一个之后运算的结果。比如计算 a+b*c
>>> a = np.array([2,3,4,5])
>>> b = np.array([8,5,4])
>>> c = np.array([5,4,6,8,3])
>>> ax,bx,cx = np.ix_(a,b,c)
>>> ax
array([[[2]],
[[3]],
[[4]],
[[5]]])
>>> bx
array([[[8],
[5],
[4]]])
>>> cx
array([[[5, 4, 6, 8, 3]]])
>>> ax.shape, bx.shape, cx.shape
((4, 1, 1), (1, 3, 1), (1, 1, 5))
>>> result = ax+bx*cx
>>> result
array([[[42, 34, 50, 66, 26],
[27, 22, 32, 42, 17],
[22, 18, 26, 34, 14]],
[[43, 35, 51, 67, 27],
[28, 23, 33, 43, 18],
[23, 19, 27, 35, 15]],
[[44, 36, 52, 68, 28],
[29, 24, 34, 44, 19],
[24, 20, 28, 36, 16]],
[[45, 37, 53, 69, 29],
[30, 25, 35, 45, 20],
[25, 21, 29, 37, 17]]])
>>> result[3,2,4]
17
>>> a[3]+b[2]*c[4]
17
可以这样实现 reduce
>>> def ufunc_reduce(ufct, *vectors):
... vs = np.ix_(*vectors)
... r = ufct.identity
... for v in vs:
... r = ufct(r,v)
... return r
>>> ufunc_reduce(np.add,a,b,c)
array([[[15, 14, 16, 18, 13],
[12, 11, 13, 15, 10],
[11, 10, 12, 14, 9]],
[[16, 15, 17, 19, 14],
[13, 12, 14, 16, 11],
[12, 11, 13, 15, 10]],
[[17, 16, 18, 20, 15],
[14, 13, 15, 17, 12],
[13, 12, 14, 16, 11]],
[[18, 17, 19, 21, 16],
[15, 14, 16, 18, 13],
[14, 13, 15, 17, 12]]])
此版本的 reduce 和 ufunc.reduce 相比的优点是利用 broadcasting rules 从而避免了中间变量的产生。
线性代数¶
简单的线代操作
>>> import numpy as np
>>> a = np.array([[1.0, 2.0], [3.0, 4.0]])
>>> print(a)
[[ 1. 2.]
[ 3. 4.]]
>>> a.transpose()
array([[ 1., 3.],
[ 2., 4.]])
>>> np.linalg.inv(a)
array([[-2. , 1. ],
[ 1.5, -0.5]])
>>> u = np.eye(2) # unit 2x2 matrix; "eye" represents "I"
>>> u
array([[ 1., 0.],
[ 0., 1.]])
>>> j = np.array([[0.0, -1.0], [1.0, 0.0]])
>>> np.dot (j, j) # matrix product
array([[-1., 0.],
[ 0., -1.]])
>>> np.trace(u) # trace
2.0
>>> y = np.array([[5.], [7.]])
>>> np.linalg.solve(a, y)
array([[-3.],
[ 4.]])
>>> np.linalg.eig(j)
(array([ 0.+1.j, 0.-1.j]), array([[ 0.70710678+0.j , 0.70710678-0.j ],
[ 0.00000000-0.70710678j, 0.00000000+0.70710678j]]))
tricks and tips¶
自动 reshape
>>> a = np.arange(30)
>>> a.shape = 2,-1,3 # -1 means "whatever is needed"
>>> a.shape
(2, 5, 3)
>>> a
array([[[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11],
[12, 13, 14]],
[[15, 16, 17],
[18, 19, 20],
[21, 22, 23],
[24, 25, 26],
[27, 28, 29]]])
vector 拼接
前面提到过
热力图
numpy histogram 函数以 array 为输入,输出一个 hitogram 向量和一个 bin 向量。matplotlib 也有热力图函数 hist 和numpy 中的不一样,主要区别是 hist 自动画出热力图而 numpy.histogram 只是产生数据。
>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> # Build a vector of 10000 normal deviates with variance 0.5^2 and mean 2
>>> mu, sigma = 2, 0.5
>>> v = np.random.normal(mu,sigma,10000)
>>> # Plot a normalized histogram with 50 bins
>>> plt.hist(v, bins=50, normed=1) # matplotlib version (plot)
>>> plt.show()
>>> # Compute the histogram with numpy and then plot it
>>> (n, bins) = np.histogram(v, bins=50, normed=True) # NumPy version (no plot)
>>> plt.plot(.5*(bins[1:]+bins[:-1]), n)
>>> plt.show()
其他东西¶
自定义 dtype¶
创建 array 时自定义 dtype 类型,也可以包含 str 类型比如
n_drops = 5
rain_drops = np.zeros(n_drops, dtype=[('position', float, 2),
('size', float, 1),
('growth', float, 1),
('color', float, 4),
('name', str, 1)])
>>> rain_drops
array([([0., 0.], 0., 0., [0., 0., 0., 0.], ''),
([0., 0.], 0., 0., [0., 0., 0., 0.], ''),
([0., 0.], 0., 0., [0., 0., 0., 0.], ''),
([0., 0.], 0., 0., [0., 0., 0., 0.], ''),
([0., 0.], 0., 0., [0., 0., 0., 0.], '')],
dtype=[('position', '<f8', (2,)), ('size', '<f8'), ('growth', '<f8'), ('color', '<f8', (4,)), ('name', '<U1')])
>>> rain_drops[0]
([0., 0.], 0., 0., [0., 0., 0., 0.], '')
>>> rain_drops[0]['position']
array([0., 0.])
一些统计函数¶
In [109]: np.sum(arr11) #计算所有元素的和
Out[109]: -18
In [110]: np.sum(arr11,axis = 0) #对每一列求和,注意axis是0
Out[110]: array([ -2, -6, -10])
In [111]: np.sum(arr11, axis = 1) #对每一行求和,注意axis是1
Out[111]: array([ 9, 0, -9, -18])
In [112]: np.cumsum(arr11) #对每一个元素求累积和(从上到下,从左到右的元素顺序),即每移动一次就把当前数字加到和值
Out[112]: array([ 4, 7, 9, 10, 10, 9, 7, 4, 0, -5, -11, -18], dtype=int32)
In [113]: np.cumsum(arr11, axis = 0) #计算每一列的累积和,并返回二维数组
Out[113]:
array([[ 4, 3, 2],
[ 5, 3, 1],
[ 3, 0, -3],
[ -2, -6, -10]], dtype=int32)
In [114]: np.cumprod(arr11, axis = 1) #计算每一行的累计积,并返回二维数组
Out[114]:
array([[ 4, 12, 24],
[ 1, 0, 0],
[ -2, 6, -24],
[ -5, 30, -210]], dtype=int32)
In [115]: np.min(arr11) #计算所有元素的最小值
Out[115]: -7
In [116]: np.max(arr11, axis = 0) #计算每一列的最大值
Out[116]: array([4, 3, 2])
In [117]: np.mean(arr11) #计算所有元素的均值
Out[117]: -1.5
In [118]: np.mean(arr11, axis = 1) #计算每一行的均值
Out[118]: array([ 3., 0., -3., -6.])
In [119]: np.median(arr11) #计算所有元素的中位数
Out[119]: -1.5
In [120]: np.median(arr11, axis = 0) #计算每一列的中位数
Out[120]: array([-0.5, -1.5, -2.5])
In [121]: np.var(arr12) #计算所有元素的方差
Out[121]: 5.354166666666667
In [122]: np.std(arr12, axis = 1) #计算每一行的标准差
Out[122]: array([ 2.49443826, 1.88561808, 1.69967317, 2.1602469 ])
另外:
unique(x): 计算x的唯一元素,并返回有序结果 intersect(x,y): 计算x和y的公共元素,即交集 union1d(x,y): 计算x和y的并集 setdiff1d(x,y): 计算x和y的差集,即元素在x中,不在y中 setxor1d(x,y): 计算集合的对称差,即存在于一个数组中,但不同时存在于两个数组中 in1d(x,y): 判断x的元素是否包含于y中
numpy.random 模块¶
一些常用的 random 函数
rand(d0, d1, ..., dn) Random values in a given shape.
randn(d0, d1, ..., dn) Return a sample (or samples) from the “standard normal” distribution.
randint(low[, high, size, dtype]) Return random integers from low (inclusive) to high (exclusive).
random_integers(low[, high, size]) Random integers of type np.int between low and high, inclusive.
random_sample([size]) Return random floats in the half-open interval [0.0, 1.0).
random([size]) Return random floats in the half-open interval [0.0, 1.0).
ranf([size]) Return random floats in the half-open interval [0.0, 1.0).
sample([size]) Return random floats in the half-open interval [0.0, 1.0).
choice(a[, size, replace, p]) Generates a random sample from a given 1-D array
bytes(length) Return random bytes.
array conversion¶
ndarray.item(*args) Copy an element of an array to a standard Python scalar and return it.
ndarray.tolist() Return the array as a (possibly nested) list.
ndarray.itemset(*args) Insert scalar into an array (scalar is cast to array’s dtype, if possible)
ndarray.tostring([order]) Construct Python bytes containing the raw data bytes in the array.
ndarray.tobytes([order]) Construct Python bytes containing the raw data bytes in the array.
ndarray.tofile(fid[, sep, format]) Write array to a file as text or binary (default).
ndarray.dump(file) Dump a pickle of the array to the specified file.
ndarray.dumps() Returns the pickle of the array as a string.
ndarray.astype(dtype[, order, casting, ...]) Copy of the array, cast to a specified type.
ndarray.byteswap(inplace) Swap the bytes of the array elements
ndarray.copy([order]) Return a copy of the array.
ndarray.view([dtype, type]) New view of array with the same data.
ndarray.getfield(dtype[, offset]) Returns a field of the given array as a certain type.
ndarray.setflags([write, align, uic]) Set array flags WRITEABLE, ALIGNED, and UPDATEIFCOPY, respectively.
ndarray.fill(value) Fill the array with a scalar value.
shape manipulation¶
ndarray.reshape(shape[, order]) Returns an array containing the same data with a new shape.
ndarray.resize(new_shape[, refcheck]) Change shape and size of array in-place.
ndarray.transpose(*axes) Returns a view of the array with axes transposed.
ndarray.swapaxes(axis1, axis2) Return a view of the array with axis1 and axis2 interchanged.
ndarray.flatten([order]) Return a copy of the array collapsed into one dimension.
ndarray.ravel([order]) Return a flattened array.
ndarray.squeeze([axis]) Remove single-dimensional entries from the shape of a.
Item selection and manipulation¶
ndarray.take(indices[, axis, out, mode]) Return an array formed from the elements of a at the given indices.
ndarray.put(indices, values[, mode]) Set a.flat[n] = values[n] for all n in indices.
ndarray.repeat(repeats[, axis]) Repeat elements of an array.
ndarray.choose(choices[, out, mode]) Use an index array to construct a new array from a set of choices.
ndarray.sort([axis, kind, order]) Sort an array, in-place.
ndarray.argsort([axis, kind, order]) Returns the indices that would sort this array.
ndarray.partition(kth[, axis, kind, order]) Rearranges the elements in the array in such a way that value of the element in kth position is in the position it would be in a sorted array.
ndarray.argpartition(kth[, axis, kind, order]) Returns the indices that would partition this array.
ndarray.searchsorted(v[, side, sorter]) Find indices where elements of v should be inserted in a to maintain order.
ndarray.nonzero() Return the indices of the elements that are non-zero.
ndarray.compress(condition[, axis, out]) Return selected slices of this array along given axis.
ndarray.diagonal([offset, axis1, axis2]) Return specified diagonals.
caculation¶
ndarray.argmax([axis, out]) Return indices of the maximum values along the given axis.
ndarray.min([axis, out, keepdims]) Return the minimum along a given axis.
ndarray.argmin([axis, out]) Return indices of the minimum values along the given axis of a.
ndarray.ptp([axis, out]) Peak to peak (maximum - minimum) value along a given axis.
ndarray.clip([min, max, out]) Return an array whose values are limited to [min, max].
ndarray.conj() Complex-conjugate all elements.
ndarray.round([decimals, out]) Return a with each element rounded to the given number of decimals.
ndarray.trace([offset, axis1, axis2, dtype, out]) Return the sum along diagonals of the array.
ndarray.sum([axis, dtype, out, keepdims]) Return the sum of the array elements over the given axis.
ndarray.cumsum([axis, dtype, out]) Return the cumulative sum of the elements along the given axis.
ndarray.mean([axis, dtype, out, keepdims]) Returns the average of the array elements along given axis.
ndarray.var([axis, dtype, out, ddof, keepdims]) Returns the variance of the array elements, along given axis.
ndarray.std([axis, dtype, out, ddof, keepdims]) Returns the standard deviation of the array elements along given axis.
ndarray.prod([axis, dtype, out, keepdims]) Return the product of the array elements over the given axis
ndarray.cumprod([axis, dtype, out]) Return the cumulative product of the elements along the given axis.
ndarray.all([axis, out, keepdims]) Returns True if all elements evaluate to True.
ndarray.any([axis, out, keepdims]) Returns True if any of the elements of a evaluate to True.
Numpy 中的 reshape 操作¶
关于 numpy 中的 array,改变其 shape,有时可以有时不可以,这很奇怪。
比如
arr = np.zeros((4,4))
slc = arr[:3,:]
slc.shape
(3, 4)
slc.shape = 4,3
slc
array([[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]])
slc = arr[:,:3]
slc.shape
(4, 3)
slc.shape = 3,4
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-11-780d0b1f4a04> in <module>()
----> 1 slc.shape = 3,4
AttributeError: incompatible shape for a non-contiguous array
这时,slc 无法改变 shape,这主要和 arr 在内存中的存储形式有关,在初始化 arr 的时候,里面的数据就按顺序排好了,而切片取前三列后如果想进行改变形状的操作,就需要在内存中跳跃,这对计算机来说是很困难的。使用 resize 同样无法改变形状
slc.resize(3, 4, refcheck = False)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-13-36d7dadd0aa5> in <module>()
----> 1 slc.resize(3, 4, refcheck = False)
ValueError: resize only works on single-segment arrays
给出的提示更加明了 only works on single-segment arrays 就是因为此时数据分段了。可以用 flags 属性查看一下,slc 并不拥有数据,而是引用 base 数组的数据。没有数据且引用的数据在内存中不连续就是无法 resize 的原因。
>>> slc.flags
C_CONTIGUOUS : False
F_CONTIGUOUS : False
OWNDATA : False
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
UPDATEIFCOPY : False
因为 reshape 创建一个全新的 array,所以无论对什么数组,都能进行 reshape
slc.reshape(3,4)
array([[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]])
之所以以 view 来举例是因为一般切片会产生不连续的 array(内存存储),而切片产生的正是原 array 的 view,当然获取 view 有很多种方式,比如直接 newarr = arr.view()
NumPy 中有些函数要求 one segment array,比如
>>> import numpy as np
>>> a = np.arange(10)
>>> a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> b = a[::2]
>>> b
array([0, 2, 4, 6, 8])
np.sort(b) 因为 b 只是 a 的一个切片 view 而不是 one segment array,所以函数必须先把 b 复制到一块新的内存上再做排序。
Pandas Quick Start¶
参考:
https://pandas.pydata.org/pandas-docs/stable/10min.html
https://pyzh.readthedocs.io/en/latest/python-pandas.html
1. 什么是pandas?¶
pandas: Python 数据分析模块
pandas是为了解决数据分析任务而创建的,纳入了大量的库和标准数据模型,提供了高效地操作大型数据集所需的工具。
pandas中的数据结构 :
Series: 一维数组,类似于python中的基本数据结构list,区别是series只允许存储相同的数据类型,这样可以更有效的使用内存,提高运算效率。就像数据库中的列数据。
DataFrame: 二维的表格型数据结构。很多功能与R中的data.frame类似。可以将DataFrame理解为Series的容器。
Panel:三维的数组,可以理解为DataFrame的容器。
2. 十分钟搞定pandas¶
引入需要的包:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
注
numpy 是一个python实现的科学计算包
matplotlib 是一个python的2D绘图库
3.创建对象¶
详情请查看 数据结构介绍
1.通过传入一个列表来创建 Series ,pandas会创建默认的整形 index:
>>> s = pd.Series([1,3,5,np.nan,6,8])
>>> s
0 1
1 3
2 5
3 NaN
4 6
5 8
dtype: float64
2.通过传递数字数组、时间索引、列标签来创建 DataFrame
>>> dates = pd.date_range('20130101',periods=6)
>>> dates
DatetimeIndex(['2013-01-01', '2013-01-02', '2013-01-03', '2013-01-04',
'2013-01-05', '2013-01-06'],
dtype='datetime64[ns]', freq='D')
>>> df = pd.DataFrame(np.random.randn(6,4),index=dates,columns=list('ABCD'))
>>> df
A B C D
2013-01-01 0.859619 -0.545903 0.012447 1.257684
2013-01-02 0.119622 -0.484051 0.404728 0.360880
2013-01-03 -0.719234 -0.396174 0.635237 0.216691
2013-01-04 -0.921692 0.876693 -0.670553 1.468060
2013-01-05 -0.300317 -0.011320 -1.376442 1.694740
2013-01-06 -1.903683 0.786785 -0.194179 0.177973
3.通过传递能被转换成类似结构的字典来创建 DataFrame:
>>>df2 = pd.DataFrame({'A' : 1.,
'B' : pd.Timestamp('20130102'),
'C' : pd.Series(1,index=list(range(4)),dtype='float32'),
'D' : np.array([3] * 4,dtype='int32'),
'E' : pd.Categorical(["test","train","test","train"]),
'F' : 'foo' })
>>> df2
A B C D E F
0 1 2013-01-02 1 3 test foo
1 1 2013-01-02 1 3 train foo
2 1 2013-01-02 1 3 test foo
3 1 2013-01-02 1 3 train foo
4.查看各列的 dtypes
>>> df2.dtypes
A float64
B datetime64[ns]
C float32
D int32
E category
F object
dtype: object
5.如果使用IPython,Tab会自动补全所有的属性和自定义的列,如下所示:
>>> df2.<TAB>
df2.A df2.boxplot
df2.abs df2.C
df2.add df2.clip
df2.add_prefix df2.clip_lower
df2.add_suffix df2.clip_upper
df2.align df2.columns
df2.all df2.combine
df2.any df2.combineAdd
df2.append df2.combine_first
df2.apply df2.combineMult
df2.applymap df2.compound
df2.as_blocks df2.consolidate
df2.asfreq df2.convert_objects
df2.as_matrix df2.copy
df2.astype df2.corr
df2.at df2.corrwith
df2.at_time df2.count
df2.axes df2.cov
df2.B df2.cummax
df2.between_time df2.cummin
df2.bfill df2.cumprod
df2.blocks df2.cumsum
df2.bool df2.D
可以看到,A、B、C、D列均通过Tab自动生成
4. 查看数据¶
1.查看DataFrame头部和尾部行的数据:
>>> df.head()
>>> df.tail(3)
2.查看索引、列、和数组数据:
>>> df.index
DatetimeIndex(['2013-01-01', '2013-01-02', '2013-01-03', '2013-01-04',
'2013-01-05', '2013-01-06'],
dtype='datetime64[ns]', freq='D')
>>> df.columns
Index([u'A', u'B', u'C', u'D'], dtype='object')
>>> df.values
array([[ 0.85961861, -0.54590304, 0.01244705, 1.25768432],
[ 0.11962178, -0.4840508 , 0.40472795, 0.36088029],
[-0.7192337 , -0.39617432, 0.63523701, 0.21669124],
[-0.92169244, 0.87669275, -0.67055318, 1.46806034],
[-0.30031679, -0.01132035, -1.37644224, 1.69474031],
[-1.90368258, 0.78678454, -0.19417942, 0.17797326]])
3.查看数据的快速统计结果:
>>> df.describe()
A B C D
count 6.000000 6.000000 6.000000 6.000000
mean -0.477614 0.037671 -0.198127 0.862672
std 0.945047 0.643196 0.736736 0.685969
min -1.903683 -0.545903 -1.376442 0.177973
25% -0.871078 -0.462082 -0.551460 0.252739
50% -0.509775 -0.203747 -0.090866 0.809282
75% 0.014637 0.587258 0.306658 1.415466
max 0.859619 0.876693 0.635237 1.694740
4.对数据进行行列转换,转置:
>>> df.T
5.按 axis 排序:
>>> df.sort_index(axis=1, ascending=False) # 按列标签排序
6.按值排序:
>>> df.sort_values(by='B') # 按 B 列的值排序
5.选择数据¶
Python 和 Numpy 中的表达式有时不够直观,使用 Pandas 方法比如 at, iat, loc, iloc, ix
等。Indexing and Selecting Data, MultiIndex / Advanced Indexing
标签式选择¶
loc
和 at
df.loc[dates[0]]
df.loc[:,['A','B']]
df.loc['20130102':'20130104',['A','B']]
df.loc['20130102':'20130104','A':'B']
df.loc['20130102':'20130104',['A','B']]
df.loc[dates[0],'A']
df.at[dates[0],'A']
位置式选择¶
iloc
和 iat
df.iloc[3]
df.iloc[3:5,0:2]
df.iloc[[1,2,4],[0,2]]
df.iloc[1:3,:]
df.iloc[:,1:3]
df.iloc[1,1]
df.iat[1,1]
Boolean 索引¶
df[df.A > 0] # 通过条件行号选取
df[df > 0] # where 选择数据
# 通过 isin 选择,通常不对数据进行选择
df2['E'] = ['one', 'one','two','three','four','three']
df2[df2['E'].isin(['two','four'])]
Boolean 索引可以是表达式,也可以是表达式的组合,用来进行一些复杂的选择和查找
设置¶
新增一列数据
s1 = pd.Series([1,2,3,4,5,6], index=pd.date_range('20130102', periods=6))
df['F'] = s1
通过标签更新值 df.at[dates[0],'A'] = 0
通过位置更新值 df.iat[0,1] = 0
通过数据更新一系列值 df.loc[:,'D'] = np.array([5] * len(df))
通过 where 更新值 df2[df2 > 0] = -df2
6.缺失数据的处理¶
pandas 用 np.nan 代表缺失数据 Missing Data section
- reindex() 可以修改/增加/删除索引,返回一个数据的副本:
>>> df1 = df.reindex(index=dates[0:4], columns=list(df.columns) + ['E'])
>>> df1.loc[dates[0]:dates[1],'E'] = 1
>>> df1
A B C D F E
2013-01-01 0.000000 0.000000 0.012447 5 NaN 1
2013-01-02 0.119622 -0.484051 0.404728 5 1 1
2013-01-03 -0.719234 -0.396174 0.635237 5 2 NaN
2013-01-04 -0.921692 0.876693 -0.670553 5 3 NaN
2.丢掉含有缺失项的行:
>>> df1.dropna(how='any')
A B C D F E
2013-01-02 0.119622 -0.484051 0.404728 5 1 1
3.对缺失项赋值:
>>> df1.fillna(value=5)
A B C D F E
2013-01-01 0.000000 0.000000 0.012447 5 5 1
2013-01-02 0.119622 -0.484051 0.404728 5 1 1
2013-01-03 -0.719234 -0.396174 0.635237 5 2 5
2013-01-04 -0.921692 0.876693 -0.670553 5 3 5
4.对缺失项布尔赋值:
>>> pd.isnull(df1)
A B C D F E
2013-01-01 False False False False True False
2013-01-02 False False False False False False
2013-01-03 False False False False False True
2013-01-04 False False False False False True
7.相关操作¶
统计(操作通常情况下不包含缺失项)
1.按列求平均值:
>>> df.mean()
2.按行求平均值:
>>> df.mean(1)
3.操作不同的维度需要先对齐,pandas会沿着指定维度执行,如果有 index 的话,pandas 会自动进行对齐:
>>> s = pd.Series([1,3,5,np.nan,6,8], index=dates).shift(2)
>>> s
2013-01-01 NaN
2013-01-02 NaN
2013-01-03 1
2013-01-04 3
2013-01-05 5
2013-01-06 NaN
Freq: D, dtype: float64
>>> df.sub(s, axis='index') # 等同 df.sub(s, axis=0)
A B C D F
2013-01-01 NaN NaN NaN NaN NaN
2013-01-02 NaN NaN NaN NaN NaN
2013-01-03 -1.719234 -1.396174 -0.364763 4 1
2013-01-04 -3.921692 -2.123307 -3.670553 2 0
2013-01-05 -5.300317 -5.011320 -6.376442 0 -1
2013-01-06 NaN NaN NaN NaN NaN
注:
这里对齐维度指的对齐时间index shift(2)指沿着时间轴将数据顺移两位,空位补 NaN sub指减法,与NaN进行操作,结果也是NaN
应用
1.对数据应用function:
>>> df.apply(np.cumsum)
A B C D F
2013-01-01 0.000000 0.000000 0.012447 5 NaN
2013-01-02 0.119622 -0.484051 0.417175 10 1
2013-01-03 -0.599612 -0.880225 1.052412 15 3
2013-01-04 -1.521304 -0.003532 0.381859 20 6
2013-01-05 -1.821621 -0.014853 -0.994583 25 10
2013-01-06 -3.725304 0.771932 -1.188763 30 15
>>> df.apply(lambda x: x.max() - x.min())
A 2.023304
B 1.360744
C 2.011679
D 0.000000
F 4.000000
dtype: float64
注: cumsum 累加
直方图:
>>> s = pd.Series(np.random.randint(0, 7, size=10))
>>> s
0 1
1 3
2 5
3 1
4 6
5 1
6 3
7 4
8 0
9 3
dtype: int64
>>> s.value_counts()
3 3
1 3
6 1
5 1
4 1
0 1
dtype: int64
pandas默认配置了一些字符串处理方法,可以方便的操作元素 Vectorized String Methods
字符串方法:
>>> s = pd.Series(['A', 'B', 'C', 'Aaba', 'Baca', np.nan, 'CABA', 'dog', 'cat'])
>>> s.str.lower()
0 a
1 b
2 c
3 aaba
4 baca
5 NaN
6 caba
7 dog
8 cat
dtype: object
8. 合并¶
连接
pandas提供了大量的方法,能轻松的对Series,DataFrame和Panel执行合并操作。Merging section
使用 concat() 连接 pandas 对象:
>>> df = pd.DataFrame(np.random.randn(10, 4))
>>> pieces = [df[:3], df[3:7], df[7:]]
>>> pd.concat(pieces)
0 1 2 3
0 -0.199614 1.914485 0.396383 -0.295306
1 -0.061961 -1.352883 0.266751 -0.874132
2 0.346504 -2.328099 -1.492250 0.095392
3 0.187115 0.562740 -1.677737 -0.224807
4 -1.422599 -1.028044 0.789487 0.806940
5 0.439478 -0.592229 0.736081 1.008404
6 -0.205641 -0.649465 -0.706395 0.578698
7 -2.168725 -2.487189 0.060258 1.965318
8 0.207634 0.512572 0.595373 0.816516
9 0.764893 0.612208 -1.022504 -2.032126
Join 和 Merge
类似SQL的合并操作,Database style joining
>>> left = pd.DataFrame({'key': ['foo', 'foo'], 'lval': [1, 2]})
>>> right = pd.DataFrame({'key': ['foo', 'foo'], 'rval': [4, 5]})
>>> left
key lval
0 foo 1
1 foo 2
>>> right
key rval
0 foo 4
1 foo 5
>>> pd.merge(left, right, on='key')
key lval rval
0 foo 1 4
1 foo 1 5
2 foo 2 4
3 foo 2 5
>>> left = pd.DataFrame({'key': ['foo', 'bar'], 'lval': [1, 2]})
>>> right = pd.DataFrame({'key': ['foo', 'bar'], 'rval': [4, 5]})
>>> left
key lval
0 foo 1
1 bar 2
>>> right
key rval
0 foo 4
1 bar 5
>>> pd.merge(left, right, on='key')
key lval rval
0 foo 1 4
1 bar 2 5
追加
>>> df = pd.DataFrame(np.random.randn(8, 4), columns=['A','B','C','D'])
>>> s = df.iloc[3]
>>> df.append(s, ignore_index=True)
A B C D
0 -1.710447 2.541720 -0.654403 0.132077
1 0.667796 -1.124769 -0.430752 -0.244731
2 1.555865 -0.483805 0.066114 -0.409518
3 1.171798 0.036219 -0.515065 0.860625
4 -0.834051 -2.178128 -0.345627 0.819392
5 -0.354886 0.161204 1.465532 1.879841
6 0.560888 1.208905 1.301983 0.799084
7 -0.770196 0.307691 1.212200 0.909137
8 1.171798 0.036219 -0.515065 0.860625
9.分组¶
group by:
- Splitting 将数据分组
- Applying 对每个分组应用不同的function
- Combining 使用某种数据结果展示结果 Grouping section
>>> df = pd.DataFrame({'A' : ['foo', 'bar', 'foo', 'bar','foo', 'bar', 'foo', 'foo'],
'B' : ['one', 'one', 'two', 'three','two', 'two', 'one', 'three'],
'C' : np.random.randn(8),
'D' : np.random.randn(8)})
>>> df
A B C D
0 foo one -0.655020 -0.671592
1 bar one 0.846428 1.884603
2 foo two -2.280466 0.725070
3 bar three 1.166448 -0.208171
4 foo two -0.257124 -0.850319
5 bar two -0.654609 1.258091
6 foo one -1.624213 -0.383978
7 foo three -0.523944 0.114338
分组后sum求和:
>>> df.groupby('A').sum()
C D
A
bar 1.358267 2.934523
foo -5.340766 -1.066481
对多列分组后 sum:
>>> df.groupby(['A','B']).sum()
C D
A B
bar one 0.846428 1.884603
three 1.166448 -0.208171
two -0.654609 1.258091
foo one -2.279233 -1.055570
three -0.523944 0.114338
two -2.537589 -0.125249
10.重塑¶
Hierarchical Indexing 和 Reshaping
stack:
>>> tuples = list(zip(*[['bar', 'bar', 'baz', 'baz',
'foo', 'foo', 'qux', 'qux'],
['one', 'two', 'one', 'two',
'one', 'two', 'one', 'two']]))
>>> index = pd.MultiIndex.from_tuples(tuples, names=['first', 'second'])
>>> index
MultiIndex(levels=[[u'bar', u'baz', u'foo', u'qux'], [u'one', u'two']],
labels=[[0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 0, 1, 0, 1, 0, 1]],
names=[u'first', u'second'])
>>> df = pd.DataFrame(np.random.randn(8, 2), index=index, columns=['A', 'B'])
>>> df
A B
first second
bar one -0.922059 -0.918091
two -0.825565 -0.880527
baz one 0.241927 1.130320
two -0.261823 2.463877
foo one -0.220328 -0.519477
two -1.028038 -0.543191
qux one 0.315674 0.558686
two 0.422296 0.241212
>>> df2 = df[:4]
>>> df2
A B
first second
bar one -0.922059 -0.918091
two -0.825565 -0.880527
baz one 0.241927 1.130320
two -0.261823 2.463877
注:pd.MultiIndex.from_tuples 将包含多个list的元组转换为复杂索引
使用 stack() 方法为 DataFrame 增加 column:???
>>> stacked = df2.stack()
>>> stacked
first second
bar one A -0.922059
B -0.918091
two A -0.825565
B -0.880527
baz one A 0.241927
B 1.130320
two A -0.261823
B 2.463877
dtype: float64
使用unstack()方法还原stack的DataFrame,默认还原最后一级,也可以自由指定:
>>> stacked.unstack()
A B
first second
bar one -0.922059 -0.918091
two -0.825565 -0.880527
baz one 0.241927 1.130320
two -0.261823 2.463877
>>> stacked.unstack(1)
second one two
first
bar A -0.922059 -0.825565
B -0.918091 -0.880527
baz A 0.241927 -0.261823
B 1.130320 2.463877
>>> stacked.unstack(0)
first bar baz
second
one A -0.922059 0.241927
B -0.918091 1.130320
two A -0.825565 -0.261823
B -0.880527 2.463877
透视表 详情请查看 Pivot Tables
>>> df = pd.DataFrame({'A' : ['one', 'one', 'two', 'three'] * 3,
'B' : ['A', 'B', 'C'] * 4,
'C' : ['foo', 'foo', 'foo', 'bar', 'bar', 'bar'] * 2,
'D' : np.random.randn(12),
'E' : np.random.randn(12)})
注:可以理解为自由组合表的行与列,类似于交叉报表
>>> pd.pivot_table(df, values='D', index=['A', 'B'], columns=['C'])
C bar foo
A B
one A -1.250611 -1.047274
B 1.532134 -0.455948
C 0.125989 -0.500260
three A 0.623716 NaN
B NaN 0.095117
C -0.348707 NaN
two A NaN 0.390363
B -0.743466 NaN
C NaN 0.792279
11. 时间序列¶
pandas可以简单高效的进行重新采样通过频率转换(例如:将秒级数据转换成五分钟为单位的数据)。这常见与金融应用中,但是不限于此。详情请查看 Time Series section
>>> rng = pd.date_range('1/1/2012', periods=100, freq='S')
>>> ts = pd.Series(np.random.randint(0, 500, len(rng)), index=rng)
>>> ts.resample('5Min').sum()
2012-01-01 24390
Freq: 5T, dtype: int64
注:将随机产生的秒级数据整合成 5min 的数据
时区表现:
>>> rng = pd.date_range('3/6/2012 00:00', periods=5, freq='D')
>>> ts = pd.Series(np.random.randn(len(rng)), rng)
>>> ts
2012-03-06 0.972202
2012-03-07 -0.839969
2012-03-08 -0.979993
2012-03-09 -0.052460
2012-03-10 -0.487963
Freq: D, dtype: float64
>>> ts_utc = ts.tz_localize('UTC')
>>> ts_utc
2012-03-06 00:00:00+00:00 0.972202 2012-03-07 00:00:00+00:00 -0.839969 2012-03-08 00:00:00+00:00 -0.979993 2012-03-09 00:00:00+00:00 -0.052460 2012-03-10 00:00:00+00:00 -0.487963 Freq: D, dtype: float64
时区变换:
>>> ts_utc.tz_convert('US/Eastern')
2012-03-05 19:00:00-05:00 0.972202 2012-03-06 19:00:00-05:00 -0.839969 2012-03-07 19:00:00-05:00 -0.979993 2012-03-08 19:00:00-05:00 -0.052460 2012-03-09 19:00:00-05:00 -0.487963 Freq: D, dtype: float64
在不同的时间跨度表现间变换:
>>> rng = pd.date_range('1/1/2012', periods=5, freq='M')
>>> ts = pd.Series(np.random.randn(len(rng)), index=rng)
>>> ts
2012-01-31 -0.681068 2012-02-29 -0.263571 2012-03-31 1.268001 2012-04-30 0.331786 2012-05-31 0.663572 Freq: M, dtype: float64
>>> ps = ts.to_period()
>>> ps
2012-01 -0.681068
2012-02 -0.263571
2012-03 1.268001
2012-04 0.331786
2012-05 0.663572
Freq: M, dtype: float64
>>> ps.to_timestamp()
2012-01-01 -0.681068
2012-02-01 -0.263571
2012-03-01 1.268001
2012-04-01 0.331786
2012-05-01 0.663572
Freq: MS, dtype: float64
注:to_period()默认频率为M,to_period和to_timestamp可以相互转换
在周期和时间戳间转换,下面的栗子将季度时间转换为各季度最后一个月的09am:
>>> prng = pd.period_range('1990Q1', '2000Q4', freq='Q-NOV')
>>> prng
PeriodIndex(['1990Q1', '1990Q2', '1990Q3', '1990Q4', '1991Q1', '1991Q2',
'1991Q3', '1991Q4', '1992Q1', '1992Q2', '1992Q3', '1992Q4',
'1993Q1', '1993Q2', '1993Q3', '1993Q4', '1994Q1', '1994Q2',
'1994Q3', '1994Q4', '1995Q1', '1995Q2', '1995Q3', '1995Q4',
'1996Q1', '1996Q2', '1996Q3', '1996Q4', '1997Q1', '1997Q2',
'1997Q3', '1997Q4', '1998Q1', '1998Q2', '1998Q3', '1998Q4',
'1999Q1', '1999Q2', '1999Q3', '1999Q4', '2000Q1', '2000Q2',
'2000Q3', '2000Q4'],
dtype='int64', freq='Q-NOV')
>>> ts = pd.Series(np.random.randn(len(prng)), prng)
>>> ts.index = (prng.asfreq('M', 'e') + 1).asfreq('H', 's') + 9
>>> ts.head()
1990-03-01 09:00 -0.927090 1990-06-01 09:00 -1.045881 1990-09-01 09:00 -0.837705 1990-12-01 09:00 -0.529390 1991-03-01 09:00 -0.423405 Freq: H, dtype: float64
12.分类¶
从 0.15 版以后,pandas 可以在 DataFrame 中包含分类数据,category
>>> df = pd.DataFrame({"id":[1,2,3,4,5,6], "raw_grade":['a', 'b', 'b', 'a', 'a', 'e']})
1.将原始成绩转换为分类数据:
>>> df["grade"] = df["raw_grade"].astype("category")
>>> df["grade"]
0 a
1 b
2 b
3 a
4 a
5 e
Name: grade, dtype: category
Categories (3, object): [a, b, e]
2.重命名分类使其更有意义:
>>> df["grade"].cat.categories = ["very good", "good", "very bad"]
3.重新整理类别,并添加缺少的类别:
>>> df["grade"] = df["grade"].cat.set_categories(["very bad", "bad", "medium", "good", "very good"])
>>> df["grade"]
0 very good
1 good
2 good
3 very good
4 very good
5 very bad
Name: grade, dtype: category
Categories (5, object): [very bad, bad, medium, good, very good]
4.按整理后的类别排序 (并非词汇的顺序)
>>> df.sort_values(by="grade")
id raw_grade grade
5 6 e very bad
1 2 b good
2 3 b good
0 1 a very good
3 4 a very good
4 5 a very good
5.按类别分组也包括空类别:
>>> df.groupby("grade").size()
grade
very bad 1
bad 0
medium 0
good 2
very good 3
dtype: int64
13.绘图¶
plot 方法可以做简单的可视化
>>> df = pd.DataFrame(np.random.randn(1000, 4), index=ts.index,
columns=['A', 'B', 'C', 'D'])
>>> df = df.cumsum()
>>> plt.figure(); df.plot(); plt.legend(loc='best')
<matplotlib.legend.Legend at 0x7ff29c8163d0>
14.数据IO¶
csv¶
写入 csv df.to_csv('foo.csv')
读取 csv
pd.read_csv('foo.csv')
Unnamed: 0 A B C D
0 2000-01-01 0.266457 -0.399641 -0.219582 1.186860
1 2000-01-02 -1.170732 -0.345873 1.653061 -0.282953
2 2000-01-03 -1.734933 0.530468 2.060811 -0.515536
3 2000-01-04 -1.555121 1.452620 0.239859 -1.156896
4 2000-01-05 0.578117 0.511371 0.103552 -2.428202
5 2000-01-06 0.478344 0.449933 -0.741620 -1.962409
6 2000-01-07 1.235339 -0.091757 -1.543861 -1.084753
.. ... ... ... ... ...
993 2002-09-20 -10.628548 -9.153563 -7.883146 28.313940
994 2002-09-21 -10.390377 -8.727491 -6.399645 30.914107
995 2002-09-22 -8.985362 -8.485624 -4.669462 31.367740
996 2002-09-23 -9.558560 -8.781216 -4.499815 30.518439
997 2002-09-24 -9.902058 -9.340490 -4.386639 30.105593
998 2002-09-25 -10.216020 -9.480682 -3.933802 29.758560
999 2002-09-26 -11.856774 -10.671012 -3.216025 29.369368
[1000 rows x 5 columns]
要以第一列为 index pd.read_csv('foo.csv', index_col = 0)
HDF5¶
写入HDF5 Store:
>>> df.to_hdf('foo.h5','df')
从 HDF5 Store 读取:
>>> pd.read_hdf('foo.h5','df')
A B C D
2000-01-01 0.266457 -0.399641 -0.219582 1.186860
2000-01-02 -1.170732 -0.345873 1.653061 -0.282953
2000-01-03 -1.734933 0.530468 2.060811 -0.515536
2000-01-04 -1.555121 1.452620 0.239859 -1.156896
2000-01-05 0.578117 0.511371 0.103552 -2.428202
2000-01-06 0.478344 0.449933 -0.741620 -1.962409
2000-01-07 1.235339 -0.091757 -1.543861 -1.084753
... ... ... ... ...
2002-09-20 -10.628548 -9.153563 -7.883146 28.313940
2002-09-21 -10.390377 -8.727491 -6.399645 30.914107
2002-09-22 -8.985362 -8.485624 -4.669462 31.367740
2002-09-23 -9.558560 -8.781216 -4.499815 30.518439
2002-09-24 -9.902058 -9.340490 -4.386639 30.105593
2002-09-25 -10.216020 -9.480682 -3.933802 29.758560
2002-09-26 -11.856774 -10.671012 -3.216025 29.369368
[1000 rows x 4 columns]
Excel¶
MS Excel
写入 excel 文件:
>>> df.to_excel('foo.xlsx', sheet_name='Sheet1')
从 excel 文件读取:
>>> pd.read_excel('foo.xlsx', 'Sheet1', index_col=None, na_values=['NA'])
A B C D
2000-01-01 0.266457 -0.399641 -0.219582 1.186860
2000-01-02 -1.170732 -0.345873 1.653061 -0.282953
2000-01-03 -1.734933 0.530468 2.060811 -0.515536
2000-01-04 -1.555121 1.452620 0.239859 -1.156896
2000-01-05 0.578117 0.511371 0.103552 -2.428202
2000-01-06 0.478344 0.449933 -0.741620 -1.962409
2000-01-07 1.235339 -0.091757 -1.543861 -1.084753
... ... ... ... ...
2002-09-20 -10.628548 -9.153563 -7.883146 28.313940
2002-09-21 -10.390377 -8.727491 -6.399645 30.914107
2002-09-22 -8.985362 -8.485624 -4.669462 31.367740
2002-09-23 -9.558560 -8.781216 -4.499815 30.518439
2002-09-24 -9.902058 -9.340490 -4.386639 30.105593
2002-09-25 -10.216020 -9.480682 -3.933802 29.758560
2002-09-26 -11.856774 -10.671012 -3.216025 29.369368
[1000 rows x 4 columns]
其他东西¶
统计分析¶
列出一些常用的统计函数
In [67]: np.random.seed(1234)
In [68]: d1 = pd.Series(2*np.random.normal(size = 100)+3)
In [69]: d2 = np.random.f(2,4,size = 100)
In [70]: d3 = np.random.randint(1,100,size = 100)
In [71]: d1.count() #非空元素计算
Out[71]: 100
In [72]: d1.min() #最小值
Out[72]: -4.1270333212494705
In [73]: d1.max() #最大值
Out[73]: 7.7819210309260658
In [74]: d1.idxmin() #最小值的位置,类似于R中的which.min函数
Out[74]: 81
In [75]: d1.idxmax() #最大值的位置,类似于R中的which.max函数
Out[75]: 39
In [76]: d1.quantile(0.1) #10%分位数
Out[76]: 0.68701846440699277
In [77]: d1.sum() #求和
Out[77]: 307.0224566250874
In [78]: d1.mean() #均值
Out[78]: 3.070224566250874
In [79]: d1.median() #中位数
Out[79]: 3.204555266776845
In [80]: d1.mode() #众数
Out[80]: Series([], dtype: float64)
In [81]: d1.var() #方差
Out[81]: 4.005609378535085
In [82]: d1.std() #标准差
Out[82]: 2.0014018533355777
In [83]: d1.mad() #平均绝对偏差
Out[83]: 1.5112880411556109
In [84]: d1.skew() #偏度
Out[84]: -0.64947807604842933
In [85]: d1.kurt() #峰度
Out[85]: 1.2201094052398012
In [86]: d1.describe() #一次性输出多个描述性统计指标
Out[86]:
count 100.000000
mean 3.070225
std 2.001402
min -4.127033
25% 2.040101
50% 3.204555
75% 4.434788
max 7.781921
dtype: float64
必须注意的是,describe方法只能针对序列或 DataFrame,一维数组(numpy.ndarray)是没有这个方法的。
自定义一个函数,将这些统计描述指标全部汇总到一起:
In [87]: def stats(x):
...: return pd.Series([x.count(),x.min(),x.idxmin(),
...: x.quantile(.25),x.median(),
...: x.quantile(.75),x.mean(),
...: x.max(),x.idxmax(),
...: x.mad(),x.var(),
...: x.std(),x.skew(),x.kurt()],
...: index = ['Count','Min','Whicn_Min',
...: 'Q1','Median','Q3','Mean',
...: 'Max','Which_Max','Mad',
...: 'Var','Std','Skew','Kurt'])
In [88]: stats(d1)
Out[88]:
Count 100.000000
Min -4.127033
Whicn_Min 81.000000
Q1 2.040101
Median 3.204555
Q3 4.434788
Mean 3.070225
Max 7.781921
Which_Max 39.000000
Mad 1.511288
Var 4.005609
Std 2.001402
Skew -0.649478
Kurt 1.220109
dtype: float64
对 DataFrame 的迭代¶
按行按列迭代
DaraFrame 的迭代器方法 iterrows() 返回 index 和 行 Series 元组 iteritems() 返回 column 和 列 Seires 元组 itertuples 按行返回 namedtuple
>>> df = pd.DataFrame(np.random.randn(10, 4),index = range(10), columns = list('abcd'))
a b c d
0 -1.445993 0.913242 0.793857 -0.407082
1 -0.857645 0.468194 -2.723609 -0.256915
2 0.495262 -1.148540 0.779249 -0.970309
3 -1.205648 0.121347 0.550200 -1.099447
4 -0.284368 0.112743 -0.730803 2.301130
5 -0.540422 1.343915 -0.505066 -1.826931
6 -1.201243 0.651857 -0.488602 -0.461413
7 0.454218 0.699664 -0.254396 0.591888
8 -0.155522 -0.507313 -0.727528 1.206387
9 -0.052086 -0.687135 -3.011200 0.550724
这样迭代得到的是 columns 名称
for i in df:
print(i)
a
b
c
d
for i,row in df.iterrows():
print(i)
print(row)
0
a 0.788948
b -0.143154
c -0.156242
d 1.802068
Name: 0, dtype: float64
.....
9
a 0.319311
b 0.567725
c -1.296204
d -0.332152
Name: 9, dtype: float64
Python Careful¶
关于局部变量和全局变量¶
在函数内部或代码块内是可以访问全局变量的,但是是以只读形式访问的,若不加 global 关键字 那么是不能对其进行修改的,若对全局变量进行赋值,默认是会创建一个新的同名局部变量的
而且 在一个代码块内部,如果有新声明的新同名变量,那么就算在其前面也是不能访问到外部的 全局变量的,此时若进行访问,会报错
var = 1
def func():
print(var) # ok 1
var = 1
def func():
print(var) # not ok referenced before assignment
var = 4
var = 1
def func():
var = 4
print(var) # ok 4
var = 1
def func():
global var
print(var) # ok 1
var = 4
print(var) # ok 4
print(var) # ok 4
注意函数返回的数据类型¶
- 注意是 list 还是 生成器
- 注意是 list/tuple 还是 单个变量
Python 很注重惰性计算,所以很多函数,方法返回的是 生成器 或者 map 类型,有些时候需要 转换 为 list tuple 等再进行使用
例如 plt.plot(...) 就返回一个 plot 对象的 list, 原因大概是可以这样写 plt.plot(x, y, x, z)
单元素 list tuple 的注意事项¶
很多函数可能需要返回多个值,所以就会返回 tuple 作为数据类型,但很多时候其返回的 tuple 中又只有一个元素,此时就加逗号咯
line, = ax.plot(Data[0, 0], Data[0, 1],color = 'red',lw = 1.5, label = 'Pos')
类似这样
有时候我们也需要返回一个 tuple 但只有一个元素,所以加逗号咯
def func():
something()
return res,
python 中的代码块¶
if 语句不会产生代码块,也就是不会生成新的变量空间,这点和其他语言不太一样
数据结构和算法¶
1.1 解压序列赋值给多个变量¶
序列解包问题
有一个前提是变量数量要保持和序列数量一样。
>>> p = (4, 5)
>>> x, y = p
>>> x
4
>>> y
5
>>> data = [ 'ACME', 50, 91.1, (2012, 12, 21) ]
>>> name, shares, price, date = data
如果数量不匹配会产生异常,如果想丢弃某些值,可以用不常用的名字占位,然后丢弃即可。
实际上这种解包可以用在任何可迭代对象上,不仅仅是 list tuple,包括字符串,文件对象,迭代器生成器等。
1.2 解压可迭代对象赋值给多个变量¶
主题是在解包的时候利用 *操作符
比如这样
>>> record = ('Dave', 'dave@example.com', '773-555-1212', '847-555-1212')
>>> name, email, *phone_numbers = record
>>> name
'Dave'
>>> email
'dave@example.com'
>>> phone_numbers
['773-555-1212', '847-555-1212']
注意 无论如何 phone_numbers 解压出来都是 list 类型,就算只有 0 个元素
另外还可以利用 *解包实现递归
>>> def sum(items):
... head, *tail = items
... return head + sum(tail) if tail else head
递归并不是 Python 擅长的
1.3 保留最后 N 个元素¶
比如保留最近 5 条历史记录
解决方案,利用 collections 库中的 deque,利用 maxlen 参数设置其最大长度
from collections import deque
def search(lines, pattern, history=5):
previous_lines = deque(maxlen=history)
for li in lines:
if pattern in li:
yield li, previous_lines
previous_lines.append(li)
# Example use on a file
if __name__ == '__main__':
with open(r'../../cookbook/somefile.txt') as f:
for line, prevlines in search(f, 'python', 5):
for pline in prevlines:
print(pline, end='')
print(line, end='')
print('-' * 20)
deque 还实现了 appendleft appendright popleft popright 等。
在队列两端插入或删除元素时间复杂度都是 O(1) ,而在列表的开头插入或删除元素的时间复杂度为 O(N) 。
1.4 查找最大或最小的N 个元素¶
问题
集合里面,求最大或者最小的 N 个元素
解决
heapq模块有两个函数:nlargest() 和 nsmallest() 可以完美解决这个问题。(其实就是利用了堆这个数据结构的性质)
import heapq
nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
print(heapq.nlargest(3, nums)) # Prints [42, 37, 23]
print(heapq.nsmallest(3, nums)) # Prints [-4, 1, 2]
可以使用 key 参数,以支持更复杂的数据结构:
cheap = heapq.nsmallest(3, portfolio, key=lambda s: s['price'])
讨论
在底层实现里面,首先会先将集合数据进行堆排序后放入一个列表中:
>>> nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
>>> import heapq
>>> heapq.heapify(nums)
>>> nums
[-4, 2, 1, 23, 7, 2, 18, 23, 42, 37, 8]
堆数据结构最重要的特征是 heap[0] 永远是最小的元素。并且剩余的元素可以很容易的通过调用 heapq.heappop() 方法得到, 该方法会先将第一个元素弹出来,然后用下一个最小的元素来取代被弹出元素(这种操作时间复杂度仅仅是O(log N),N是堆大小)。
>>> heapq.heappop(nums)
-4
>>> heapq.heappop(nums)
1
>>> heapq.heappop(nums)
2
注意
如果你仅仅想查找唯一的最小或最大(N=1)的元素的话,那么使用 min() 和 max() 函数会更快些。 类似的,如果N的大小和集合大小接近的时候,通常先排序这个集合然后再使用切片操作会更快点 ( sorted(items)[:N] 或者是 sorted(items)[-N:] )。
所以使用 heap 的场景是需要的 n 不是太大也不是太小的情况。
关于 堆排序更多的细节参考排序算法中堆排序的相关知识。
1.5 实现一个优先级队列¶
问题
怎样实现一个按优先级排序的队列? 并且在这个队列上面每次pop操作总是返回优先级最高的那个元素
解决方案
一种方式是使用堆,不过每个元素不仅仅包括了值,还包括了优先级,于是堆按照优先级进行处理操作:
import heapq
class PriorityQueue:
def __init__(self):
self._queue = []
self._index = 0
def push(self, item, priority):
heapq.heappush(self._queue, (-priority, self._index, item))
self._index += 1
def pop(self):
return heapq.heappop(self._queue)[-1]
使用方式如下:
>>> class Item:
... def __init__(self, name):
... self.name = name
... def __repr__(self):
... return 'Item({!r})'.format(self.name)
...
>>> q = PriorityQueue()
>>> q.push(Item('foo'), 1)
>>> q.push(Item('bar'), 5)
>>> q.push(Item('spam'), 4)
>>> q.push(Item('grok'), 1)
>>> q.pop()
Item('bar')
>>> q.pop()
Item('spam')
>>> q.pop()
Item('foo')
>>> q.pop()
Item('grok')
注意到如果两个有着相同优先级的元素( foo 和 grok ),pop操作按照它们被插入到队列的顺序返回的。
讨论
函数 heapq.heappush() 和 heapq.heappop()分别在队列 _queue 上插入和删除第一个元素, 并且队列_queue保证第一个元素拥有最高优先级。 heappop() 函数总是返回”最小的”的元素,这就是保证队列pop操作返回正确元素的关键。 另外,push和pop操作时间复杂度为O(log N)
在上面代码中,队列包含了一个 (-priority, index, item) 的元组。 优先级为负数的目的是使得元素按照优先级从高到低排序。
index 变量的作用是保证同等优先级元素的正确排序。 通过保存一个不断增加的 index下标变量,可以确保元素按照它们插入的顺序排序。 而且,index 变量也在相同优先级元素比较的时候起到重要作用。
关于 Python 中的元祖排序,默认是按照元祖内元素排序先后进行排序的。即先比较元祖第一个元素,如果相同再比较第二个元素。如果前面的比较已经可以确定结果了,后面的比较操作就不会发生了。
如果想在多个线程中使用同一个队列,那么你需要增加适当的锁和信号量机制。
1.6 字典中的键映射多个值¶
问题
怎样实现一个键对应多个值的字典(也叫 multidict )?
解决方案
依旧是依靠字典一个键对应一个值,但是这个值可以是列表之类的,这样就实现了一个键对应多个值了。
你可以很方便的使用 collections 模块中的 defaultdict 来构造这样的字典。defaultdict 的一个特征是它会自动初始化每个 key 刚开始对应的值,所以你只需要关注添加元素操作了。比如:
from collections import defaultdict
d = defaultdict(list)
d['a'].append(1)
d['a'].append(2)
d['b'].append(4)
d = defaultdict(set)
d['a'].add(1)
d['a'].add(2)
d['b'].add(4)
讨论
创建一个多值映射字典是很简单的。但是,如果你选择自己实现的话,那么对于值的初始化可能会有点麻烦
这样
d = {}
for key, value in pairs:
if key not in d:
d[key] = []
d[key].append(value)
或者这样
d = {} # A regular dictionary
d.setdefault('a', []).append(1)
如果使用 defaultdict 的话代码就更加简洁了:
d = defaultdict(list)
for key, value in pairs:
d[key].append(value)
1.7 字典排序¶
问题
想要一个有序字典
解决方案
可以使用 collections 模块中的 OrderedDict类。 在迭代操作的时候它会保持元素被插入时的顺序:
from collections import OrderedDict
d = OrderedDict()
d['foo'] = 1
d['bar'] = 2
d['spam'] = 3
d['grok'] = 4
# Outputs "foo 1", "bar 2", "spam 3", "grok 4"
for key in d:
print(key, d[key])
当你想要构建一个将来需要序列化或编码成其他格式的映射的时候, OrderedDict 是非常有用的。 比如,你想精确控制以JSON编码后字段的顺序
讨论
OrderedDict 内部维护着一个根据键插入顺序排序的双向链表。每次当一个新的元素插入进来的时候, 它会被放到链表的尾部。对于一个已经存在的键的重复赋值不会改变键的顺序。
需要注意的是,一个 OrderedDict 的大小是一个普通字典的两倍,因为它内部维护着另外一个链表。
得仔细权衡一下是否使用 OrderedDict 带来的好处要大过额外内存消耗的影响。
1.8 字典的运算¶
问题
如何对字典进行类似于求最大值、最小值等操作?
解决方案
有两种方式:
方式一,利用 zip 把 key 和 value 进行反转,然后利用 tuple 的比较操作就好。
prices = {
'ACME': 45.23,
'AAPL': 612.78,
'IBM': 205.55,
'HPQ': 37.20,
'FB': 10.75
}
min_price = min(zip(prices.values(), prices.keys()))
# min_price is (10.75, 'FB')
max_price = max(zip(prices.values(), prices.keys()))
# max_price is (612.78, 'AAPL')
prices_sorted = sorted(zip(prices.values(), prices.keys()))
# prices_sorted is [(10.75, 'FB'), (37.2, 'HPQ'),
# (45.23, 'ACME'), (205.55, 'IBM'),
# (612.78, 'AAPL')]
注意 zip() 函数创建的是一个只能访问一次的迭代器
方式二,利用 min 或 max 的 key 参数,但是这种方法只适合只想获得值,或者获得键的情况
min(prices.values()) # Returns 10.75
max(prices, key=lambda k: prices[k]) # Returns 'AAPL'
当多个实体拥有相同的值的时候,键会决定返回结果。 比如,在执行 min() 和 max() 操作的时候,如果恰巧最小或最大值有重复的,那么拥有最小或最大键的实体会返回。
1.9 查找两字典的相同点¶
问题
在两个字典中寻找相同点(比如相同的键,相同的值等)
解决方案
可以在 keys() 或者 items() 返回的结果上执行集合操作。
a = {
'x' : 1,
'y' : 2,
'z' : 3
}
b = {
'w' : 10,
'x' : 11,
'y' : 2
}
# Find keys in common
a.keys() & b.keys() # { 'x', 'y' }
# Find keys in a that are not in b
a.keys() - b.keys() # { 'z' }
# Find (key,value) pairs in common
a.items() & b.items() # { ('y', 2) }
讨论
字典的 keys() 方法返回一个展现键集合的键视图对象。 键视图的一个很少被了解的特性就是它们也支持集合操作。
所以,如果你想对集合的键执行一些普通的集合操作,可以直接使用键视图对象而不用先将它们转换成一个set。
字典的 values() 方法也是类似,但是它并不支持集合操作。 某种程度上是因为值视图不能保证所有的值互不相同。
如果非要堆 对 values() 进行集合操作,可以先 set() 一下。
1.10 删除序列相同元素并保持顺序¶
问题
怎样在一个序列上面保持元素顺序的同时消除重复的值?
解决方案
如果序列上的值都是 hashable 类型
def dedupe(items):
seen = set()
for item in items:
if item not in seen:
yield item
seen.add(item)
如果你想消除元素不可哈希(比如 dict 类型),可以这样
def dedupe(items, key=None):
seen = set()
for item in items:
val = item if key is None else key(item)
if val not in seen:
yield item
seen.add(val)
这里的key参数指定了一个函数,将序列元素转换成 hashable 类型
>>> a = [ {'x':1, 'y':2}, {'x':1, 'y':3}, {'x':1, 'y':2}, {'x':2, 'y':4}]
>>> list(dedupe(a, key=lambda d: (d['x'],d['y'])))
[{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 2, 'y': 4}]
>>> list(dedupe(a, key=lambda d: d['x']))
[{'x': 1, 'y': 2}, {'x': 2, 'y': 4}]
讨论
如果你仅仅就是想消除重复元素,通常可以简单的构造一个集合,但是这样会打乱原序列的顺序。
1.11 命名切片¶
问题
代码中出现了一大堆硬编码切片下标,影响程序可读性
解决方案
命名切片
SHARES = slice(20, 23)
PRICE = slice(31, 37)
cost = int(record[SHARES]) * float(record[PRICE])
讨论
内置的 slice()
函数创建了一个切片对象,slice() 接受三个参数,start stop step 可以被用在任何切片允许使用的地方。比如:
>>> items = [0, 1, 2, 3, 4, 5, 6]
>>> a = slice(2, 4)
>>> items[2:4]
[2, 3]
>>> items[a]
[2, 3]
>>> items[a] = [10,11]
>>> items
[0, 1, 10, 11, 4, 5, 6]
>>> del items[a]
>>> items
[0, 1, 4, 5, 6]
如果你有一个切片对象a,你可以分别调用它的 a.start
, a.stop
, a.step
属性来获取更多的信息。比如:
>>> a = slice(5, 50, 2)
>>> a.start
5
>>> a.stop
50
>>> a.step
2
另外,你还能通过调用切片的 indices(size)
方法将它映射到一个确定大小的序列上, 这个方法返回一个三元组 (start, stop, step)
,所有值都会被合适的缩小以满足边界限制, 从而使用的时候避免出现 IndexError
异常。比如:
>>> s = 'HelloWorld'
>>> a.indices(len(s))
(5, 10, 2)
1.12 序列中出现次数最多的元素¶
问题
怎样找出一个序列中出现次数最多的元素呢?
解决方案
collections.Counter
类就是专门为这类问题而设计的,而且有 most_common()
方法直接给了你答案。
words = [
'look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes',
'the', 'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around', 'the',
'eyes', "don't", 'look', 'around', 'the', 'eyes', 'look', 'into',
'my', 'eyes', "you're", 'under'
]
from collections import Counter
word_counts = Counter(words)
# 出现频率最高的3个单词
top_three = word_counts.most_common(3)
print(top_three)
# Outputs [('eyes', 8), ('the', 5), ('look', 4)]
讨论
作为输入, Counter
对象可以接受任意的由可哈希(hashable
)元素构成的序列对象。 在底层实现上,一个 Counter
对象就是一个字典,将元素映射到它出现的次数上。
如何增加计数:
1.手动增加计数,可以简单的用加法:
word_counts[word] += 1
2.使用 update()
方法
>>> morewords = ['why','are','you','not','looking','in','my','eyes']
>>> word_counts.update(morewords)
Counter
实例一个鲜为人知的特性是可以跟数学运算操作或者说是集合操作相结合
>>> a = Counter(words)
>>> b = Counter(morewords)
>>> a
Counter({'eyes': 8, 'the': 5, 'look': 4, 'into': 3, 'my': 3, 'around': 2,
"you're": 1, "don't": 1, 'under': 1, 'not': 1})
>>> b
Counter({'eyes': 1, 'looking': 1, 'are': 1, 'in': 1, 'not': 1, 'you': 1,
'my': 1, 'why': 1})
>>> # Combine counts
>>> c = a + b
>>> c
Counter({'eyes': 9, 'the': 5, 'look': 4, 'my': 4, 'into': 3, 'not': 2,
'around': 2, "you're": 1, "don't": 1, 'in': 1, 'why': 1,
'looking': 1, 'are': 1, 'under': 1, 'you': 1})
>>> # Subtract counts
>>> d = a - b
>>> d
Counter({'eyes': 7, 'the': 5, 'look': 4, 'into': 3, 'my': 2, 'around': 2,
"you're": 1, "don't": 1, 'under': 1})
1.13 通过某个关键字排序一个字典列表¶
问题
你有一个字典列表,你想根据某个或某几个字典字段来排序这个列表。
解决方案
通过使用 operator
模块的 itemgetter
函数,可以非常容易的排序这样的数据结构
rows = [
{'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
{'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
{'fname': 'John', 'lname': 'Cleese', 'uid': 1001},
{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
]
from operator import itemgetter
rows_by_fname = sorted(rows, key=itemgetter('fname'))
itemgetter()
函数也支持多个keys,在比较的时候按顺序进行比较
rows_by_lfname = sorted(rows, key=itemgetter('lname','fname'))
讨论
operator.itemgetter()
函数的参数可以是一个字典键名称, 一个整形值或者任何能够传入一个对象的 __getitem__()
方法的值。
itemgetter()
有时候也可以用 lambda
表达式代替,感觉itemgetter()
就是一个闭包或者类
rows_by_fname = sorted(rows, key=lambda r: r['fname'])
使用 itemgetter()
方式会运行的稍微快点。因此如果你对性能要求比较高的话就使用 itemgetter()
方式。
这样的技术也同样适用于 min()
和 max()
等函数
1.14 排序不支持原生比较的对象¶
问题
你想排序类型相同的对象,但是他们不支持原生的比较操作。
解决方案
内置的 sorted()
函数有一个关键字参数 key
,可以传入一个 callable
对象给它
对于类对象一种方法是使用 operator.attrgetter()
另一种则是使用 lambda 函数
>>> from operator import attrgetter
>>> sorted(users, key=attrgetter('user_id'))
[User(3), User(23), User(99)]
class User:
def __init__(self, user_id):
self.user_id = user_id
def __repr__(self):
return 'User({})'.format(self.user_id)
def sort_notcompare():
users = [User(23), User(3), User(99)]
print(sorted(users, key=lambda u: u.user_id))
讨论
attrgetter()
函数通常会运行的快点,并且还能同时允许多个字段进行比较
1.15 通过某个字段将记录分组¶
问题
你有一个字典或者实例的序列,然后你想根据某个特定的字段比如 date
来分组迭代访问。
解决方案
itertools.groupby()
函数对于这样的数据分组操作非常实用。
rows = [
{'address': '5412 N CLARK', 'date': '07/01/2012'},
{'address': '5148 N CLARK', 'date': '07/04/2012'},
{'address': '5800 E 58TH', 'date': '07/02/2012'},
{'address': '2122 N CLARK', 'date': '07/03/2012'},
{'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'},
{'address': '1060 W ADDISON', 'date': '07/02/2012'},
{'address': '4801 N BROADWAY', 'date': '07/01/2012'},
{'address': '1039 W GRANVILLE', 'date': '07/04/2012'},
]
from operator import itemgetter
from itertools import groupby
# Sort by the desired field first
rows.sort(key=itemgetter('date'))
# Iterate in groups
for date, items in groupby(rows, key=itemgetter('date')):
print(date)
for i in items:
print(' ', i)
运行结果:
07/01/2012
{'date': '07/01/2012', 'address': '5412 N CLARK'}
{'date': '07/01/2012', 'address': '4801 N BROADWAY'}
07/02/2012
{'date': '07/02/2012', 'address': '5800 E 58TH'}
{'date': '07/02/2012', 'address': '5645 N RAVENSWOOD'}
{'date': '07/02/2012', 'address': '1060 W ADDISON'}
07/03/2012
{'date': '07/03/2012', 'address': '2122 N CLARK'}
07/04/2012
{'date': '07/04/2012', 'address': '5148 N CLARK'}
{'date': '07/04/2012', 'address': '1039 W GRANVILLE'}
讨论
groupby()
函数扫描整个序列并且查找连续相同值(或者根据指定key函数返回值相同)的元素序列。 在每次迭代的时候,它会返回一个值和一个迭代器对象, 这个迭代器对象可以生成元素值全部等于上面那个值的组中所有对象。
有两点需要注意的地方:
1.groupby()
仅仅检查连续的元素,所以事先需要根据指定的字段将数据排序
2.如果只是想根据 date
字段将数据分组到一个大的数据结构中去,并且允许随机访问, 那么你最好使用 defaultdict()
来构建一个多值字典。
from collections import defaultdict
rows_by_date = defaultdict(list)
for row in rows:
rows_by_date[row['date']].append(row)
这种方式会比先排序然后再通过 groupby()
函数迭代的方式运行得快一些。
1.16 过滤序列元素¶
问题
你有一个数据序列,想利用一些规则从中提取出需要的值或者是缩短序列
解决方案
最简单的过滤序列元素的方法就是使用列表推导。
为了避免对内存的过度消耗,可以考虑换用生成器表达式。
如果过滤规则过于复杂,则可以将过滤操作放在一个函数中,然后使用 filter()
进行过滤,注意 filter()
返回一个迭代器。
讨论
列表和生成器表达式不仅可以过滤,还可以在过滤的过程中替换数据,可以配合 python 三元表达式效果更佳。
>>> clip_neg = [n if n > 0 else 0 for n in mylist]
>>> clip_neg
[1, 4, 0, 10, 0, 2, 3, 0]
>>> clip_pos = [n if n < 0 else 0 for n in mylist]
>>> clip_pos
>>> clip_pos
[0, 0, -5, 0, -7, 0, 0, -1]
另外一个值得关注的过滤工具是 itertools.compress()
它以一个 iterable 对象和一个相对应的 Boolean 选择器序列作为输入参数。 然后输出 iterable 对象中对应选择器为 True 的元素.
addresses = [
'5412 N CLARK',
'5148 N CLARK',
'5800 E 58TH',
'2122 N CLARK',
'5645 N RAVENSWOOD',
'1060 W ADDISON',
'4801 N BROADWAY',
'1039 W GRANVILLE',
]
counts = [ 0, 3, 10, 4, 1, 7, 6, 1]
>>> from itertools import compress
>>> more5 = [n > 5 for n in counts]
>>> more5
[False, False, True, False, False, True, True, False]
>>> list(compress(addresses, more5))
['5800 E 58TH', '1060 W ADDISON', '4801 N BROADWAY']
1.17 从字典中提取子集¶
问题
根据一个字典构造新的字典
解决方案
最简单的方式是使用字典推导
p1 = {key: value for key, value in prices.items() if value > 200}
讨论
大多数情况下字典推导能做到的,通过创建一个元组序列然后把它传给 dict()
函数也能实现。
p1 = dict((key, value) for key, value in prices.items() if value > 200)
字典推导方式表意更清晰,并且实际上也会运行的更快些
1.18 映射名称到序列元素¶
问题
你有一段通过下标访问列表或者元组中元素的代码,但是这样有时候会使得你的代码难以阅读, 于是你想通过名称来访问元素。
解决方案
命名元组:collections.namedtuple() 函数通过使用一个普通的元组对象来帮你解决这个问题。
>>> from collections import namedtuple
>>> Subscriber = namedtuple('Subscriber', ['addr', 'joined'])
>>> sub = Subscriber('jonesy@example.com', '2012-10-19')
>>> sub
Subscriber(addr='jonesy@example.com', joined='2012-10-19')
>>> sub.addr
'jonesy@example.com'
>>> sub.joined
'2012-10-19'
namedtuple 看起来像一个普通的类实例,但是它跟元组类型是可交换的,支持所有的普通元组操作,比如索引和解压。
讨论
命名元组的一个优点就是作为字典的替代,因为字典存储需要更多的内存空间。 如果你需要构建一个非常大的包含字典的数据结构,那么使用命名元组会更加高效。
但是,不像字典那样,命名元组是不可更改的
如果你真的需要改变属性的值,那么**可以使用命名元组实例的 _replace() 方法**, 它会创建一个全新的命名元组并将对应的字段用新的值取代。比如:
>>> s = s._replace(shares=75)
Stock(name='ACME', shares=75, price=123.45)
一个实践
_replace() 方法还有一个很有用的特性就是当你的命名元组拥有可选或者缺失字段时候, 它是一个非常方便的填充数据的方法。 你可以先创建一个包含缺省值的原型元组,然后使用 _replace() 方法创建新的值被更新过的实例。
from collections import namedtuple
Stock = namedtuple('Stock', ['name', 'shares', 'price', 'date', 'time'])
# Create a prototype instance
stock_prototype = Stock('', 0, 0.0, None, None)
# Function to convert a dictionary to a Stock
def dict_to_stock(s):
return stock_prototype._replace(**s)
>>> a = {'name': 'ACME', 'shares': 100, 'price': 123.45}
>>> dict_to_stock(a)
Stock(name='ACME', shares=100, price=123.45, date=None, time=None)
>>> b = {'name': 'ACME', 'shares': 100, 'price': 123.45, 'date': '12/17/2012'}
>>> dict_to_stock(b)
Stock(name='ACME', shares=100, price=123.45, date='12/17/2012', time=None)
最后 如果要定义一个需要更新很多实例属性的高效数据结构,那么命名元组并不是你的最佳选择。 这时候你应该考虑定义一个包含 slots 方法的类
1.19 转换并同时计算数据¶
问题
你需要在数据序列上执行聚集函数(比如 sum() , min() , max() ), 但是首先你需要先转换或者过滤数据
解决方案
一个非常优雅的方式去结合数据计算与转换就是使用一个生成器表达式参数。
nums = [1, 2, 3, 4, 5]
s = sum(x * x for x in nums)
讨论
语法:
当生成器表达式作为一个单独参数传递给函数时候的巧妙语法(你并不需要多加一个括号)。 比如,下面这些语句是等效的:
s = sum((x * x for x in nums)) # 显式的传递一个生成器表达式对象
s = sum(x * x for x in nums) # 更加优雅的实现方式,省略了括号
使用一个生成器表达式作为参数会比先创建一个临时列表更加高效和优雅。
1.20 合并多个字典或映射¶
问题
合并多个字典或映射,之后还要对其进行操作,如何实现?
解决方案
一个非常简单的解决方案就是使用 collections 模块中的 ChainMap 类。
from collections import ChainMap
c = ChainMap(a,b)
print(c['x']) # Outputs 1 (from a)
print(c['y']) # Outputs 2 (from b)
print(c['z']) # Outputs 3 (from a)
讨论
一个 ChainMap 接受多个字典并将它们在逻辑上变为一个字典。 然后,这些字典并不是真的合并在一起了, ChainMap 类只是在内部创建了一个容纳这些字典的列表 并重新定义了一些常见的字典操作来遍历这个列表。大部分字典操作都是可以正常使用的。
如果出现重复键,那么第一次出现的映射值会被返回
对于字典的更新或删除操作总是影响的是列表中第一个字典
new_child()
parents()
以及 update()
方法
ChainMap 对于编程语言中的作用范围变量(比如 globals , locals 等)是非常有用的。 事实上,有一些方法可以使它变得简单:
>>> values = ChainMap()
>>> values['x'] = 1
>>> # Add a new mapping
>>> values = values.new_child()
>>> values['x'] = 2
>>> # Add a new mapping
>>> values = values.new_child()
>>> values['x'] = 3
>>> values
ChainMap({'x': 3}, {'x': 2}, {'x': 1})
>>> values['x']
3
>>> # Discard last mapping
>>> values = values.parents
>>> values['x']
2
>>> # Discard last mapping
>>> values = values.parents
>>> values['x']
1
>>> values
ChainMap({'x': 1})
作为 ChainMap 的替代,你可能会考虑使用 update() 方法将两个字典合并。但是它需要你创建一个完全不同的字典对象(或者是破坏现有字典结构)。ChainMap 使用原来的字典,它自己不创建新的字典。
第七章 函数¶
7.1 可接受任意数量参数的函数¶
问题
需要构造一个可接受任意数量参数的函数。
解决方案
使用*参数。
要接受任意数量的关键字参数,使用以**开头的参数。
如果希望函数能同时接受任意数量的位置参数和关键字参数,可以同时使用*
和**
讨论
一个*
参数只能出现在函数定义中最后一个位置参数后面,而 **
参数只能是最后一个参数。 要注意的是,在*
参数后面仍可以定义其他参数。
def a(x, *args, y):
pass
def b(x, *args, y, **kwargs):
pass
这种参数就是强制关键字参数。
7.2 只接受关键字参数的函数¶
问题
你希望函数的某些参数强制使用关键字参数传递
解决方案
将强制关键字参数放到某个*
参数或者单个*
后面就能达到这种效果。
def recv(maxsize, *, block):
'Receives a message'
pass
recv(1024, True) # TypeError
recv(1024, block=True) # Ok
7.3 给函数参数增加元信息¶
问题
想为这个函数的参数增加一些额外的信息,使得其他使用者知道这个函数应该怎么使用。
解决方案
使用函数参数注解
def add(x:int, y:int) -> int:
return x + y
python解释器不会对这些注解添加任何的语义,但第三方工具和框架可能会对这些注解添加语义。
讨论
函数注解只存储在函数的annotations 属性中
7.4 返回多个值的函数¶
问题
你希望构造一个可以返回多个值的函数
解决方案 函数直接返回一个元组就行了
>>> def myfun():
... return 1, 2, 3
...
>>> a, b, c = myfun()
>>> a
1
>>> b
2
>>> c
3
讨论
很多时候序列的构造和解包都是自动完成的
比如可以这样
return a,
来返回一个只有一个元素的元组
7.5 定义有默认参数的函数¶
问题
你想定义一个函数或者方法,它的一个或多个参数是可选的并且有一个默认值。
解决方案
定义一个有可选参数的函数是非常简单的,直接在函数定义中给参数指定一个默认值,并放到参数列表最后就行了。
我的理解
此处所说的参数列表最后有些不够清楚,我想这样的情况可以分为两种,第一是带默认值的位置参数,第二是带默认值的强制关键字参数。比如
def func1(a, b=1, *args, **kw):
pass
def func2(a, *args, b=1, **kw):
pass
总之无论如何 **kw 都必须在所有参数的最后
讨论
默认参数的值仅仅在函数定义的时候赋值一次其后对默认参数的修改是没有作用的
>>> x = 42
>>> def spam(a, b=x):
... print(a, b)
...
>>> spam(1)
1 42
>>> x = 23 # Has no effect
>>> spam(1)
1 42
其次默认参数的值应该是不可变的对象,比如None、True、False、数字或字符串。不要把参数默认值设为 [].
在测试 None 值时使用 is 操作符是很重要的,也是这种方案的关键点。 有时候大家会犯下下面这样的错误
def spam(a, b=None):
if not b: # NO! Use 'b is None' instead
b = []
另外还有一个比较微妙的问题,就是测试某个可选参数是否被用户传递进来的时候,仅仅通过 None 0 或者 False 来测试是远远不够的。 一般来说可以创造一个特有的值来进行测试,比如这样
_no_value = object()
def spam(a, b=_no_value):
if b is _no_value:
print('No b value supplied')
7.6 定义匿名或内联函数¶
问题
你想为 sort() 操作创建一个很短的回调函数,但又不想用 def 去写一个单行函数, 而是希望通过某个快捷方式以内联方式来创建这个函数。
解决方案
可以使用lambda表达式来代替。lambda表达式典型的使用场景是排序或数据reduce等:
>>> names = ['David Beazley', 'Brian Jones',
... 'Raymond Hettinger', 'Ned Batchelder']
>>> sorted(names, key=lambda name: name.split()[-1].lower())
讨论
尽管lambda表达式允许你定义简单函数,但是它的使用是有限制的。 你只能指定单个表达式,它的值就是最后的返回值。你不能使用复杂的条件语句,迭代以及异常等
7.7 匿名函数捕获变量值¶
问题
你用lambda定义了一个匿名函数,并想在定义时捕获到某些变量的值。
解决方案
先看下下面代码的效果:
>>> x = 10
>>> a = lambda y: x + y
>>> x = 20
>>> b = lambda y: x + y
>>> a(10)
30 # 不是 20
>>> b(10)
30
这其中的奥妙在于lambda表达式中的 x 是一个自由变量, 在运行时绑定值,而不是定义时就绑定,这跟函数的默认值参数定义是不同的。 因此,在调用这个lambda表达式的时候,x的值是执行时的值。
如果你想让某个匿名函数在定义时就捕获到值,可以将那个参数值定义成默认参数,就像下面这样:
>>> x = 10
>>> a = lambda y, x=x: x + y
>>> x = 20
>>> b = lambda y, x=x: x + y
>>> a(10)
20
>>> b(10)
30
讨论
有些新手可能会不恰当的使用lambda表达式。 比如,通过在一个循环或列表推导中创建一个lambda表达式列表,并期望函数能在定义时就记住每次的迭代值。例如:
>>> funcs = [lambda x: x+n for n in range(5)]
>>> for f in funcs:
... print(f(0))
...
4
4
4
4
4
但是实际效果是运行是n的值为迭代的最后一个值。 通过使用函数默认值参数形式,lambda函数在定义时就能绑定到值。
>>> funcs = [lambda x, n=n: x+n for n in range(5)]
>>> for f in funcs:
... print(f(0))
...
0
1
2
3
4
7.8 减少可调用对象的参数个数¶
问题
你有一个被其他python代码使用的callable对象,可能是一个回调函数或者是一个处理器, 但是它的参数太多了,导致调用时出错。
解决方案
如果需要减少某个函数的参数个数,你可以使用 functools.partial() 。 partial() 函数允许你给一个或多个参数设置固定的值,减少接下来被调用时的参数个数。
def spam(a, b, c, d):
print(a, b, c, d)
使用 partial() 函数来固定某些参数值:
>>> from functools import partial
>>> s1 = partial(spam, 1) # a = 1
>>> s1(2, 3, 4)
1 2 3 4
>>> s1(4, 5, 6)
1 4 5 6
>>> s2 = partial(spam, d=42) # d = 42
>>> s2(1, 2, 3)
1 2 3 42
>>> s2(4, 5, 5)
4 5 5 42
>>> s3 = partial(spam, 1, 2, d=42) # a = 1, b = 2, d = 42
>>> s3(3)
1 2 3 42
可以看出 partial() 固定某些参数并返回一个新的callable对象。这个新的callable接受未赋值的参数, 然后跟之前已经赋值过的参数合并起来,最后将所有参数传递给原始函数。
讨论
本节要解决的问题是让原本不兼容的代码可以一起工作。
partial() 通常被用来微调其他库函数所使用的回调函数的参数。
例如,下面是一段代码,使用 multiprocessing 来异步计算一个结果值, 然后这个值被传递给一个接受一个result值和一个可选logging参数的回调函数:
def output_result(result, log=None):
if log is not None:
log.debug('Got: %r', result)
# A sample function
def add(x, y):
return x + y
if __name__ == '__main__':
import logging
from multiprocessing import Pool
from functools import partial
logging.basicConfig(level=logging.DEBUG)
log = logging.getLogger('test')
p = Pool()
p.apply_async(add, (3, 4), callback=partial(output_result, log=log))
p.close()
p.join()
当给 apply_async() 提供回调函数时,通过使用 partial() 传递额外的 logging 参数。 而 multiprocessing 对这些一无所知——它仅仅只是使用单个值来调用回调函数。
很多时候 partial() 能实现的效果,lambda表达式也能实现。只是稍显臃肿
points.sort(key=lambda p: distance(pt, p))
p.apply_async(add, (3, 4), callback=lambda result: output_result(result,log))
serv = TCPServer(('', 15000),
lambda *args, **kwargs: EchoHandler(*args, ack=b'RECEIVED:',␣
,!**kwargs))
7.9 将单方法的类转换为函数¶
问题
你有一个除 init() 方法外只定义了一个方法的类。为了简化代码,你想将它转换成一个函数。
解决方案
大多数情况下,可以使用闭包来将单个方法的类转换成函数。 举个例子,下面示例中的类允许使用者根据某个模板方案来获取到URL链接地址。
from urllib.request import urlopen
class UrlTemplate:
def __init__(self, template):
self.template = template
def open(self, **kwargs):
return urlopen(self.template.format_map(kwargs))
# Example use. Download stock data from yahoo
yahoo = UrlTemplate('http://finance.yahoo.com/d/quotes.csv?s={names}&f={fields}')
for line in yahoo.open(names='IBM,AAPL,FB', fields='sl1c1v'):
print(line.decode('utf-8'))
这个类可以被一个更简单的函数来代替:
def urltemplate(template):
def opener(**kwargs):
return urlopen(template.format_map(kwargs))
return opener
# Example use
yahoo = urltemplate('http://finance.yahoo.com/d/quotes.csv?s={names}&f={fields}')
for line in yahoo(names='IBM,AAPL,FB', fields='sl1c1v'):
print(line.decode('utf-8'))
讨论
大部分情况下,你拥有一个单方法类的原因是需要存储某些额外的状态来给方法使用。
使用一个内部函数或者闭包的方案通常会更优雅一些。简单来讲,一个闭包就是一个函数, 只不过在函数内部带上了一个额外的变量环境。闭包关键特点就是它会记住自己被定义时的环境
7.10 带额外状态信息的回调函数¶
问题
你的代码中需要依赖到回调函数的使用(比如事件处理器、等待后台任务完成后的回调等), 并且你还需要让回调函数拥有额外的状态值,以便在它的内部使用到。
解决方案
这一小节主要讨论的是那些出现在很多函数库和框架中的回调函数的使用——特别是跟异步处理有关的。
为了让回调函数访问外部信息
第一种方法是使用一个绑定方法来代替一个简单函数。 比如,下面这个类会保存一个内部序列号,每次接收到一个 result 的时候序列号加1:
class ResultHandler:
def __init__(self):
self.sequence = 0
def handler(self, result):
self.sequence += 1
print('[{}] Got: {}'.format(self.sequence, result))
使用
>>> r = ResultHandler()
>>> apply_async(add, (2, 3), callback=r.handler)
[1] Got: 5
>>> apply_async(add, ('hello', 'world'), callback=r.handler)
[2] Got: helloworld
第二种方式,使用闭包
def make_handler():
sequence = 0
def handler(result):
nonlocal sequence
sequence += 1
print('[{}] Got: {}'.format(sequence, result))
return handler
使用
>>> handler = make_handler()
>>> apply_async(add, (2, 3), callback=handler)
[1] Got: 5
>>> apply_async(add, ('hello', 'world'), callback=handler)
[2] Got: helloworld
第三种方法,一种更加高级的方法,使用协程
def make_handler():
sequence = 0
while True:
result = yield
sequence += 1
print('[{}] Got: {}'.format(sequence, result))
记住要使用协程的 send() 方法作为回调函数
>>> handler = make_handler()
>>> next(handler) # Advance to the yield
>>> apply_async(add, (2, 3), callback=handler.send)
[1] Got: 5
>>> apply_async(add, ('hello', 'world'), callback=handler.send)
[2] Got: helloworld
7.11 内联回调函数¶
本节需要认真理解
问题
当你编写使用回调函数的代码的时候,担心很多小函数的扩张可能会弄乱程序控制流。你希望找到某个方法来让代码看上去更像是一个普通的执行序列。
解决方案
通过使用生成器和协程可以使得回调函数内联在某个函数中。
假设有需求
def apply_async(func, args, *, callback):
# Compute the result
result = func(*args)
# Invoke the callback with the result
callback(result)
下面的代码,包含了一个 Async 类和一个 inlined_async 装饰器:
from queue import Queue
from functools import wraps
class Async:
def __init__(self, func, args):
self.func = func
self.args = args
def inlined_async(func):
@wraps(func)
def wrapper(*args):
f = func(*args)
result_queue = Queue()
result_queue.put(None)
while True:
result = result_queue.get()
try:
a = f.send(result)
apply_async(a.func, a.args, callback=result_queue.put)
except StopIteration:
break
return wrapper
这两个代码片段允许你使用 yield 语句内联回调步骤。比如:
def add(x, y):
return x + y
@inlined_async
def test():
r = yield Async(add, (2, 3))
print(r)
r = yield Async(add, ('hello', 'world'))
print(r)
for n in range(10):
r = yield Async(add, (n, n))
print(r)
print('Goodbye')
>>> test()
5
helloworld
0
2468
10
12
14
16
18
Goodbye
首先,在需要使用到回调的代码中,关键点在于当前计算工作会挂起并在将来的某个时候重启(比如异步执行)。当计算重启时,回调函数被调用来继续处理结果。 apply_async() 函数演示了执行回调的实际逻辑,尽管实际情况中它可能会更加复杂(包括线程、进程、事件处理器等等)。计算的暂停与重启思路跟生成器函数的执行模型不谋而合。
具体来讲,yield 操作会使一个生成器函数产生一个值并暂停。接下来调用生成器的 next() 或 send() 方法又会让它从暂停处继续执行。根据这个思路,这一小节的核心就在inline_async() 装饰器函数中了。
关键点就是,装饰器会逐步遍历生成器函数的所有yield 语句,每一次一个。为了这样做,刚开始的时候创建了一个result 队列并向里面放入一个None 值。然后开始一个循环操作,从队列中取出结果值并发送给生成器,它会持续到下一个yield 语句,在这里一个Async 的实例被接受到。然后循环开始检查函数和参数,并开始进行异步计算 apply_async() 。然而,这个计算有个最诡异部分是它并没有使用一个普通的回调函数,而是用队列的put() 方法来回调。这时候,是时候详细解释下到底发生了什么了。主循环立即返回顶部并在队列上执行get() 操作。如果数据存在,它一定是put() 回调存放的结果。如果没有数据,那么先暂停操作并等待结果的到来。这个具体怎样实现是由apply_async() 函数来决定的
7.12 访问闭包中定义的变量¶
问题
你想要扩展函数中的某个闭包,允许它能访问和修改函数的内部变量。
解决方案
通常来讲,闭包的内部变量对于外界来讲是完全隐藏的。但是,你可以通过编写访问函数并将其作为函数属性绑定到闭包上来实现这个目的
def sample():
n = 0
# Closure function
def func():
print('n=', n)
# Accessor methods for n
def get_n():
return n
def set_n(value):
nonlocal n
n = value
# Attach as function attributes
func.get_n = get_n
func.set_n = set_n
return func
使用
>>> f = sample()
>>> f()
n= 0
>>> f.set_n(10)
>>> f()
n= 10
>>> f.get_n()
10
讨论
首先,nonlocal 声明可以让我们编写函数来修改内部变量的值。 其次,函数属性允许我们用一种很简单的方式将访问方法绑定到闭包函数上,这个跟实例方法很像(尽管并没有定义任何类)。
还可以进一步的扩展,让闭包模拟类的实例。
import sys
class ClosureInstance:
def __init__(self, locals=None):
if locals is None:
locals = sys._getframe(1).f_locals
# Update instance dictionary with callables
self.__dict__.update((key,value) for key, value in locals.items()
if callable(value) )
# Redirect special methods
def __len__(self):
return self.__dict__['__len__']()
# Example use
def Stack():
items = []
def push(item):
items.append(item)
def pop():
return items.pop()
def __len__():
return len(items)
return ClosureInstance()
代码中的 sys._getframe(1).f_locals 是获得当前栈中上一层函数的方法。也可以指定 flocals 关键字参数。 工作
>>> s = Stack()
>>> s
<__main__.ClosureInstance object at 0x10069ed10>
>>> s.push(10)
>>> s.push(20)
>>> s.push('Hello')
>>> len(s)
3
>>> s.pop()
'Hello'
上面代码的作用和类其实已经很像了,但是经过测试这样的代码会比类要快一些,大概是因为不会涉及到额外的 self 变量
迭代器与生成器¶
关注 4.4 4.13
4.1 手动遍历迭代器¶
问题
不使用 for
循环来实现迭代
解决方案
使用 next()
函数并在代码中捕获 StopIteration 异常。
通常来讲, StopIteration 用来指示迭代的结尾。不过也可以通过返回一个指定值来标记结尾,比如 None 。
def manual_iter():
with open('/etc/passwd') as f:
try:
while True:
line = next(f)
print(line, end='')
except StopIteration:
pass
# same 指定迭代结束后返回什么
with open('/etc/passwd') as f:
while True:
line = next(f, None) # here
if line is None:
break
print(line, end='')
讨论
迭代期间的基本细节:
>>> items = [1, 2, 3]
>>> # Get the iterator
>>> it = iter(items) # Invokes items.__iter__()
>>> # Run the iterator
>>> next(it) # Invokes it.__next__()
1
>>> next(it)
2
>>> next(it)
3
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
4.2 代理迭代¶
问题
想在自定义的容器对象里执行迭代操作
解决方案
实际上你只需要定义一个 iter() 方法,将迭代操作代理到容器内部的对象上去。比如:
class Node:
def __init__(self, value):
self._value = value
self._children = []
def __repr__(self):
return 'Node({!r})'.format(self._value)
def add_child(self, node):
self._children.append(node)
def __iter__(self):
return iter(self._children)
# Example
if __name__ == '__main__':
root = Node(0)
child1 = Node(1)
child2 = Node(2)
root.add_child(child1)
root.add_child(child2)
# Outputs Node(1), Node(2)
for ch in root:
print(ch)
讨论
Python的迭代器协议需要 iter() 方法返回一个实现了 next() 方法的迭代器对象。
iter(s) 只是简单的通过调用 s.iter() 方法来返回对应的迭代器对象。就像 len(s) 只是调用了 s.len() 一样。
4.3 使用生成器创建新的迭代模式¶
问题
想自定义迭代模式
解决方案
如果你想实现一种新的迭代模式,使用一个生成器函数来定义它。
def frange(start, stop, increment):
x = start
while x < stop:
yield x
x += increment
讨论
一个函数中需要有一个 yield 语句即可将其转换为一个生成器。
一个生成器函数主要特征是它只会回应在迭代中使用到的 next 操作。 一旦生成器函数返回退出,迭代终止。
另外,生成器只能使用一次,即迭代完了就无法恢复最初的状态,但是生成器函数是可以多次使用的,比如
def gen(n):
for i in range(n):
yield i
for i in gen(3):
print(i)
for i in gen(3):
print(i)
0
1
2
0
1
2
```python
def gen(n):
for i in range(n):
yield i
c = gen(3)
for i in c:
print(i)
for i in c:
print(i)
0
1
2
后一种情况下,生成器只运行了一次
4.4 实现迭代器协议¶
问题
想构建一个能支持迭代操作的自定义对象,并希望找到一个能实现迭代协议的简单方法。
解决方案
目前为止,在一个对象上实现迭代最简单的方式是使用一个生成器函数。
实现一个以深度优先方式遍历树形节点的生成器。 下面是代码示例:
class Node:
def __init__(self, value):
self._value = value
self._children = []
def __repr__(self):
return 'Node({!r})'.format(self._value)
def add_child(self, node):
self._children.append(node)
def __iter__(self):
return iter(self._children)
def depth_first(self):
yield self
for c in self:
yield from c.depth_first()
# Example
if __name__ == '__main__':
root = Node(0)
child1 = Node(1)
child2 = Node(2)
root.add_child(child1)
root.add_child(child2)
child1.add_child(Node(3))
child1.add_child(Node(4))
child2.add_child(Node(5))
for ch in root.depth_first():
print(ch)
# Outputs Node(0), Node(1), Node(3), Node(4), Node(2), Node(5)
在这段代码中,depth_first() 方法简单直观。 它首先返回自己本身并迭代每一个子节点并通过调用子节点的 depth_first() 方法(使用 yield from 语句)返回对应元素。
讨论
使用一个关联迭代器类重新实现 depth_first() 方法:
class Node2:
def __init__(self, value):
self._value = value
self._children = []
def __repr__(self):
return 'Node({!r})'.format(self._value)
def add_child(self, node):
self._children.append(node)
def __iter__(self):
return iter(self._children)
def depth_first(self):
return DepthFirstIterator(self)
class DepthFirstIterator(object):
'''
Depth-first traversal
'''
def __init__(self, start_node):
self._node = start_node
self._children_iter = None
self._child_iter = None
def __iter__(self):
return self
def __next__(self):
# Return myself if just started; create an iterator for children
if self._children_iter is None:
self._children_iter = iter(self._node)
return self._node
# If processing a child, return its next item
elif self._child_iter:
try:
nextchild = next(self._child_iter)
return nextchild
except StopIteration:
self._child_iter = None
return next(self)
# Advance to the next child and start its iteration
else:
self._child_iter = next(self._children_iter).depth_first()
return next(self)
DepthFirstIterator 类和上面使用生成器的版本工作原理类似, 但是它写起来很繁琐,因为必须在迭代处理过程中维护大量的状态信息
4.5 反向迭代¶
问题
反方向迭代一个序列
解决方案
使用内置的 reversed() 函数,比如:
>>> a = [1, 2, 3, 4]
>>> for x in reversed(a):
... print(x)
反向迭代仅仅当对象的大小可预先确定或者对象实现了 __reversed__() 的特殊方法时才能生效。 如果两者都不符合,那你必须先将对象转换为一个列表才行,比如:
# Print a file backwards
f = open('somefile')
for line in reversed(list(f)):
print(line, end='')
要注意的是如果可迭代对象元素很多的话,将其预先转换为一个列表要消耗大量的内存。
讨论
很多人都可以通过在自定义类上实现 reversed() 方法来实现反向迭代。比如:
class Countdown:
def __init__(self, start):
self.start = start
# Forward iterator
def __iter__(self):
n = self.start
while n > 0:
yield n
n -= 1
# Reverse iterator
def __reversed__(self):
n = 1
while n <= self.start:
yield n
n += 1
4.6 带有外部状态的生成器函数¶
问题
定义一个生成器函数,但是它会调用某个你想暴露给用户使用的外部状态值。
解决方案
如果你想让你的生成器暴露外部状态给用户, 别忘了你可以简单的将它实现为一个类,然后把生成器函数放到 iter() 方法中过去。比如:
from collections import deque
class linehistory:
def __init__(self, lines, histlen=3):
self.lines = lines
self.history = deque(maxlen=histlen)
def __iter__(self):
for lineno, line in enumerate(self.lines, 1):
self.history.append((lineno, line))
yield line
def clear(self):
self.history.clear()
可以将它当做是一个普通的生成器函数,由于可以创建一个实例对象,于是你可以访问内部属性值, 比如 history 属性或者是 clear() 方法
with open('somefile.txt') as f:
lines = linehistory(f)
for line in lines:
if 'python' in line:
for lineno, hline in lines.history:
print('{}:{}'.format(lineno, hline), end='')
讨论
如果生成器函数需要跟你的程序其他部分打交道的话(比如暴露属性值,允许通过方法调用来控制等等), 可能会导致你的代码异常的复杂。 如果是这种情况的话,可以考虑使用上面介绍的定义类的方式。 在 iter() 方法中定义你的生成器不会改变你任何的算法逻辑。 由于它是类的一部分,所以允许你定义各种属性和方法来供用户使用。
当基于类实现迭代时, for 循环可以直接使用 iter() 方法,如果要使用 next() 函数必须通过 iter() 以调用 iter() 方法。
4.7 迭代器切片¶
问题
想得到一个由迭代器生成的切片对象,但是标准切片操作并不能做到。
解决方案
函数 itertools.islice() 正好适用于在迭代器和生成器上做切片操作。比如:
>>> def count(n):
... while True:
... yield n
... n += 1
...
>>> c = count(0)
>>> c[10:20]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'generator' object is not subscriptable
>>> # Now using islice()
>>> import itertools
>>> for x in itertools.islice(c, 10, 20):
... print(x)
...
10
11
12
# xxx
讨论
迭代器和生成器不能使用标准的切片操作,因为它们的长度事先我们并不知道(并且也没有实现索引)。 函数 islice() 返回一个可以生成指定元素的迭代器,它通过遍历并丢弃直到切片开始索引位置的所有元素。 然后才开始一个个的返回元素,并直到切片结束索引位置。
如果需要再次使用迭代器中的值,需要用 list 等容器保存起来。
4.8 跳过可迭代对象的开始部分¶
问题
想遍历一个可迭代对象,但是它开始的某些元素你并不感兴趣,想跳过它们。
解决方案
itertools 模块中有一些函数可以完成这个任务。 首先介绍的是 itertools.dropwhile()函数。使用时,你给它传递一个函数对象和一个可迭代对象。 它会返回一个迭代器对象,丢弃原有序列中直到函数返回Flase之前的所有元素,然后返回后面所有元素
读取一个开始部分是几行注释的源文件。比如:
>>> from itertools import dropwhile
>>> with open('/etc/passwd') as f:
... for line in dropwhile(lambda line: line.startswith('#'), f):
... print(line, end='')
如果你已经明确知道了要跳过的元素的个数的话,那么可以使用 itertools.islice() 来代替。比如:
讨论
跳过一个可迭代对象的开始部分跟通常的过滤是不同的,dropwhile 在跳过开头的注释后就不再关注后面是否有注释行了。
本节的方案适用于所有可迭代对象,包括那些事先不能确定大小的, 比如生成器,文件及其类似的对象。
4.9 排列组合的迭代¶
问题
想迭代遍历一个集合中元素的所有可能的排列或组合
解决方案
itertools模块提供了三个函数来解决这类问题。
- itertools.permutations(items, len = None) 返回一个元组序列,每个元组是集合中所有元素的一个可能排列,可以指定排列长度
- itertools.combinations(items, len = None) 返回输入集合中元素的所有的组合
- itertools.combinations_with_replacement(items, len = None) 允许同一个元素被选择多次
>>> items = ['a', 'b', 'c']
>>> from itertools import permutations
>>> for p in permutations(items, 2):
... print(p)
...
('a', 'b')
('a', 'c')
('b', 'a')
('b', 'c')
('c', 'a')
('c', 'b')
>>> from itertools import combinations
>>> for c in combinations(items, 2):
... print(c)
...
('a', 'b')
('a', 'c')
('b', 'c')
>>> for c in combinations_with_replacement(items, 3):
... print(c)
...
('a', 'a', 'a')
('a', 'a', 'b')
('a', 'a', 'c')
('a', 'b', 'b')
('a', 'b', 'c')
('a', 'c', 'c')
('b', 'b', 'b')
('b', 'b', 'c')
('b', 'c', 'c')
('c', 'c', 'c')
4.10 序列上索引值迭代¶
问题
想在迭代一个序列的同时跟踪正在被处理的元素索引。
解决方案
内置的 enumerate() 函数可以很好的解决这个问题:enumerate(iterable, start=0)
start 为第一个标号
>>> for idx, val in enumerate(my_list, 1)
小陷阱
data = [ (1, 2), (3, 4), (5, 6), (7, 8) ]
# Correct!
for n, (x, y) in enumerate(data):
...
# Error!
for n, x, y in enumerate(data):
4.11 同时迭代多个序列¶
问题
想同时迭代多个序列,每次分别从一个序列中取一个元素。
解决方案
- zip(*iterables) Returns an iterator of tuples 返回迭代器
- zip_longest method from itertools
为了同时迭代多个序列,使用 zip() 函数。比如:
>>> xpts = [1, 5, 4, 2, 10, 7]
>>> ypts = [101, 78, 37, 15, 62, 99]
>>> for x, y in zip(xpts, ypts):
... print(x,y)
zip(a, b) 会生成一个可返回元组 (x, y) 的迭代器,其中x来自a,y来自b。
迭代长度跟参数中最短序列长度一致。
如果这个不是你想要的效果,那么还可以使用 itertools.zip_longest() 函数来代替。
>>> from itertools import zip_longest
>>> for i in zip_longest(a,b):
... print(i)
...
(1, 'w')
(2, 'x')
(3, 'y')
(None, 'z')
>>> for i in zip_longest(a, b, fillvalue=0):
... print(i)
...
(1, 'w')
(2, 'x')
(3, 'y')
(0, 'z')
讨论
使用zip()可以打包并生成一个字典:s = dict(zip(headers,values))
zip() 可以接受多于两个的序列的参数
4.12 不同集合上元素的迭代¶
问题
想在多个对象执行相同的操作,但是这些对象在不同的容器中,你希望代码在不失可读性的情况下避免写重复的循环。
解决方案
itertools.chain() 方法可以用来简化这个任务。 它接受一个可迭代对象列表作为输入,并返回一个迭代器,有效的屏蔽掉在多个容器中迭代细节。
>>> from itertools import chain
>>> a = [1, 2, 3, 4]
>>> b = ['x', 'y', 'z']
>>> for x in chain(a, b):
... print(x)
...
1
2
3
4
x
y
z
讨论
itertools.chain() 接受一个或多个可迭代对象最为输入参数。 然后创建一个迭代器,依次连续的返回每个可迭代对象中的元素,节省内存。
4.13 创建数据处理管道¶
问题
想以数据管道(类似Unix管道)的方式迭代处理数据。 比如,你有个大量的数据需要处理,但是不能将它们一次性放入内存中。
解决方案
生成器函数是一个实现管道机制的好办法。
假定你要处理一个非常大的日志文件目录:
foo/ access-log-012007.gz access-log-022007.gz access-log-032007.gz ... access-log-012008 bar/ access-log-092007.bz2 ... access-log-022008 假设每个日志文件包含这样的数据:
124.115.6.12 - - [10/Jul/2012:00:18:50 -0500] "GET /robots.txt ..." 200 71 210.212.209.67 - - [10/Jul/2012:00:18:51 -0500] "GET /ply/ ..." 200 11875 210.212.209.67 - - [10/Jul/2012:00:18:51 -0500] "GET /favicon.ico ..." 404 369 61.135.216.105 - - [10/Jul/2012:00:20:04 -0500] "GET /blog/atom.xml ..." 304 - ...
为了处理这些文件,你可以定义一个由多个执行特定任务独立任务的简单生成器函数组成的容器。就像这样:
import os
import fnmatch
import gzip
import bz2
import re
def gen_find(filepat, top):
'''
Find all filenames in a directory tree that match a shell wildcard pattern
'''
for path, dirlist, filelist in os.walk(top):
for name in fnmatch.filter(filelist, filepat):
yield os.path.join(path,name)
def gen_opener(filenames):
'''
Open a sequence of filenames one at a time producing a file object.
The file is closed immediately when proceeding to the next iteration.
'''
for filename in filenames:
if filename.endswith('.gz'):
f = gzip.open(filename, 'rt')
elif filename.endswith('.bz2'):
f = bz2.open(filename, 'rt')
else:
f = open(filename, 'rt')
yield f
f.close()
def gen_concatenate(iterators):
'''
Chain a sequence of iterators together into a single sequence.
'''
for it in iterators:
yield from it
def gen_grep(pattern, lines):
'''
Look for a regex pattern in a sequence of lines
'''
pat = re.compile(pattern)
for line in lines:
if pat.search(line):
yield line
现在你可以很容易的将这些函数连起来创建一个处理管道。 比如,为了查找包含单词python的所有日志行,你可以这样做:
lognames = gen_find('access-log*', 'www')
files = gen_opener(lognames)
lines = gen_concatenate(files)
pylines = gen_grep('(?i)python', lines)
for line in pylines:
print(line)
如果将来的时候你想扩展管道,你甚至可以在生成器表达式中包装数据。
lognames = gen_find('access-log*', 'www')
files = gen_opener(lognames)
lines = gen_concatenate(files)
pylines = gen_grep('(?i)python', lines)
bytecolumn = (line.rsplit(None,1)[1] for line in pylines)
bytes = (int(x) for x in bytecolumn if x != '-')
print('Total', sum(bytes))
讨论
以管道方式处理数据可以用来解决各类其他问题,包括解析,读取实时数据,定时轮询等。
这种方式一个非常好的特点是每个生成器函数很小并且都是独立的。这样的话就很容易编写和维护它们了。 很多时候,这些函数如果比较通用的话可以在其他场景重复使用。 并且最终将这些组件组合起来的代码看上去非常简单,也很容易理解。
事实上,由于使用了迭代方式处理,代码运行过程中只需要很小很小的内存。
在调用 gen_concatenate()
函数的时候你可能会有些不太明白。 这个函数的目的是将输入序列拼接成一个很长的行序列。itertools.chain()
函数同样有类似的功能,但是它需要将所有可迭代对象最为参数传入。在上面这个例子中,你可能会写类似这样的语句lines = itertools.chain(*files)
,这将导致 gen_opener()
生成器被提前全部消费掉。但由于 gen_opener()
生成器每次生成一个打开过的文件,等到下一个迭代步骤时文件就关闭了,因此 chain()
在这里不能这样使用,上面的方案可以避免这种情况。
gen_concatenate() 函数中出现过 yield from 语句,它将 yield 操作代理到父生成器上去。 语句 yield from it 简单的返回生成器 it 所产生的所有值。
管道方式并不是万能的。有时候你想立即处理所有数据。即便如此,使用生成器管道也可以将这类问题从逻辑上变为工作流的处理方式。
4.14 展开嵌套的序列¶
问题
想将一个多层嵌套的序列展开成一个单层列表
解决方案
可以写一个包含 yield from 语句的递归生成器来轻松解决这个问题。比如:
from collections import Iterable
def flatten(items, ignore_types=(str, bytes)):
for x in items:
if isinstance(x, Iterable) and not isinstance(x, ignore_types):
yield from flatten(x)
else:
yield x
items = [1, 2, [3, 4, [5, 6], 7], 8]
# Produces 1 2 3 4 5 6 7 8
for x in flatten(items):
print(x)
讨论
语句 yield from 在你想在生成器中调用其他生成器作为子例程的时候非常有用。 如果不使用的话,那就必须再写一个 for 循环了。
yield from 在涉及到基于协程和生成器的并发编程中扮演着更加重要的角色
4.15 顺序迭代合并后的排序迭代对象¶
问题
有一系列排序序列,想将它们合并后得到一个排序序列并在上面迭代遍历。
解决方案
heapq.merge() 函数可以帮你解决这个问题。比如:
>>> import heapq
>>> a = [1, 4, 7, 10]
>>> b = [2, 5, 6, 11]
>>> for c in heapq.merge(a, b):
... print(c)
...
1
2
4
5
6
7
10
11
讨论
heapq.merge 返回一个迭代器而不是列表
有一点要强调的是** heapq.merge() 需要所有输入序列必须是排过序的,它并不会预先读取所有数据到堆栈中或者预先排序**,仅仅是检查所有序列的开始部分并返回最小的那个,这个过程一直会持续到所有输入序列中的元素都被遍历完。
4.16 迭代器代替 while 循环¶
问题
在代码中使用 while 循环来迭代处理数据,因为它需要调用某个函数或者和一般迭代模式不同的测试条件。 能不能用迭代器来重写这个循环呢?
解决方案
一个常见的IO操作程序可能会像下面这样:
CHUNKSIZE = 8192
def reader(s):
while True:
data = s.recv(CHUNKSIZE)
if data == b'':
break
process_data(data)
这种代码通常可以使用 iter() 来代替,如下所示:
def reader2(s):
for chunk in iter(lambda: s.recv(CHUNKSIZE), b''):
pass
# process_data(data)
讨论
iter 函数一个鲜为人知的特性是它接受一个可选的 callable 对象和一个标记(结尾)值作为输入参数。 当以这种方式使用的时候,它会创建一个迭代器, 这个迭代器会不断调用 callable 对象直到返回值和标记值相等为止。这种特殊的方法对于一些特定的会被重复调用的函数很有效果,比如涉及到I/O调用的函数。 举例来讲,如果你想从套接字或文件中以数据块的方式读取数据,通常你得要不断重复的执行 read() 或 recv() , 并在后面紧跟一个文件结尾测试来决定是否终止。这节中的方案使用一个简单的 iter() 调用就可以将两者结合起来了
Python Tricks¶
Table of Contents¶
- Table of Contents
- Reference
- 变量交换
- 元素遍历
- 字符串连接
- 上下文管理器
- 使用生成式
- 使用装饰器
- 使用 collections 库
- 序列解包
- 遍历字典 key 和 value
- 链式比较操作
- if else 三目运算
- 真值判断
- for else 语句
- 字符串格式化
- 切片
- 使用生成器
- 获取字典元素的 get 方法
- 预设字典返回的默认值
- 函数参数 unpack
- 注意函数默认参数
- 为 dict 添加missing方法
- python 解释器中的 _
- Zen
- try/except/else
- print 重定向输出到文件
- 省略号
- pow 还有第三个参数
- isinstance可以接收一个元组
- 字典的无限递归
- Python可以认识Unicode中的数字
- 不能访问到的属性
- 使用计数器对象进行计数
- 漂亮的打印 JSON
- 字典的剧本
- 无限循环的结束
- 序列乘法
- here starts my collection
- 或的另一种写法
变量交换¶
python 中交换两个变量不需要引入临时变量
a = 1
b = 2
a, b = b, a
>>> a
2
>>> b
1
由此自然想到三个变量的交换 答案是也可以。
a = 1
b = 2
c = 3
a, b, c = c, a, b
>>> a
3
>>> b
1
>>> c
2
元素遍历¶
普通的遍历,类似于其他语言中的 for each 或者是基于范围的 for 循环
for i in range(5):
print(i)
带索引的容器遍历,使用 enumerate
colors = ['red', 'green', 'blue', 'yellow']
for index, color in enumerate(colors):
print('%d, %s' %(index, color))
事实上,enumerate 还接受第二个参数,用于指定 index 的起始位置,默认为 0
>>> lst = ["a", "b", "c"]
>>> list(enumerate(lst, 1))
[(1, 'a'), (2, 'b'), (3, 'c')]
字符串连接¶
names = ['raymond', 'rachel', 'matthew', 'roger',
'betty', 'melissa', 'judith', 'charlie']
print (', '.join(names))
实际上,经常使用的 字符串连接方法有三种,join format 和 + 操作符 在需要连接的字符串个数较少的情况下,+ 的效率有些许优势,随着个数的增加,join 的优势会迅速显现
上下文管理器¶
with open('data.txt') as f:
data = f.read()
使用生成式¶
最常见的是列表推导式
[2*i for i in range(10)]
其次还有:集合推导式和字典推导式,原先在 python2 版本中的 set 是需要通过 set 函数来显示转换的, 但后来 python3 中直接进行 set 推导的语法也被移植到了 python2 中。不过要注意的是 () 定义的不是元组生成式而是生成器
numbers = [1, 2, 3]
my_dict = {number: number * 2 for number in numbers if number > 1}
print(my_dict)
推导式可以嵌套,而且可以加入一定的条件机制,各个条件之间的关系是 与 。
results = [(x, y)
for x in range(10)
for y in range(10)
if x + y == 5
if x > y]
>>> results
[(3, 2), (4, 1), (5, 0)]
甚至可以利用列表生成式来实现快速排序
def quicksort(lst):
if len(lst) == 0:
return []
else:
return quicksort([x for x in lst[1:] if x < lst[0]]) + [lst[0]] + \
quicksort([x for x in lst[1:] if x >= lst[0]])
非常优美
使用装饰器¶
import urllib.request as urllib
def cache(func):
saved = {}
def wrapper(url):
if url in saved:
return saved[url]
else:
page = func(url)
saved[url] = page
return page
return wrapper
@cache
def web_lookup(url):
return urllib.urlopen(url).read()
使用 collections 库¶
deque
from collections import deque
names = deque(['raymond', 'rachel', 'matthew', 'roger',
'betty', 'melissa', 'judith', 'charlie'])
names.popleft()
names.appendleft('mark')
deque 还具有 maxlen 关键字参数,可以限制其最大容量
letters = deque(['a', 'b', 'c', 'd', 'e'], maxlen = 5)
>>> letters
>>> deque(['a', 'b', 'c', 'd', 'e'])
>>> letters.append('f')
>>> deque(['b', 'c', 'd', 'e', 'f'])
序列解包¶
p = 'vttalk', 'female', 30, 'python@qq.com'
name, gender, age, email = p
*另外可以用 name 来接收不需要的一大段值
name, *middle, email = p
>>> name
>>> 'vttalk'
>>> middle
>>> ['female', 30]
>>> email
>>> 'python@qq.com'
遍历字典 key 和 value¶
d = {'a':1, 'b':2, 'c':3}
for k, v in d.items(): # python2 有 iteritems 方法,但在 python3 中已经被移除
print (k, '--->', v)
链式比较操作¶
age = 59
if 18 < age < 60:
print('yong man')
>>> False == False == True
# 相当于 (False == False) == (False == True)
>>> False
if else 三目运算¶
text = '男' if gender == 'male' else '女'
真值判断¶
很多情况下 不需要使用 == 和 != 去比较真假
if values:
do_something()
真假值对照表
类型 | False | True :--: | :---: | :---: bool | False | True str | '' | 非空str 数值 | 0,0.0 | 非0数值 容器 | [],(),{},set() | 非空容器 None | None | 非None对象
for else 语句¶
mylist = [0, 1, 2, 3]
for i in mylist:
if i == 2:
break # 跳出整个 for else 循环
print(i)
else:
print('oh yeah')
字符串格式化¶
python3 更提倡使用 format 格式化方法,虽然很难说这样做代码量更少,但 format 确实有很多 % 格式化方法所不具有的优点
s1 = "foofish.net"
s2 = "vttalk"
s3 = "welcome to {blog} and following {wechat}".format(blog=s1, wechat=s2)
切片¶
切片操作接受三个参数,分别是起始位置,结束位置(不包括)和步长
items = list(range(10))
# 第1到第4个元素的范围区间
sub_items = items[0:4]
# 奇数
odd_items = items[1::2]
#拷贝
copy_items = items[::]
#或者
copy_items1 = items[:]
python 中常见的容器拷贝(深)就是通过切片来实现的,因为赋值操作本质上是标签(指针)的赋值
使用生成器¶
def fib(n):
a, b = 0, 1
while a < n:
yield a
a, b = b, a + b
预设字典返回的默认值¶
当 key 不存在时即返回默认值
# 第一种方式 setdefault
data = {('a', 1), ('b', 2), ('a', 3)}
groups = {}
for (key, value) in data:
groups.setdefault(key, []).append(value)
# 第二种方式 defaultdict 库
from collections import defaultdict
groups = defaultdict(list)
for (key, value) in data:
groups[key].append(value)
函数参数 unpack¶
本质上就是容器/可迭代对象的解包
def foo(x, y):
print(x, y)
alist = [1, 2]
adict = {'x': 1, 'y': 2}
foo(*alist) # 1, 2
foo(**adict) # 1, 2
注意函数默认参数¶
def foo(x=[]):
x.append(1)
print(x)
>>> foo()
[1]
>>> foo()
[1, 1]
更安全的做法
def foo(x=None):
if x is None:
x = []
x.append(1)
print(x)
>>> foo()
[1]
>>> foo()
[1, 1]
为 dict 添加missing方法¶
class Dict(dict):
def __missing__(self, key):
self[key] = []
return self[key]
>>> dct = Dict()
>>> dct["foo"].append(1)
>>> dct["foo"].append(2)
>>> dct["foo"]
[1, 2]
很像 collections.defaultdict
from collections import defaultdict
dct = defaultdict(list)
>>> dct["foo"]
[]
>>> dct["bar"].append("Hello")
>>> dct
defaultdict(<type 'list'>, {'foo': [], 'bar': ['Hello']})
Zen¶
import this
try/except/else¶
try:
put_4000000000_volts_through_it(parrot)
except Voom:
print "'E's pining!"
else:
print "This parrot is no more!"
finally:
end_sketch()
print 重定向输出到文件¶
# python3 中移除
>>> print >> open("somefile", "w+"), "Hello World"
注意打开的模式: "w+" 而不能 "w" , 当然 "a" 是可以的
pow 还有第三个参数¶
都知道 pow 是用来求幂的函数,但其实 pow 还接受第三个参数,用来求模 pow(x, y, z) == (x ** y) % z
>>> pow(4, 2, 2)
0
>>> pow(4, 2, 3)
1
isinstance可以接收一个元组¶
元组内的类型是或的关系
>>> isinstance(1, (float, int))
True
>>> isinstance(1.3, (float, int))
True
>>> isinstance("1.3", (float, int))
False
>>> isinstance("1.3", (float, int, str))
True
字典的无限递归¶
虽然没什么用,但很有意思
>>> a, b = {}, {}
>>> a['b'] = b
>>> b['a'] = a
>>> a
{'b': {'a': {...}}}
# 链表无限循环
>>> a, b = [], []
>>> a.append(b)
>>> b.append(a)
>>> a
[[[...]]]
不能访问到的属性¶
>>> class O(object):pass
...
>>> o = O()
>>> setattr(o, "can't touch this", 123)
>>> o.can't touch this
File "<stdin>", line 1
o.can't touch this
^
SyntaxError: EOL while scanning string literal
不过,能用 setattr 设置属性,自然就可以用 getattr 取出
使用计数器对象进行计数¶
这是 collections 库中的一个 dict 子类,专门用于解决计数问题, 子类还包括 most_common 等方法
from collections import Counter
c = Counter('hello world')
>>> c
Counter({'l': 3, 'o': 2, ' ': 1, 'e': 1, 'd': 1, 'h': 1, 'r': 1, 'w': 1})
>>> c.most_common(2)
[('l', 3), ('o', 2)]
collections 库中的其他方法,稍后进行总结 collections 总结
漂亮的打印 JSON¶
使用 indent 关键字参数即可偏亮的打印 JSON
>>> import json
>>> print(json.dumps(data)) # No indention
{"status": "OK", "count": 2, "results": [{"age": 27, "name": "Oz", "lactose_intolerant": true}, {"age": 29, "name": "Joe", "lactose_intolerant": false}]}
>>> print(json.dumps(data, indent=2)) # With indention
{
"status": "OK",
"count": 2,
"results": [
{
"age": 27,
"name": "Oz",
"lactose_intolerant": true
},
{
"age": 29,
"name": "Joe",
"lactose_intolerant": false
}
]
}
无限循环的结束¶
# 不推荐
file = open("some_filename", "r")
while 1: # infinite loop
line = file.readline()
if not line: # 'readline()' returns None at end of file.
break
# Process the line.
file = open("some_filename", "r")
# 推荐
for line in file:
# Process the line.
另外,open 函数的功能也可由 file 替代
here starts my collection¶
或的另一种写法¶
# way 1
if i==a or i==b:
pass
# way 2
if i in (a, b):
pass
Python 之禅¶
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch. Now is better than never.
Although never is often better than right now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
Python 装饰器¶
Python 中的装饰器本质上就是一个函数(或者类,因为也存在类装饰器)装饰器最经典的应用场景就是在不改变原函数(类)的情况下增加其他功能或代码,或者说可以抽离大量与新函数功能无关的可复用代码。
装饰器由来¶
先看个例子:
def origin():
print('This is the origin function')
origin()
>>> This is the origin function
现在有一个新的需求,要记录下函数执行日志,可以直接再函数定义中添加代码
def origin():
print('This is the origin function')
logging.info('origin is running')
>>> This is the origin function
但这样做有一个缺点就是如果有很多类似函数也要增加日志,那么每一函数都要相应修改很麻烦,为了复用代码可以这样
def use_logging(func):
logging.warning('%s is running' % func.__name__)
func()
def origin():
print('This is the origin function')
use_logging(origin)
>>> WARNING:root:origin is running
This is the origin function
但这样破坏了原来的代码结构,每次都要将需要记录日志的函数作为参数传递给 use_logging, 这时候装饰器就应运而生
简单装饰器¶
def use_logging(func):
def wrapper():
logging.warning("%s is running" % func.__name__)
return func() # 把 origin 当做参数传递进来时,执行func()就相当于执行foo(), 虽然 func 可能没有返回值但这样写还是更安全
return wrapper
def origin():
print('This is the origin function')
origin = use_logging(origin)
origin()
>>> WARNING:root:origin is running
This is the origin function
这时,use_logging 就像是把真正的函数 origin 包裹起来了一样,所以叫做装饰器。这个例子中,函数进入和推出时被称为一个切面,所以这也叫做面向切面编程。
@语法糖¶
上面的例子中最后一步赋值可以被省略
origin = use_logging(foo)
origin()
在业务函数定义前面加上 @use_logging, 就可以省略最后一步的赋值过程
def use_logging(func):
def wrapper():
logging.warning("%s is running" % func.__name__)
return func() # 把 origin 当做参数传递进来时,执行func()就相当于执行foo(), 虽然 func 可能没有返回值但这样写还是更安全
return wrapper
@use_logging
def origin():
print('This is the origin function')
origin()
>>> WARNING:root:origin is running
This is the origin function
带参数的装饰器¶
有时装饰器可能也需要参数,可以给装饰器提供很大的灵活性,比如在装饰器中可以制定日志的等级
def use_logging(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "warn":
logging.warning("%s is running" % func.__name__)
elif level == "info":
logging.info("%s is running" % func.__name__)
return func(*args)
return wrapper
return decorator
@use_logging(level="warn")
def origin(name='origin'):
print("This is %s" % name)
origin()
>>> WARNING:root:origin is running
This is origin
这就是允许参数的装饰器,实际上时对原有装饰器函数的再封装并返回一个装饰器,本质上这是一个含参数的闭包。当使用 @use_logging(level='warn') 调用的时候,Python 能发现这一层封装,并把参数传递到装饰器环境中。
@use_logging(level='warn') 等价于 @decorator(带参数)
类装饰器¶
装饰器不仅可以是函数,也可以是类,相比函数装饰器,类装饰器实际上具有更高的灵活性,内聚性和封装性。类装饰器主要依靠类的 call() 方法,当使用 @ 将装饰器附加到函数上的时候,就会调用此方法。
class test_decorator(object):
def __init__(self, func):
self._func = func
def __call__(self):
print ('class decorator runing')
self._func()
print ('class decorator ending')
@test_decorator
def origin():
print ('This is the origin function')
origin()
>>> class decorator runing
This is the origin function
class decorator ending
functools.wraps¶
使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的信息不见了,比如函数的docstring、name、参数列表,先看例子:
# 装饰器
def logged(func):
def with_logging(*args, **kwargs):
'''doc for with_logging'''
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
'''doc for f'''
return x + x * x
>>> f.__name__
>>> with_logging
>>> f.__doc__
>>> doc for with_logging
函数 f 的信息被 with_logging 的信息取代了,于是它的 docstring, name 就变成了 with_logging 函数的信息了。 好在有 functools.wraps,wraps 本身也是一个装饰器,它能把原函数的信息拷贝到装饰器的 func 函数中,这就使得装饰器里的 func 具有和原函数一样的信息了。
from functools import wraps
def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
'''doc for with_logging'''
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
'''doc for f'''
return x + x * x
>>> f.__name__
>>> f
>>> f.__doc__
>>> doc for f
装饰器作用的顺序¶
一个函数可以同时被多个装饰器修饰,装饰器执行的顺序是从内到外的,最先调用内层的装饰器,然后依次到外层
@a
@b
@c
def f(x):
return x+1
# 等价于
f = a(b(c(f)))
Python 协程¶
协程,又称微线程,纤程。英文名Coroutine。
子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。 所以子程序调用是通过栈实现的,一个线程就是执行一个子程序。
子程序调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和子程序不同。
协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。
注意,在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似CPU的中断
看起来A、B的执行有点像多线程,但协程的特点在于是一个线程执行,那和多线程比,协程有何优势?
第一大优势就是协程极高的执行效率
因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
第二大优势就是不需要多线程的锁机制
因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。
Python对协程的支持还非常有限,用在generator中的yield可以一定程度上实现协程。虽然支持不完全,但已经可以发挥相当大的威力了。
import time
def generate():
n = 0
while True:
n = n + 1
print('generate is going to yield %d' % n)
r = yield n # 关键一句
print('generate has got %d' % r)
def process(g):
next(g)
n = 0
for i in range(10):
n = n + 1
print('process is going to send %d' % n)
r = g.send(n) # 关键一句
print('process has got %d' % r)
print('====================')
time.sleep(1)
g.close()
if __name__=='__main__':
g = generate()
process(g)
运行结果
generate is going to yield 1
process is going to send 1
generate has got 1
generate is going to yield 2
process has got 2
====================
process is going to send 2
generate has got 2
generate is going to yield 3
process has got 3
====================
process is going to send 3
generate has got 3
generate is going to yield 4
process has got 4
====================
process is going to send 4
generate has got 4
generate is going to yield 5
process has got 5
====================
process is going to send 5
generate has got 5
generate is going to yield 6
process has got 6
====================
process is going to send 6
generate has got 6
generate is going to yield 7
process has got 7
====================
process is going to send 7
generate has got 7
generate is going to yield 8
process has got 8
====================
process is going to send 8
generate has got 8
generate is going to yield 9
process has got 9
====================
process is going to send 9
generate has got 9
generate is going to yield 10
process has got 10
====================
process is going to send 10
generate has got 10
generate is going to yield 11
process has got 11
====================
协程看起来就好像是两个程序互为对方的子程序,程序在两个子程序之间不停的切换
深拷贝与浅拷贝的区别¶
直接赋值¶
首先,如果我们不进行拷贝,而是直接赋值,很有可能会出现意料之外的结果。比如 a 是一个列表,b=a,那么修改a的同时,b也会同样被修改,因为Python对象的赋值都是进行引用(内存地址)传递的,实际上 a 和 b 指向的都是同一个对象。
>>> a = [1,2,3]
>>> b = a
>>> a[2] = 4
>>> a
[1, 2, 4]
>>> b
[1, 2, 4]
>>> a is b
True
浅拷贝¶
为了避免这种情况发生,我们可以使用浅拷贝:
>>> a = [1,2,3]
>>> import copy
>>> b = copy.copy(a)
>>> b is a
False
>>> a[0] is b[0]
True
浅拷贝会创建一个新的对象,然后把生成的新对象赋值给新变量。注意这句话的意思,上面这个例子中1,2,3这三个int型对象并没有创建新的,新的对象是指 copy 创建了一个新的列表对象,这样 a 和 b 这两个变量指向的列表对象就不是同一个,但和两个列表对象里面的元素依然是按引用传递的,所以a列表中的对象1和b列表中的对象1是同一个。 但是这时修改a列表的不可变对象,b列表不会受到影响:
>>> a[0] = 4
>>> a
[4, 2, 3]
>>> b
[1, 2, 3]
由于浅拷贝时,对于对象中的元素,浅拷贝只会使用原始元素的引用(内存地址),所以如果对象中的元素是可变对象,浅拷贝就没辙了。比方说列表中包含一个列表,这时改动a,浅拷贝的b依然可能受影响:
>>> a = [1,2,[3,]]
>>> b = copy.copy(a)
>>> a is b
False
>>> a[2].append(4)
>>> a
[1, 2, [3, 4]]
>>> b
[1, 2, [3, 4]]
可以产生浅拷贝的操作有以下几种:
- 使用切片[:]操作
- 使用工厂函数(list/dir/set)
工厂函数看上去像函数,实质上是类,调用时实际上是生成了该类型的一个实例,就像工厂生产货物一样.
- 使用 copy 模块中的 copy() 函数
深拷贝¶
对于这个问题,又引入了深拷贝机制,这时不仅创建了新的对象,连对象中的元素都是新的,深拷贝都会重新生成一份,而不是简单的使用原始元素的引用(内存地址)。注意了,对象中的元素,不可变对象还是使用引用,因为没有重新生成的必要,变量改动时会自动生成另一个不可变对象,然后改变引用的地址。但可变对象的内容是可变的,改动后不会产生新的对象,也不会改变引用地址,所以需要重新生成。
>>> a = [1,2,[3,]]
>>> b = copy.deepcopy(a)
>>> a is b
False
>>> a[0] is b[0]
True
>>> a[2] is b[2]
False
这时再改变a中的元素对b就完全没有影响了:
>>> a = [1,2,[3,]]
>>> b = copy.deepcopy(a)
>>> a[2].append(4)
>>> a
[1, 2, [3, 4]]
>>> b
[1, 2, [3]]
特殊情况¶
- 对于非容器类型(如数字、字符串、和其他'原子'类型的对象)是没有拷贝这个说法的。
>>> a = 'hello'
>>> b = copy.copy(a)
>>> c = copy.deepcopy(a)
>>> a is b
True
>>> a is c
True
对于这种类型的对象,无论是浅拷贝还是深拷贝都不会创建新的对象。
- 如果元祖变量只包含原子类型对象,则不能深拷贝:
原子类型指所有的数值类型以及字符串
>>> a=(1,2,3)
>>> a = (1,2,3)
>>> b = copy.copy(a)
>>> c = copy.deepcopy(a)
>>> a is b
True
>>> a is c
True
元组本身是不可变对象,如果元组里的元素也是不可变对象,就没有进行拷贝的必要了。实测如果元组里面的元素是只包含原子类型对象的元组,则也属于这个范畴。
>>> a = (1,2,(3,))
>>> b = copy.copy(a)
>>> c = copy.deepcopy(a)
>>> a is c
True
>>> a is b
True
总结¶
- Python中对象的赋值都是进行对象引用(内存地址)传递
- 使用copy.copy(),可以进行对象的浅拷贝,它复制了对象,但对于对象中的元素,依然使用原始的引用.
- 如果需要复制一个容器对象,以及它里面的所有元素(包含元素的子元素),可以使用copy.deepcopy()进行深拷贝
- 非容器类型(如数字、字符串、和其他'原子'类型的对象)不存在拷贝。
- 只包含原子类型对象的元组变量不存在拷贝。
问题记录¶
关于切片和拷贝的深度¶
想用 matplotlib 画一个动态图,但是数据是实现读取好的。所以只能不断改变画图数据的大小来实现 plot 的动态过程。 想到两种方法,一个是新建一个 array,然后每一次迭代都 append 一个数据进去,然后 plot 这个 array,另一种方法 是每次对原始数据进行切片,后画图。
后来采用的是切片的方法,发现速度竟然比 append 要快很多,总觉得每一步一次的 append 应该不会比对那么多数据进行浅复制的切片慢啊。
因为数据是存储在 numpy 的 ndarray 中和 Python 内置的 list 可能有些不一样。list 在切片的时候实际上是进行浅拷贝的,而 ndarray 在切片时是返回一个 view(numpy 名词),共享核心数据,但其他的属性例如 shape 等都是不影响的。可能 ndarray 的核心数据都是 flatten 之后存储的吧。
总之,append 很慢,如果知道数组长度就预先分配好空间,如果不知道也尽量少做 append 或者 resize 的操作,用空间换时间是更优解吧。
input 和 stdin.readline¶
input() 读取一行并丢弃 回车
而 sys.stdin.readline() 读取一行但不会丢弃回车
输入流中都不再有 delim 字符
另外 stdin 中还有 read(size) readlines() 方法
readline
和 readlines
方法都读取并保留行尾的 '\n'
int()
在接受 str 作为参数时,会自动去掉行尾的 '\n'
>>> int('123\n')
123
Python 读取文件的几个函数以及大文件读写¶
read() 函数一次性读取整个文件,如果是大文件通常反复调用 read(size) 比较保险,每次读取 size 个字节。
另外 readline 每次读取一行内容,而 readlines 一次性读取所有行并返回一个 list
读取大文件的一个封装¶
def read_in_chunks(filePath, chunk_size):
file_object = open(filePath)
while True:
chunk_data = file_object.read(chunk_size)
if not chunk_data:
break
yield chunk_data
if __name__ == "__main__":
filePath = './path/filename'
for chunk in read_in_chunks(filePath):
pass
另外 也可以通过 iter 和 partial 更简单的实现这一功能
from functools import partial
def readanddraw():
with open(filePath) as f:
chunks = iter(partial(f.read, chunksize), '')
for chunk in chunks:
pass
iter() 函数有一个鲜为人知的特性就是,如果你给它传递一个可调用对象和一个 标记值,它会创建一个迭代器。这个迭代器会一直调用传入的可调用对象直到它返回标 记值为止,这时候迭代终止。
综上,对于大文件,要么 readline 要么 按固定大小读取文件。
3_其他¶
Literature¶
毛不易¶
项羽虞姬¶
项羽虞姬 (《王者荣耀》项羽虞姬英雄主打歌) - 毛不易
词:毛不易
曲:毛不易
长夜漫漫声声楚歌残
草长处月碎大河蓝
依稀旧梦幕幕总纠缠
不忍看不忍看
挥刀斩尽万古愁
奈何断水水更流
宿命欲催英雄瘦
不改天命誓不休
再歌一曲哪怕风急雨骤
角声阵阵相思写满袖
金戈铁马旧烈酒喝不够
梦里见你在桥头
情生两端绿野水向南
林深处岁月不流转
倩影青衫弦上月已满
风也轻云也淡
藤上飞鸟来又走
晚钟催红相思豆
英雄做刀妾作酒
荡尽世间恶与丑
涤净苍生直至山明水秀
情深意重丝丝不能露
金戈铁马旧烈酒喝不够
梦里见你在桥头
深情不死终有重逢时候
湮灭千年不曾放开手
再看你眼眸光彩依旧
续写那万世千秋
像我这样的人¶
像我这样的人 - 毛不易
词:毛不易
曲:毛不易
像我这样优秀的人
本该灿烂过一生
怎么二十多年到头来
还在人海里浮沉
像我这样聪明的人
早就告别了单纯
怎么还是用了一段情
去换一身伤痕
像我这样迷茫的人
像我这样寻找的人
像我这样碌碌无为的人
你还见过多少人
像我这样庸俗的人
从不喜欢装深沉
怎么偶尔听到老歌时
忽然也晃了神
像我这样懦弱的人
凡事都要留几分
怎么曾经也会为了谁
想过奋不顾身
像我这样迷茫的人
像我这样寻找的人
像我这样碌碌无为的人
你还见过多少人
像我这样孤单的人
像我这样傻的人
像我这样不甘平凡的人
世界上有多少人
像我这样莫名其妙的人
会不会有人心疼
消愁¶
消愁 (Live) - 毛不易
词:毛不易
曲:毛不易
当你走进这欢乐场
背上所有的梦与想
各色的脸上各色的妆
没人记得你的模样
三巡酒过你在角落
固执的唱着苦涩的歌
听他在喧嚣里被淹没
你拿起酒杯对自己说
一杯敬朝阳 一杯敬月光
唤醒我的向往 温柔了寒窗
于是可以不回头地逆风飞翔
不怕心头有雨 眼底有霜
一杯敬故乡 一杯敬远方
守着我的善良 催着我成长
所以南北的路从此不再漫长
灵魂不再无处安放
一杯敬明天 一杯敬过往
支撑我的身体 厚重了肩膀
虽然从不相信所谓山高水长
人生苦短何必念念不忘
一杯敬自由 一杯敬死亡
宽恕我的平凡 驱散了迷惘
好吧天亮之后总是潦草离场
清醒的人最荒唐
好吧天亮之后总是潦草离场
清醒的人最荒唐
无问¶
无问 (《无问西东》电影宣传曲) - 毛不易
词:毛不易
曲:毛不易
你问风为什么托着候鸟飞翔
却又吹的让他慌张
你问雨为什么滋养万物生长
却也湿透他的衣裳
你问他为什么亲吻他的伤疤
却又不能带他回家
你问我为什么还是不敢放下
明知听不到回答
如果光已忘了要将前方照亮
你会握着我的手吗
如果路会通往不知名的地方
你会跟我一起走吗
一生太短 一瞬好长
我们哭着醒来 又哭着遗忘
幸好啊 你的手曾落在我肩膀
就像空中漂浮的
渺小的 某颗尘土
它到底 为什么 为什么
不肯停驻
直到乌云散去 风雨落幕
他会带你找到 光的来处
就像手边落满了
灰尘的 某一本书
它可曾 单薄地
承载了 谁的酸楚
尽管岁月无声 流向迟暮
他会让你想起 你的归途
如果光已忘了要将前方照亮
你会握着我的手吗
如果路会通往不知名的地方
你会跟我一起走吗
盛夏¶
盛夏 - 毛不易
词:毛不易
曲:毛不易
那是日落时候轻轻发出的叹息吧
昨天已经走远了 明天该去哪啊
相框里的那些闪闪发光的我们啊
在夏天发生的事 你忘了吗
铁道旁的老树下 几只乌鸦
叫到嗓音沙哑 却再没人回答
火车呼啸着驶过 驶过寂寞或繁华
曾经年轻的人 也想我吗
就回来吧 回来吧 有人在等你啊
有人在等你说完那句说一半的话
就别走了 留下吧 外面它太复杂
多少次让你热泪盈眶却不敢流下
铁道旁的老树下 几只乌鸦
叫到嗓音沙哑 却再没人回答
火车呼啸着驶过 驶过寂寞或繁华
曾经年轻的人啊 也会想我吗
就回来吧 回来吧 有人在等你啊
有人在等你说完那句说一半的话
就别走了 留下吧 外面它太复杂
多少次让你热泪盈眶却不敢流下
可时光啊 不听话 总催着人长大
这一站到下一站旅途总是停不下
就慢慢的 忘了吧 因为回不去啊
那闭上眼睛就拥有了一切的盛夏
如果有一天我变得很有钱¶
如果有一天我变得很有钱 - 毛不易
词:毛不易
曲:毛不易
如果有一天我变得很有钱
我的第一选择不是去环游世界
躺在世界上最大最软的沙发里
吃了就睡醒了再吃先过一年
如果有一天我变得很有钱
就可以把所有人都留在我身边
每天快快乐乐吃吃喝喝聊聊天
不用担心关于明天或离别
变有钱 我变有钱
多少人没日没夜地浪费时间
变有钱 我变有钱
然后故作谦虚地说金钱不是一切
如果有一天我变得很有钱
我会买下所有难得一见的笑脸
让所有可怜的孩子不再胆怯
所有邪恶的人不再掌握话语权
如果有一天我变得很有钱
我会想尽一切办法倒流时间
不是为了人类理想做贡献
只是想和她说一句我很抱歉
变有钱 我变有钱
多少人没日没夜地浪费时间
变有钱 我变有钱
然后故作谦虚地说金钱不是一切
我变有钱
所有烦恼都被留在天边
变有钱 我变有钱
然后发自内心地说金钱它不是一切
变有钱 我变有钱
变有钱 我变有钱
春边¶
春边 - 毛不易
词:毛不易
曲:毛不易
拾一瓣落花
留一段牵挂
带不走风中的话
就留在这里吧
徘徊夕阳下
等不到回答
往日年少的你啊
也留在这里吧
春的风
夏的雨
秋的萧萧 冬的静
你的痴
我的傻
最是沉沉 舍不下
人说
江南桃花三月红
一寸往事一寸梦
千里送君留不住
散尽韶光两空空
我说
江南桃花三月红
人生何处不相逢
天涯咫尺君归处
蕙风不改酒香浓
江南桃花三月红
一寸往事一寸梦
千里送君留不住
散尽韶光两空空
我说
江南桃花三月红
人生何处不相逢
天涯咫尺君归处
蕙风不改酒香浓
天涯咫尺君归处
蕙风不改酒香浓
二零三¶
二零三 - 毛不易
词:毛不易
曲:毛不易
那天偶然走进你
五月南风正和煦
也许它的双手太多情
让我想留在这里
后来刮风又下雨
我都躲在你怀里
看着我所有的小脾气
无论快乐或伤心
虽然到现在 还孑然一身
你会陪我默默忧愁到凌晨
其实你知道 来来往往有多少人
只是扬起了灰尘
明天我就要远走
最后和你挥挥手
但你不会哭也不会挽留
给我想要的自由
虽然到现在 还不太安稳
你会为我默默留下一盏灯
其实你知道 匆匆忙忙有多少人
渐渐丢失了单纯
许多年之后 在哪里安身
是否还能拥抱这样的温存
其实我知道
于你而言我只不过是个
匆匆而过的旅人
其实我知道
于你而言我只不过是个
匆匆而过的旅人
其实我知道
于你而言我只不过是个
匆匆而过的旅人
芬芳一生¶
芬芳一生 - 刘惜君/毛不易
词:毛不易
曲:毛不易
男:
雨后晴空 玫瑰丛中
蝴蝶一路雨水 飞过彩虹
芬芳随风 柔情正浓
我愿在这风里 与你相逢
是否你会带着我未曾看过的色彩
装扮我长长的裙摆
是否我会用我未曾说过的言语
吐露着浪漫情怀
女:
月落星沉 暮色深深
秘密小路镌刻着往日恋痕
你说一生 我也默认
我们在这月光下十指生根
合:
仿佛远方传来悠扬的恋曲
见证了此刻甜蜜
终于我
终于我在你怀里翩翩舞蹈
从此再不怕衰老
天意让我拥有今生的你
完整了我的意义
告别
告别之前请你不要忘记
我会在远处等你
男:
雪地黄昏 缓缓两人
年轻的恋人已满是皱纹
回忆往事 悉数年轮
合:
笑容里却仍有那一片热忱
牧马城市 - 毛不易¶
词:段思思
曲:谭旋
游历在大街和楼房
心中是骏马和猎场
最了不起的脆弱迷惘
不过就这样
天外有天有无常
山外有山有他乡
跌了撞了 心还是回老地方
游离于城市的痛痒
错过了心爱的姑娘
宣告世界的那个理想
已不知去向
为所欲为是轻狂
防不胜防是悲伤
后来才把成熟当偏方
当所有想的说的要的爱的
都挤在心脏
行李箱里装不下我 想去的远方
这来的去的给的欠的 算一种褒奖
风吹草低见惆怅 抬头至少还有光
游历在大街和楼房
心中是骏马和猎场
最了不起的脆弱迷惘
不过就这样
天外有天有无常
山外有山有他乡
跌了撞了 心还是回老地方
游离于城市的痛痒
错过了心爱的姑娘
宣告世界的那个理想
已不知去向
为所欲为是轻狂
防不胜防是悲伤
后来才把成熟当偏方
当所有想的说的要的爱的
都挤在心脏
行李箱里装不下我 想去的远方
这来的去的给的欠的算一种褒奖
风吹草低见惆怅
抬头至少还有光
把烦恼痛了吞了认了算了
不对别人讲
谁还没有辜负几段 昂贵的时光
若男孩笑了哭了累了
说要去流浪
留下大人的模样
看岁月剑拔弩张
总会有个人成为你的远方
借¶
借 - 毛不易
词:毛不易
曲:毛不易
借一盏午夜街头 昏黄灯光
照亮那坎坷路上人影一双
借一寸三九天里 冽冽暖阳
融这茫茫人间刺骨凉
借一泓古老河水 九曲回肠
带着那摇晃烛火 漂往远方
借一段往日旋律 婉转悠扬
把这不能说的轻轻唱
被这风吹散的人说他爱得不深
被这雨淋湿的人说他不会冷
无边夜色到底还要蒙住多少人
它写进眼里 他不敢承认
借一抹临别黄昏悠悠斜阳
为这漫漫余生添一道光
借一句刻骨铭心来日方长
倘若不得不天各一方
被这风吹散的人说他爱得不深
被这雨淋湿的人说他不会冷
无边夜色到底还要蒙住多少人
它写进眼里 他不敢承认
可是啊 总有那风吹不散的认真
总有大雨也不能抹去的泪痕
有一天太阳会升起在某个清晨
一道彩虹 两个人
借一方乐土让他容身
借他平凡一生
感觉自己是巨星¶
感觉自己是巨星 - 毛不易
词:毛不易
曲:毛不易
有时候我感觉自己是一个巨星
每个人都爱着我有钱又有名
所以每当生活让我想死的时候
对自己说巨星只是在扮演平民
有时候我感觉自己是一个巨星
年轻貌美有才华用也用不尽
所以当我走到一筹莫展的时候
只是巨星需要休息
巨星啊巨星我们爱你
少了你生活就不能继续
为了让粉丝们活下去
你要好好照顾自己
巨星啊巨星我们支持你
支持你做的所有决定
大胆做自己别犹豫
巨星永远有人气
有时候我感觉自己是一个巨星
每天各大时尚 party 出席个不停
所以当我偶尔觉得孤单的时候
离开人群假装很庆幸
巨星啊巨星我们爱你
少了你生活就不能继续
为了让粉丝们活下去
你要好好照顾自己
巨星啊巨星我们支持你
支持你做的所有决定
大胆做自己别犹豫
巨星永远有人气
希望你也感觉自己是一个巨星
这样活着也许就能有点乐趣
如果你个人没有很想当巨星
想当什么都可以
Log¶
Other¶
Principle¶
虽说学海无涯,进一寸有一寸的欢喜,但实在不可贪多求全,铺的太开
过早考虑优化是万恶之源,在实现的初期考虑太多犹豫不决同样令人烦躁
学的东西能用起来才是坠吼滴
只学习不工作也是不行滴
TODO¶
collections 整理 Done
python 二维数组创建
jupyternotebook 特性
md 文件中 ``` 后的 Python 改为 python
list 和 str 类方法总结
杂项笔记¶
GitHub Flavored Markdown¶
GitHub在标准 markdown 基础上修改的 Markdown 语法,称为 GFM 具体语法见GFM 语法
GFM 将标题视为锚点,为实现页面内跳转,主要注意以下四点
1. 页面链接将标题内容转换为小写
2. 移除所有不是 字母,数字,空格和连字符的内容
3. 将所有空格转换为连字符
4. 如果标题不唯一,在后面添加 -1,-2 等使其唯一
页间指定位置的跳转也是可以实现的
小工具¶
一款自动生成 GFM 目录的工具¶
两个 while True 之间的处理,使用标志位?
GitHub README 中类似这样的图片网站 https://shields.io/
Markdown 编辑器 Atom + 几个插件 https://www.cnblogs.com/libin-1/p/6638165.html 使用 atom 打造 markdwon 编辑器
另外用 jupyternotebook 写 markdown 也不错,但是不要在里面加太多图片或者任何视频
获取文件夹内文件列表
os.walk()
os.walk() 有一个特性就是 当 topdown 为 True(默认)时,可以手动修改 dirs 内容,以调整是否在某些文件夹内迭代。但是当 topdown 为 False 的时候不可以,因为迭代先于 3-tuple 的生成。
os.listdir() 返回一个所有文件和文件夹的列表,不区分
文件路径的拆分和连接(自动处理系统差异)
os.path.split() 拆分 os.path.join() 连接 os.path.splitext() 拆分文件名和扩展名
给 python 脚本传递参数
使用 os.argv,这是一个 str 的列表,os.argv[0] 是文件名
控制台清屏
os.system("cls") # windows
os.system("clear") # linux
Linux 中把输出信息同时记录到文件的方法
使用 tee 命令 read from standard input and write to standard output and files.
ls tee ls_tee.txt
cat ls_tee.txt
使用 tee 时,如果想保留目标文件原有的内容,可以使用 -a 参数,附加至给出的文件,而不是覆盖它。
命令 tee 的参数选项:
-a: 向文件中重定向时使用追加模式
-i: 忽略终端信号(Interrupt)
http://kuanghy.github.io/2017/01/02/linux-tee
windows 下 在任意文件夹打开 命令行的方法: 按住 shift 单击鼠标右键 选择 在此处打开 powershell
ubuntu 遇到的解决办法 找出所有 用到 apt 和 grep 的进程 ps -A |grep apt 杀死进程 sudo -kill -SIGKILL processnumber 例如 sudo -kill -9 13431
python 执行系统命令的几种方式
1.os.system() 仅仅打开窗口并执行
2.os.popen() 执行并返回一个结果的类文件对象 可对其使用 readlines() 方法
3.使用 commands 模块
http://www.cnblogs.com/xuxm2007/archive/2011/01/17/1937220.html
git clone 速度太慢的解决方案
https://www.zhihu.com/question/27159393/answer/142210469
更改 jupyter notebook 起始目录的四种方法 http://blog.csdn.net/qq_33039859/article/details/54604533
在 Ubuntu 上 build aseba 过程中的问题 有时候在一行命令中安装太多依赖库,有些被自动忽略是不会报错的,这种时候后面一切基于依赖库的操作都可能受到影响,并且有可能确信自己已经安装好了必要的依赖。
记一次学姐的硬盘修复¶
昨天学姐跟我说她的硬盘坏了,无缘无故的连接电脑就显示“未格式化,需要格式化”,盘符在资源管理器里有显示,可就是进不去,一点击就显示“需要格式化”, 用 Windows 自带的硬盘管理查看了一下,硬盘的文件系统显示为 RAW, 也就是未格式化的意思。赶紧用 DiskGenius 试着进行文件恢复,发现可以扫描出硬盘内原有的文件,但无奈速度太慢,不想等待,于是求助百度。
百度给出的解决方案是 Windows 系统自带的一个命令,CHKDSK 顾名思义就是硬盘检查的意思。
于是照做,cmd 内输入 CHKDSK E:/f E 是盘符,结果发现这个命令执行起来也很慢,不过好在最后是修复了硬盘的分区表, 可以进入硬盘读取文件了,就是仍有一部分文件是损坏状态,无法复制出来。
阅读清单¶
python¶
魔方公式¶
本公式从完成底面和侧面第一第二层开始
Step 1¶
目标:完成顶面黄色十字
描述:顶面可能有三种情况:
||
|
:--: | :---: | :---:
|形状1中心黄色块|形状2小拐弯 小拐放在左上角(从顶向下看)|形状3一字 一字横放
此三种情况都使用一个公式,上述形状不严格定义(可以存在其他黄色块)
顶层形状可能在上述形状之间变换,重复公式1即可
公式 1:F R U R' U' F'
Step 3¶
目标:完成顶面
描述:小鱼分为两种,分别使用两个不同的公式可以一步到达顶面完整状态
|
:--: | :---:
小鱼1 使用公式2|小鱼2 使用公式3
公式 3:F U F' U F U2 F'
Step 4¶
目标:侧面第三层出现一面三块同色,其余三面都为“眼”
描述:(重复)公式4
魔方摆放:顶层黄色面正对自己,将侧面第三层的眼放在右手面(如果存在的话,若不存在则在使用一次公式4后出现)
公式 4:R2 D2 R' U' R D2 R' U R'
Step 5¶
目标:完成魔方
描述:这一步需要判断三个棱的交换顺序,顺时针还是逆时针
|
:--: | :---:
顺时针|逆时针
- 顺时针:公式3 -> 魔方整体 U2 -> 小鱼1 -> 公式2
- 逆时针:公式2 -> 魔方整体 U2 -> 小鱼2 -> 公式3
注意这两种情况下,同色棱一个在前一个在右