眾所周知C++是幾乎所有XCPCer的主語言,但是有一些時候(比如不想寫大數乘法、在LeetCode上做題、參加筆試面試)不得不使用在某些場景下更好寫的Python。Python的標準庫提供了很多很實用的功能,而且有著非常人性化的異常(比如陣列越界、除以零等),最重要的是它內建的print函式可以非常方便的輸出各種各樣複雜的巢狀結構(C++需要自己寫一個函式進行輸出,或者每次都使用for迴圈等,在LeetCode/筆試面試階段非常不利)。
這篇文章記錄了一些我覺得Python提供了內建的方便的庫函式,並且有一些坑是可能需要注意的也會記錄下來。
輸入
一般是用字串的split(),無參的split會自動過濾各種空白字元,不要畫蛇添足。(注意split(" ")會把"1 2"分割為["1", "", "2"])其返回一個列表,使用列表推導式把他分別賦值給需要的資料型別就可以了。
a, b, c = [int(i) for i in input().split()] # 一行輸入多個整數
print(a, b, c) # 1 2 3
print(a + b + c) # 6
a, b, c = input().split() # 一行輸入多個字串
print(a, b, c) # 1 2 3 注意這裡是字串
print(a + b + c) # 123
出現混合輸入的情況就分別進行轉換即可。
二進位制輸入
a_str = "1001"
a_int = int(a_str, 2)
print(a_int) # 9
b_int = 12
b_str = bin(b_int)
print(b_str) # 0b1100
print(b_str[2:]) # 1100
數學
Python中的一些簡單的數學行為和C++的不一樣,要特別注意
INF_float = 1e9
print(INF_float) # 1000000000.0
INF_int = 10**9
print(INF_int) # 1000000000
print(5 / 2) # 2.5
print(5 // 2) # 2
print(7 % 3) # 1
print(-7 % 3) # 2 注意這個和C++的顯著區別,Python的%是會自動變回非負數的
print(5 // 0) # ZeroDivisionError: integer division or modulo by zero
Python中還有更好用的函式,可以對列表或者列表切片等使用。
my_list = [i for i in range (1, 6)]
print(my_list) # [1, 2, 3, 4, 5]
print(sum(my_list)) # 15
print(min(my_list)) # 1
print(max(my_list)) # 5
my_list = [1, 1, 2, 2, 3, 3]
print(my_list.index(min(my_list))) # 0 返回第一個最小值的位置
print(my_list.index(max(my_list))) # 4 返回第一個最大值的位置
列表 List
https://docs.python.org/zh-cn/3/tutorial/datastructures.html
常用方法
見上面的連結,說得非常清楚,基本上都和C++的vector的方法一模一樣。與眾不同的是:
len(my_list) # vector.size()
my_list.append(x) # vector.push_back(x);
x = my_list.pop() # x = vector.back(); vector.pop_back();
my_list.sort() # sort(vector.begin(), vector.end()) 原地排序
my_list.sort(reversed=True) # sort(vector.begin(), vector.end(), greater<int>())
my_list.reverse() # reverse(vector.begin(), vector.end())
set(my_list) # 在不修改list的前提下返回一個無重的set,不一定保證有序 https://docs.python.org/zh-cn/3/tutorial/datastructures.html#sets
sorted(my_list) # 在不修改原本的list的情況下返回一個新的有序list
my_list= list(set(sorted(my_list))) # 排序並去重
二維列表
生成一個初始值為0的,長度為n的空列表:
n = 10
my_list_1 = [0] * n # 如果要生成包含n個空列表的列表,則是[[]] * n
my_list_2 = [0 for _ in range(n)] # 注意這個列表推導式要用[]括起來才是列表
print(my_list_1, my_list_2) # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
my_list_1[1] = 1
my_list_2[2] = 2
print(my_list_1, my_list_2) # [0, 1, 0, 0, 0, 0, 0, 0, 0, 0] [0, 0, 2, 0, 0, 0, 0, 0, 0, 0]
print(my_list_1[n]) # IndexError: list index out of range
看起來沒啥問題是吧?看看二維列表
n = 3
my_list_1 = [[0] * n] * n
my_list_2 = [[0 for _ in range(n)] for _ in range(n)]
print(my_list_1, my_list_2) # [[0, 0, 0], [0, 0, 0], [0, 0, 0]] [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
my_list_1[1][1] = 1
my_list_2[2][2] = 2
print(my_list_1, my_list_2) # [[0, 1, 0], [0, 1, 0], [0, 1, 0]] [[0, 0, 0], [0, 0, 0], [0, 0, 2]]
用第一種方法生成的列表,其的引用被複制了n次。而對於內建的整數型別0來說,沒有這個問題。意味著如果生成的是引用型別,要注意區分,一般都是應該用第二種方法生成的。
當然由於0沒問題,所以下面的寫法也是可以的
n = 3
my_list_3 = [[0] * n for _ in range(n)]
my_list_4 = [0 for _ in range(n)] * n # 這種方法生成的是一維列表
print(my_list_3, my_list_4) # [[0, 0, 0], [0, 0, 0], [0, 0, 0]] [0, 0, 0, 0, 0, 0, 0, 0, 0]
my_list_3[1][1] = 1
my_list_4[2] = 2
print(my_list_3, my_list_4) # [[0, 0, 0], [0, 1, 0], [0, 0, 0]] [0, 0, 2, 0, 0, 0, 0, 0, 0]
當然,為了防止迷惑,我覺得最好統一養成用my_list_2的寫法的習慣。
逆序下標訪問列表
python可以透過負數下標列舉列表:
n = 6
my_list = [i for i in range(n)]
print(my_list) # [0, 1, 2, 3, 4, 5]
# 用負數逆序列舉列表
print(my_list[-1], my_list[-2]) # 5 4
# 負0和正0都是0,沒有區別
print(my_list[-0], my_list[+0]) # 0 0
# 要記得逆序的時候,最後一個元素是-n而不是-(n - 1)
print(my_list[-n], my_list[-(n - 1)]) # 0 1
逆序列舉列表
使用range逆序列舉的時候,要寫清楚3個引數。只寫兩個引數的時候,結果如下:
n = 6
my_list = [i for i in range(n)]
print(my_list) # [0, 1, 2, 3, 4, 5]
print([my_list[i] for i in range(n - 1, -1)]) # [] 輸出是空列表
print([i for i in range(n - 1, -1)]) # [] 因為range(n - 1, -1) 是空列表
print([my_list[i] for i in range(-1, -n - 1)]) # [] 輸出是空列表
print([i for i in range(-1, -n - 1)]) # [] 因為range(-1, -n - 1) 是空列表
寫清楚三個引數之後就可以了,我相信在XCPC裡面第二種、第三種寫法是比較常用的。
n = 6
my_list = [i for i in range(n)]
print(my_list) # [0, 1, 2, 3, 4, 5]
print([my_list[i] for i in range(n - 1, -1, -1)]) # [5, 4, 3, 2, 1, 0]
print([my_list[i] for i in range(-1, -n - 1, -1)]) # [5, 4, 3, 2, 1, 0]
print([my_list[i] for i in reversed(range(n))]) # [5, 4, 3, 2, 1, 0] 推薦,生成了逆序的下標i,方便處理
如果不需要用到下標i,則直接生成一個反向的列表就可以了:
n = 6
my_list = [i for i in range(n)]
print(my_list) # [0, 1, 2, 3, 4, 5]
print(my_list[::-1]) # [5, 4, 3, 2, 1, 0] 要麼只寫最後一個步長-1
print(my_list[-1:-n - 1:-1]) # [5, 4, 3, 2, 1, 0] 要麼必須寫夠3個引數
print(reversed(my_list)) # <list_reverseiterator object at 0x7fc4e27655b0>
print([reversed(my_list)]) # [<list_reverseiterator object at 0x7fc4e27655b0>]
my_list.reverse() # 此方法會修改my_list
print(my_list) # [0, 1, 2, 3, 4, 5]
字串 str
字串的長度,注意寫法和C++的不太一樣
my_str = "Hello world!"
print(my_str) # Hello world!
print(len(my_str)) # 12
# print(my_str.len) # 崩潰
# print(my_str.length) # 崩潰
# print(my_str.size) # 崩潰
字串也沒有reverse方法,要使用一些奇怪的辦法去實現,一般應該是生成一個反向的字串切片覆蓋到原本的上面
my_str = "Hello world!"
# my_str.reverse() # 崩潰
# reverse(my_str) # 崩潰
print(reversed(my_str)) # <reversed object at 0x7fcea5f158b0>
print("".join(reversed(my_str))) # !dlrow olleH
print(my_str[::-1]) # !dlrow olleH
字串有find方法,找到第一個匹配的下標的位置,找不到返回-1
my_str = "abcabcabc"
print(my_str.find("abc")) # 0
print(my_str.find("cab")) # 2
print(my_str.find("d")) # -1
字串有count方法,找到所有不重疊的子串出現的次數。
my_str = "abababab"
print(my_str.count("ab")) # 4
print(my_str.count("abab")) # 2
二分查詢
https://docs.python.org/zh-cn/3.12/library/bisect.html
# bisect.bisect_left(a, x, lo=0, hi=len(a), *, key=None) # 第一個大於等於x的元素,相當於C++的lower_bound
# bisect.bisect_right(a, x, lo=0, hi=len(a), *, key=None) # 第一個大於x的元素,相當於C++的upper_bound
# bisect.insort_left(a, x, lo=0, hi=len(a), *, key=None) # 插入到第一個大於等於x的元素的左邊
# bisect.insort_right(a, x, lo=0, hi=len(a), *, key=None) # 插入到第一個大於x的元素的左邊(最後一個小於等於x的元素的右邊)
import bisect
my_list = [1, 2, 3, 4, 4, 4, 4, 5, 6]
print(bisect.bisect_left(my_list, 4)) # 3 第一個4的位置
print(bisect.bisect_right(my_list, 4)) # 7 最後一個4的下一個位置,也就是5
bisect.insort_left(my_list, 4)
print(my_list) # [1, 2, 3, 4, 4, 4, 4, 4, 5, 6]
bisect.insort_right(my_list, 7)
print(my_list) # [1, 2, 3, 4, 4, 4, 4, 4, 5, 6, 7]
更多複雜的判斷可以點進連結自己看。
奇怪的列表列舉 itertools
https://docs.python.org/3/library/itertools.html
import itertools
n = 5
my_list = [i for i in range(1, 1 + n)]
print(my_list) # [1, 2, 3, 4, 5]
accumulated_my_list = [i for i in itertools.accumulate(my_list)]
print(accumulated_my_list) # [1, 3, 6, 10, 15]
對同一個列表還有一些看起來有用的方法:
product: 相當於n維for迴圈的笛卡爾積,可能在Floyd演算法可以用?
permutations: 排列數(按位置區分元素而不是按值區分),可以代替C++的next_permutation使用
combinations: 組合數
import itertools
two_dimension_product = [[i, j] for i, j in itertools.product(range(3), repeat=2)]
print(two_dimension_product) # [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]]
A_3_3 = [l for l in itertools.permutations([1, 2, 3])] # 位置的排列數,(位置的)字典序
print(A_3_3) # [(1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), (3, 2, 1)]
A_3_2 = [l for l in itertools.permutations([1, 2, 3], 2)] # 位置的排列數,(位置的)字典序
print(A_3_2) # [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]
C_3_3 = [l for l in itertools.combinations([1, 2, 3], 3)] # 位置的組合數,(位置的)字典序
print(C_3_3) # [(1, 2, 3)]
C_3_2 = [l for l in itertools.combinations([1, 2, 3], 2)] # 位置的組合數,(位置的)字典序
print(C_3_2) # [(1, 2), (1, 3), (2, 3)]
C_3_3_special = [l for l in itertools.combinations_with_replacement([1, 2, 3], 3)] # 同一個位置可以重複選的組合數
print(C_3_3_special) # [(1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 2, 2), (1, 2, 3), (1, 3, 3), (2, 2, 2), (2, 2, 3), (2, 3, 3), (3, 3, 3)]
C_3_2_special = [l for l in itertools.combinations_with_replacement([1, 2, 3], 2)] # 同一個位置可以重複選的組合數
print(C_3_2_special) # [(1, 1), (1, 2), (1, 3), (2, 2), (2, 3), (3, 3)]
# 這些方法說的重複是指位置重複,與值是否重複是無關的
A_3_2_replacement = [l for l in itertools.permutations([1, 1, 2], 2)]
print(A_3_2_replacement) # [(1, 1), (1, 2), (1, 1), (1, 2), (2, 1), (2, 1)]
C_3_2_replacement = [l for l in itertools.combinations([1, 1, 2], 2)]
print(C_3_2_replacement) # [(1, 1), (1, 2), (1, 2)]
簡單資料結構
棧
棧用list來模仿剛剛好。基本上是完全一致的。
佇列
如果使用內建的雙端佇列,和C++的queue是一樣的體驗。https://docs.python.org/zh-cn/3/library/collections.html#deque-objects
其實列表可以一邊遍歷一邊append,所以也可以這樣寫BFS:
Q = []
vis[1] = True
Q.append(1)
for u in Q:
for v in G[u]:
if not vis[v]:
vis[v] = True
Q.append(v)
在實現BFS時,C++也可以用vector來代替queue,其實更好寫。寫法和上面相同。
集合
set對應的大概是C++的unordered_set。
詳見:https://docs.python.org/zh-cn/3/library/stdtypes.html#set-types-set-frozenset
對映 / 字典
python的字典對應的大概是C++的unordered_map,不過字典的順序是插入順序。
詳見:https://docs.python.org/zh-cn/3/library/stdtypes.html#mapping-types-dict