1. 程式人生 > >python資料結構和序列

python資料結構和序列

1.元祖

元祖是一種固定長度、不可變的Python物件序列。建立元祖最簡單的方法就是用逗號分隔序列值。

tup = 4,5,6
print(tup)  #(4,5,6)

用更復雜的表示式來定義元祖時,一般需要用括號將值包起來。

complex_tup = (1,2,3),(4,5)
print(complex_tup) #((1, 2, 3), (4, 5))

我們也可以使用tuple函式將任意序列或者迭代器轉換為元祖:

tup_1 = tuple([4,0,2])
print(tup_1) #(4, 0, 2)
tup_2 = tuple('string')
print(tup_2) #('s', 't', 'r', 'i', 'n', 'g')

元祖的元素可以通過[ ]來獲取,python中的序列索引是從0開始的。
雖然元祖中儲存的物件其自身是可變的,但是元祖一旦建立,各個位置上的物件是無法被修改的:

tup = tuple(['foo',[1,2],True])
tup[2] = False
print(tup)
---------------------------------------------------------------------------
Traceback (most recent call last):
  File "C:/Users/asus/PycharmProjects/資料分析/3.1資料結構和序列.py", line 16, in <module>
    tup[2] = False
TypeError: 'tuple' object does not support item assignment

如果元祖中的一個物件是可變的,例如列表,你可以在它內部進行修改:

tup = tuple(['foo',[1,2],True])
tup[1].append(4)
print(tup) #('foo', [1, 2, 4], True)

我們可以使用+號連線元祖來生成更長的元祖:

a = (4,5,6)+(None,4,[8,9])+('xiewangting',)
print(a) #(4, 5, 6, None, 4, [8, 9], 'xiewangting')

將元祖乘以整數,則會和列表一樣,生成含有多份拷貝的元祖:

print(('foo','bar') * 4) #('foo', 'bar', 'foo', 'bar', 'foo', 'bar', 'foo', 'bar')

1.1元祖拆包

如果想要將元祖型的表示式賦值給變數,python會對等號右邊的值進行拆包:

tup = (4,5,6)
a,b,c = tup
print(a) #4
print(b) #5
print(c) #6

即使是巢狀的元祖也可以拆包:

tup = 4,5,(6,7)
a,b,(c,d) = tup
print(c) #6

在python中交換兩數的值,可以使用如下程式碼完成:

a,b = 1,2
a,b = b,a
print(a) #2
print(b) #1

拆包的一個常用場景就是遍歷元祖或列表組成的序列:

seq = [(1,2,3),(4,5,6),(7,8,9)]
for a,b,c in seq:
    print('a={0},b={1},c={2}'.format(a,b,c)
---------------------------------------------------------------------------
a=1,b=2,c=3
a=4,b=5,c=6
a=7,b=8,c=9

python語言新增了一些更為高階的元祖拆包功能,我們可以用元祖的起始位置“採集”一些元素,這個功能使用特殊的語法*rest,用於在函式呼叫時獲取任意長度的位置引數列表:

values = 1,2,3,4,5
a,b,*rest = values
print(a,b) #1 2
print(rest) #[3, 4, 5]

rest部分有時是你想要丟棄的資料,rest這個變數名並沒有特殊之處,出於習慣,很多python程式設計師會用下劃線來表示想要丟棄的資料。

1.2元祖方法

由於元祖的內容和長度是無法改變的,所以它的例項方法很少。一個常見的有用方法是count(在列表中也可以使用),用於計算某個數值在元祖中出現的次數:

a = (1,2,2,2,3,4,5,6,6,7,7,5,8)
print(a.count(2)) #3

2列表

與元祖不同,列表的長度是可以變化的,它所包含的內容也是可以修改的。我們可以使用中括號[ ]或者list型別函式來定義列表:

a_list = [2,3,5,6,None]
tup = ('abc','xwt','www')
b_list = list(tup)
print(b_list) #['abc', 'xwt', 'www']
b_list[1] = 'aaa'
print(b_list) #['abc', 'aaa', 'www']

list函式在資料處理中常用於將迭代器或者生成器轉化為列表:

gen = range(10)
print(gen) #range(0, 10)
print(list(gen)) #[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

2.1增加和移除元素

使用append方法可以將元素新增到列表的尾部:

b_list = ['aaa','bbb','ccc']
b_list.append('dwarf')
print(b_list) #['aaa', 'bbb', 'ccc', 'dwarf']

使用insert方法可以將元素插入到指定的列表位置:

b_list = ['aaa','bbb','ccc']
b_list.insert(1,'ggg')
print(b_list) #['aaa', 'ggg', 'bbb', 'ccc']

插入位置的範圍在0到列表長度之間。
insert與append相比,計算代價更高。因為子序列元素不得不在內部移動為新元素提供空間。
insert的反操作是pop,該操作會將特定位置的元素移除並返回:

b_list = ['aaa','bbb','ccc']
print(b_list.pop(2)) #ccc
print(b_list) #['aaa', 'bbb']

元素可以通過remove方法移除,該方法會定位第一個符合要求的值並移除它:

b_list = ['aaa','bbb','ccc']
b_list.append('aaa')
b_list.remove('aaa')
print(b_list) #['bbb', 'ccc', 'aaa']

使用in關鍵字可以檢查一個值是否在列表中:

b_list = ['aaa','bbb','ccc']
print('aaa' in b_list) #True

not關鍵字可以用作in的反義詞,表示“不在”。
與字典、集合相比,檢查列表中是否包含一個值是非常緩慢的。這是因為python在列表中進行了線性逐個掃描,而在字典和集合中python是同時檢查所有元素的(基於雜湊表)。

2.2連線和聯合列表

與元祖類似,兩個列表也可以使用+號連線。
如果有一個已經定義的列表,那麼我們可以用extend方法向該列表新增多個元素:

a = [3,None,'bar']
a.extend([8,9,(6,7)])
print(a) #[3, None, 'bar', 8, 9, (6, 7)]

請注意通過新增內容來連線列表是一個相對高代價的操作,這是因為連線過程中建立了新列表,並且還要複製物件。使用extend將元素新增到已經存在的列表是更好的方式,尤其是我們需要構建一個大型列表時:

everything = []
for chunk in list_of_lists:
    everything.extend(chunk)

上述實現比下述實現更快:

everything = []
for chunk in list_of_lists:
    everything = everything + chunk

2.3排序

我們可以呼叫列表的sort方法來對列表進行內部排序(無須新建一個物件):

a = [7,5,4,6,4,3,2,1]
a.sort()
print(a) #[1, 2, 3, 4, 4, 5, 6, 7]

sort函式中有一個項是傳遞一個二級排序key----一個用於生成排序值的函式。例如,我們可以通過字串的長度進行排序:

b = ['xiewt','xwt','xiewangting','xiewangt']
b.sort(key = len)
print(b) #['xwt', 'xiewt', 'xiewangt', 'xiewangting']

2.4二分搜尋和已排序列表的維護

內建的bisect模組實現了二分搜尋和已排序列表的插值。bisect.bisect會找到元素應當被插入的位置,並保持序列排序,而bisect.insort將元素插入到相應位置:

import bisect
c = [1,2,2,2,3,4,7]
print(bisect.bisect(c,2)) #4
print(bisect.bisect(c,5)) #6
bisect.insort(c,6)
print(c) #[1, 2, 2, 2, 3, 4, 6, 7]

2.5切片

使用切片符號可以對大多數序列型別選取其子集,它的基本形式是將start:stop傳入到索引符號[ ]中。
由於起始位置start的索引是包含的,而結束位置stop的索引並不包含,因此元素的數量是stop-start。
start和stop是可以省略的,如果省略的話會預設傳入序列的起始位置或結束位置。
負索引可以從序列的尾部進行索引:

seq = [7,5,0,4,3,6,7,3,1]
print(seq[-4:]) #[6, 7, 3, 1]
print(seq[-7:-4]) #[0, 4, 3]

步進值step可以在第二個冒號後面使用,意思是每隔多少個數取一個值:

seq = [7,5,0,4,3,6,7,3,1]
print(seq[::2]) #[7, 0, 3, 7, 1]

當需要對列表或元祖進行翻轉時,我們可以向步進傳值-1:

seq = [7,5,0,4,3,6,7,3,1]
print(seq[::-1]) #[1, 3, 7, 6, 3, 4, 0, 5, 7]

3內建序列函式

3.1enumerate

我們經常需要在遍歷一個序列的同時追蹤當前元素的索引。可以使用python內建函式enumerate來實現,返回了(i,value)元祖的序列,其中value是元素的值,i是元素的索引:

for i,value in enumerate(collection):

當你需要對資料建立索引時,一種有效的模式就是使用enumerate構造一個字典,將序列值對映到索引位置上:

some_list = ['foo','bar','baz']
mapping = {}
for i,v in enumerate(some_list):
    mapping[v] = i
print(mapping) #{'foo': 0, 'bar': 1, 'baz': 2}

3.2sorted

sorted函式返回一個根據任意序列中的元素新建的已排序列表:

print(sorted([7,8,5,4,6,3,4,6,7])) #[3, 4, 4, 5, 6, 6, 7, 7, 8]
print(sorted('horse race')) [' ', 'a', 'c', 'e', 'e', 'h', 'o', 'r', 'r', 's']

sorted函式接受的引數與列表的sort方法一致。

3.3zip

zip將列表、元祖或其他序列的元素配對,新建一個元祖構成的列表:

seq1 = ['foo','bar','baz']
seq2 = ['one','two','three']
zipped = zip(seq1,seq2)
print(list(zipped)) #[('foo', 'one'), ('bar', 'two'), ('baz', 'three')]

zip可以處理任意長度的序列,它生成列表長度由最短的序列決定:

seq1 = ['foo','bar','baz']
seq2 = ['one','two','three']
seq3 = [False,True]
print(list(zip(seq1,seq2,seq3))) #[('foo', 'one', False), ('bar', 'two', True)]

zip的常用場景為同時遍歷多個序列,有時候會和enumerate同時使用:

seq1 = ['foo','bar','baz']
seq2 = ['one','two','three']
for i,(a,b) in enumerate(zip(seq1,seq2)):
    print('{0}:{1},{2}'.format(i,a,b))
--------------------------------------
0:foo,one
1:bar,two
2:baz,three

給定一個已經“配對”的序列時,zip函式有一種機智的方式去“拆分”序列:

pitchers = [('Nolan','Ryan'),('Roger','Clemens'),('Schilling','Curt')]
first_names,last_names = zip(*pitchers)
print(first_names) #('Nolan', 'Roger', 'Schilling')
print(last_names) #('Ryan', 'Clemens', 'Curt')

3.4reversed

reversed函式將序列的元素倒序排列:

print(list(reversed(range(10)))) #[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

reversed是一個生成器,因此如果沒有例項化(例如使用list函式或進行for迴圈)的時候,它並不會產生一個倒序的列表。

4字典

字典(dict)可能是python內建資料結構中最重要的。它更為常用的名字是雜湊表或者是關聯陣列。字典擁有鍵值對集合,其中鍵和值都是python物件。字典表示方法如下:

empty_dict = {}
d1 = {'a':'some value','b':[1,2,3,4]}
print(d1)  #{'a': 'some value', 'b': [1, 2, 3, 4]}

我們也可以訪問、插入或設定字典中的元素,就和訪問列表和元祖中的元素一樣:

empty_dict = {}
d1 = {'a':'some value','b':[1,2,3,4]}
print(d1)  #{'a': 'some value', 'b': [1, 2, 3, 4]}
d1[6] = 'an integer'
print(d1) #{'a': 'some value', 'b': [1, 2, 3, 4], 6: 'an integer'}
print(d1['b']) #[1, 2, 3, 4]

我們也可以用檢查列表或元祖中是否含有一個元素的相同語法來檢查字典是否含有一個鍵:

print('b' in d1) #True

我們也可以使用del關鍵字或pop方法刪除值,pop方法會在刪除的同時返回被刪的值,並刪除鍵:

empty_dict = {}
d1 = {'a':'some value','b':[1,2,3,4]}
d1[5] = 'some value'
d1['dummy'] = 'another value'
print(d1) #{'a': 'some value', 'b': [1, 2, 3, 4], 5: 'some value', 'dummy': 'another value'}
del d1[5]
print(d1) #{'a': 'some value', 'b': [1, 2, 3, 4], 'dummy': 'another value'}
ret = d1.pop('dummy')
print(ret) #another value
print(d1) #{'a': 'some value', 'b': [1, 2, 3, 4]}

keys方法和values方法會分別提供字典鍵、值的迭代器。但是鍵值對並沒有特定的順序,這些函式輸出的鍵、值都是按照相同的順序。
我們也可以使用update方法將兩個字典合併:

empty_dict = {}
d1 = {'a':'some value','b':[1,2,3,4],7:'an integer'}
d1.update({'b':'foo','c':12})
print(d1) #{'a': 'some value', 'b': 'foo', 7: 'an integer', 'c': 12}

如果傳給update方法的資料也含有相同的鍵,那麼它的值將會被覆蓋。

4.1從序列生成字典

通常情況下,我會有兩個序列想要在字典中按元素配對:

mapping = {}
for key,value in zip(key_list,value_list):
	mapping[key] = value

由於字典本質上是含有2個元素的元祖的集合,字典是可以接受一個2-元祖的列表作為引數的:

mapping = dict(zip(range(5),reversed(range(5))))
print(mapping) #{0: 4, 1: 3, 2: 2, 3: 1, 4: 0}

4.2預設值

通常情況下會有這樣的程式碼邏輯:

if key in some_dict:
    value = some_dict[key]
else:
    value = default_value

上述的if-else程式碼塊也可以被簡寫為:

value = some_dict.get(key,default_value)

將字片語成的列表根據首字母分類為包含列表的字典的程式碼實現有以下三種方式:

words = ['apple','bat','bar','atom','book']
by_letter = {}
for word in words:
    letter = word[0]
    if letter not in by_letter:
        by_letter[letter] = [word]
    else:
        by_letter[letter].append(word)
print(by_letter)  #{'a': ['apple', 'atom'], 'b': ['bat', 'bar', 'book']}
words = ['apple','bat','bar','atom','book']
by_letter = {}
for word in words:
    letter = word[0]
    by_letter.setdefault(letter,[]).append(word)
print(by_letter)  #{'a': ['apple', 'atom'], 'b': ['bat', 'bar', 'book']}

內建的集合模組有一個類defaultdict,這個類使得上述實現更為簡單:

words = ['apple','bat','bar','atom','book']
by_letter = {}
from collections import defaultdict
by_letter = defaultdict(list)
for word in words:
    by_letter[word[0]].append(word)
print(by_letter)  #defaultdict(<class 'list'>, {'a': ['apple', 'atom'], 'b': ['bat', 'bar', 'book']})

4.3有效的字典鍵型別

儘管字典的值可以是任何python物件,但鍵必須是不可變的物件,比如標量型別(整數、浮點數、字串)或元祖(元祖內物件也必須是不可變物件)。通過hash函式可以檢查一個物件是否可以雜湊化(即是否可以用作字典的鍵):

print(hash((1,2,[2,3])))
Traceback (most recent call last):
  File "C:/Users/asus/PycharmProjects/資料分析/3.1資料結構和序列.py", line 159, in <module>
    print(hash((1,2,[2,3])))
TypeError: unhashable type: 'list'

5集合

集合是一種無序且元素唯一的容器。集合可以有兩種建立方式:通過set函式或者是用字面值集與大括號的語法:

a = set([2,2,2,1,3,3])
print(a) #{1, 2, 3}
print({1,2,2,3,4,5,5}) #{1, 2, 3, 4, 5}

集合支援數學上的集合操作,python集合操作如下表所示:

函式 描述
a.add(x) 將x元素加入集合a
a.clear() 將集合重置為空,清空所有元素
a.remove(x) 從集合a移除某個元素
a.pop() 移除任意元素,如果集合是空的丟擲keyError
a.union(b) 兩個集合的聯合就是兩個集合中不同元素的並集
a.update(b) 將a的內容設定為a和b的並集
a.intersection(b) a、b中同時包含的元素
a.difference(b) 在a不在b的元素
a.difference_update(b) 將a的內容設為在a不在b的元素
a.issubset(b) 如果a包含於b返回True
a.issuperset(b) 如果a包含b返回True
a.isdisjoint(b) a、b沒有交集返回True

和字典類似,集合的元素必須是不可變的。如果想要包含列表型的元素,必須先轉換為元祖:

my_data = [1,2,3,4]
my_set = {tuple(my_data)}
print(my_set) #{(1, 2, 3, 4)}

當且僅當兩個集合的內容一模一樣時,兩個集合才相等:

print({1,2,3} == {3,2,1}) #True

6列表、集合和字典的推導式

列表推導式的基本形式為:
[expr for val in collection if condition]
這條語句與下面的for迴圈是等價的:

result = []
for val in collection:
	if condition:
		result.append(expr)

例如,給定一個字串列表,我們可以過濾出長度大於2的,並且將字母改為大寫:

strings = ['a','as','bat','car','dove','python']
print([x.upper() for x in strings if len(x) > 2]) #['BAT', 'CAR', 'DOVE', 'PYTHON']

集合推導式看起來很像列表推導式,只是中括號變成了大括號:
set_comp = {expr for value in collection if condition}

strings = ['a','as','bat','car','dove','python']
unique_lengths = {len(x) for x in strings}
print(unique_lengths) #{1, 2, 3, 4, 6}

使用map函式可以使得程式碼更為簡潔:

strings = ['a','as','bat','car','dove','python']
print(set(map(len,strings))) #{1, 2, 3, 4, 6}

6.1巢狀列表推導式

all_data = [['John','Emily','Michael','Mary','Steven'],
            ['Maria','Juan','Javier','Natalia','Pilar']]
names_of_interest = []
for names in all_data:
    enough_es = [name for name in names if name.count('e') >= 2]
    names_of_interest.append(enough_es)

等價於:

all_data = [['John','Emily','Michael','Mary','Steven'],
            ['Maria','Juan','Javier','Natalia','Pilar']]
result = [name for names in all_data for name in names if name.count('e') >= 2]
print(result) #['Steven']
some_tuples = [(1,2,3),(4,5,6),(7,8,9)]
flattened = [x for tup in some_tuples for x in tup]
print(flattened) #[1, 2, 3, 4, 5, 6, 7, 8, 9]

等價於:

some_tuples = [(1,2,3),(4,5,6),(7,8,9)]
flattened = []
for tup in some_tuples:
    for x in tup:
        flattened.append(x)
print(flattened) #[1, 2, 3, 4, 5, 6, 7, 8, 9]