??數據結構,是指通過某種方式(例如對元素進行編號)組織在一起的數據元素的集合,這些數據元素可以是數字或者字符,甚至可以是其他數據結構。在Python中,最基本的數據結構是序列(sequence)。序列中的每個元素被分配一個序列號,即元素的位置,也稱為索引。索引從0開始。
2.1 序列概述
??在Python中包含6中內建的序列,重點討論最常用的兩種類型:列表和元組。其他內建序列有字符串、Unicode字符串、buffer對象和xrange對象。
??列表和元組的主要區別在于,列表可以修改,元組則不能。也就是說如果要根據要求來添加元素,那么列表可能會更好用;而處于某些原因,序列不能修改的時候,使用元組則更為合適。一般來說,在幾乎所有的情況下列表都可以替代元組(使用元組作為字典的鍵,在這種情況下,因為鍵不可修改,所以就不能使用列表)。在需要操作一組數值的時候,序列很好用。可以用序列表示數據庫中一個人的信息,第一個元素表示姓名,第二個元素表示年齡,如下所示:
>>>edward=['Edward Gumby',42]
??同時序列也可以包含其他序列,因此可以構建如下的一個人員信息列表:
>>>edwrad=['Edward Gumby',42]
>>>john=['John Smith',50]
>>>database=[edward,john]
>>>database
[['Edward Gumby',42],['John Smith',50]]
注意:Python之中還有一種名為容器(container)的數據結構。容器基本上是包含其他對象的任意對象。序列(例如列表和元組)和映射(例如字典)是兩類主要的容器。序列中的每個元素都有自己的編號,而映射中的每個元素則有一個名字(也稱為鍵)。
2.2 通用序列操作
??所有的序列類型都可以進行某些特定的操作。這些操作包括:索引(indexing)、分片(sliceing)、加(adding)、乘(multiplying)以及檢查某個元素是否屬于序列的成員(成員資格)。除此之外,Python還有計算序列長度、找出最大元素和最小元素的內建函數。
??序列中的所有元素都是有編號的(從0遞增)。這些元素可以通過編號分別訪問,如下例所示:
>>>greeting='Hello'
>>>greeting[0]
'H'
??這就是索引。可以通過索引獲取元素,所有序列都可以通過這種方法進行索引。使用負數索引時,Python會從右邊,也就是從最后1個元素開始計數。最后一個元素的位置編號為-1(不是-0,因為那會和第一個元素重合):
>>>greeting[-1]
'o'
??字符串字面值能夠直接使用索引,而不需要一個變量引用它們。效果是一樣的,如下所示:
>>>'Hello'[1]
'e'
??如果一個函數返回一個序列,那么可以直接對返回金額過進行索引操作,如下所示:
>>>fourth=raw_input('Year:')[3]
Year: 2005
>>>fourth
'5'
??與使用索引來訪問單個元素類似,可以使用分片操作來訪問一定范圍內的元素。分片通過冒號相隔的兩個索引來實現:
>>>tag='<a >Python web site</a>'
>>>tag[9:30]
'http://www.python.org'
>>>tag[32:-4]
'Python web site'
??分片操作對于提取序列的一部分是很有用的。而編號在這里尤為重要。第一個索引是需要提取部分的第一個元素的編號,而最后索引則是分片之后剩下部分的第一個元素的編號:
>>>numbers=[1,2,3,4,5,6,7,8,9,10]
>>>numbers[3:6]
[4,5,6]
>>>numbers[0:1]
[1]
??簡而言之,分片操作的實現需要提供兩個索引作為邊界,第一個索引的元素是包含在分片內的,而第二個則不包含在分片內。
??如果分片所得部分包括序列結尾的元素,那么,只需置空最后一個索引即可:
>>>numbers[-3:]
[8,9,10]
同樣,如果分片所得部分包含序列開始的元素,只需置空第一個索引:
>>>numbers[:3]
[1,2,3]
實際上,如果需要復制整個序列,可以將兩個序列都置空:
>>>numbers[:]
[1,2,3,4,5,6,7,8,9,10]
??進行分片的時候,分片的起始點和結束點需要進行指定(不管是直接還是間接)。而另外一個參數(步長)通常都是隱式設置的。在普通的分片中,步長是1,分片操作就是按照這個步長逐個遍歷序列的元素,然后返回開始和結束點之間的所有元素:
#與numbers[0:10]作用一樣,步長默認是1
>>>numbers[0:10:1]
[1,2,3,4,5,6,7,8,9,10]
??在這個例子中,分片包含了另外一個數字,即步長。如果步長設置成比1大的數,那么就會跳過某些元素:
>>>numbers[0:10:2]
[1,3,5,7,9]
>>>numbers[3:6:3]
[4]
??如果需要將每4個元素的第一個提取出來,那么只要將步長設置為4即可:
>>>numbers[::4]
[1,5,9]
??注意,步長不能為0,那不會往下執行,但步長可以是負數,即從右到左提取元素:
>>>numbers[8:3:-1]
[9,8,7,6,5]
>>>numbers[10:0:-2]
[10,8,6,4,2]
>>>numbers[0:10:-2]
[]
??注意,當時用一個負數作為步長時,必須讓開始點大于結束點。否則返回一個空列表。
??通過使用加號可以進行序列的連接操作:
>>>[1,2,3]+[4,5,6]
[1,2,3,4,5,6]
>>>'Hello,'+'world!'
'Hello,world!'
??兩種相同類型的序列才能進行連接操作。如列表和字符串是無法連接在一起的,盡管他們都是序列。
??用用數字x乘以一個序列會生成新的序列,而在新的序列中,原來的序列將被重復x次:
>>>'python'*5
'pythonpythonpythonpythonpython'
>>>[42]*10
[42,42,42,42,42,42,42,42,42,42]
??空列表可以簡單地通過兩個中括號表示[],里面什么都沒有。但是如果想創建一個占用十個元素空間,卻不包括任何有用內容的列表,可以像前面那樣使用[42]10,或者使用[0]10,這會更加實際一些。這就生成了一個包括10個00的列表。可以通過下面的方式來初始化一個長度為10的空列表:
#None是Python的內建值,表示空
>>>sequence=[None]*10
>>>sequence
[None,None,None,None,None,None,None,None,None,None]
??為了檢查一個值是否在序列中,可以使用in運算符。該運算符和之前討論過的有一點不同。這個運算符檢查某個條件是否為真,然后返回相應的值:條件為真返回True,條件為假返回False。這樣的運算符叫做布爾運算符,而真值則叫做布爾值:
>>>permissions='rw'
>>>'w' in permissions
True
>>>'x' in permissions
False
>>>users=['mlh','foo','bar']
>>>raw_input('Enter your user name: ') in users
Enter your user name: mlh
True
>>>subject='$$$ Get rich now!!! $$$'
>>>'$$$' in subjectt
True
??下面的例子查看用戶輸入的用戶名和PIN碼是否存在于數據中:
database=[['albert','1234'],['dilbert','4242'],['smith','7524'],['jones','9843']]
username=raw_input('User name: ')
pin=raw_input('PIN code: ')
if [username,pin] in database:
print 'Access granted'
??內建函數len、min和max非常有用。len函數返回序列中所包含元素的數量,min函數和max函數則分別返回序列中最大和最小的元素:
>>>numbers=[100,34,678]
>>>len(numbers)
3
>>>max(numbers)
678
>>>min(numbers)
34
>>>max(2,3)
3
>>>min(9,3,2,5)
2
??從上面的例子中,可以看出,max和min函數的參數并不是一個序列,而是以多個數字直接作為參數。
??列表和元組、字符串不同在于列表是可變的,即可以改變列表的內容,并且列表有很多有用的專門的方法。
??因為字符串不能像列表一樣被修改,所以有時根據字符串創建列表會很有用。list函數可以實現這個操作:
>>>list('Hello')
['H','e','l','l','o']
??注意,list函數適用于所有類型的序列,而不只是字符串。可以用下面的表達式將一個由字符組成的列表轉換為字符串:
#somelist是需要轉換的列表
''.join(somelist)
??列表可以使用所有適用于列表的標準操作,例如索引、分片、連接和乘法。有趣的是,列表是可以修改的。改變列表是很容易的,只需要使用普通賦值語句即可。然而,我們并不會使用x=2這樣的語句進行賦值,而是使用索引標記來為某個特定的、位置明確的元素賦值,如x[1]=2:
>>>x=[1,1,1]
>>>x[1]=2
>>>x
[1,2,1]
??注意,不能為一個位置不存在的元素進行賦值。
??從列表中刪除元素也很容易,使用del語句來實現:
>>>names=['Alice','Beth','Cecil','Dee-Dee','Earl']
>>>del names[2]
>>>names
['Alice','Beth','Dee-Dee','Earl']
??刪除后,列表的長度也從5變成了4。除了刪除列表中的元素,該語句還能用于刪除其他元素。它可以用于字典元素甚至是其他變量的刪除操作。
??使用分片賦值時,可以使用與原序列不等長的序列將分片替換:
>>>name=list('Perl')
>>>name
['P','e','r','l']
>>>name[1:]=list('ar')
>>>name
['P','e','a','r']
>>>demo=list('Perl')
>>>name[1:]=list('ython')
>>>name
['P','y','t','h','o','n']
??分片復制語句可以在不需要替換任何原有元素的情況下插入新的元素:
>>>numbers=[1,5]
>>>numbers[1:1]=[2,3,4]
>>>numbers
[1,2,3,4,5]
??這個程序只是替換了一個空的分片,因此實際的操作是插入了一個序列。因此,通過分片復制來刪除元素也是可以的:
>>>numbers=[1,2,3,4,5]
>>>numbers[1:4]=[]
>>>numbers
[1,5]
??列表方法:方法是一個與某些對象有緊密聯系的函數,對象可能是列表/數字,也可能是字符串或者其他類型的對象。一般來說,方法可以這樣進行調用:
對象.方法(參數)
??除了對象被放置在方法名之前,并且兩者之間有一個點號隔開,方法調用和函數調用很類似。
-
append:append方法用于在列表末尾追加新的對象:
lst=[1,2,3]
lst.append(4)
lst
[1,2,3,4] -
count:count方法統計某個元素在列表中出現的次數:
['to','be','or','not','to','be'].count('to')
2
x=[[1,2],1,1,[2,1,[1,2]]]
x.count(1)
2
x.count([1,2])
1 -
extend:extend方法可以在列表的末尾一次性追加另一個序列中的多個值。換句話說,可以用新列表擴展原有的列表:
a=[1,2,3]
b=[4,5,6]
a.extend(b)
a
[1,2,3,4,5,6]
??這個操作很像連接操作,兩者最主要的區別在于:extend方法些改了被擴展的序列(在這個例子中,也就是a),而原始的連接操作則不然,它會返回一個全新的列表:
>>>a=[1,2,3]
>>>b=[4,5,6]
>>>a+b
[1,2,3,4,5,6]
>>>a
[1,2,3]
-
index:index方法用于從列表中找出某個值第一個匹配項的索引位置:
knights=['we','are','the','knights','who','say','ni']
knights.index('who')
4
knights.index('herring')
ValueError:list.index(x):x not in list -
insert:insert方法用于將對象插入到列表中:
numbers=[1,2,3,4,5,6,7]
numbers.insert(3,'four')
numbers
[1,2,3,'four',5,6,7] -
pop:pop方法會移除列表中的一個元素(默認是最后一個),并且返回該元素的值:
x=[1,2,3]
x.pop()
3
x
[1,2]
x.pop(0)
1
x
[2]
??pop方法是唯一一個既能修改列表又返回元素值的列表方法。使用pop方法可以實現的一種常用的數據結構--棧。棧的原理就像堆放盤子那樣。只能在頂部放一個盤子,同樣,也只能從頂部拿走一個盤子。最后被放入堆棧的最先被移除(這個原則稱為LIFO,即后進先出)。
??對于上述兩個棧操作(放入和移除),即入棧(push)和出棧(pop)。Python沒有入棧方法,但可以使用append方法來代替。pop方法和append方法的操作結果恰好相反,如果入棧剛剛出棧的值,最后得到的結果還是原來的棧。
>>>x=[1,2,3]
>>>x.append(x.pop())
>>>x
[1,2,3]
-
remove:remove方法用于移除列表中某個值的第一個匹配項:
x=['to','be','or','not','to','be']
x.remove('be')
x
['to','or','not','to','be']
??可以看到,只有第一次出現的值被移除了,而不存在于列表中的值是不會移除的。值得注意的是,remove是一個沒有返回值的原位置改變方法。它修改了列表卻沒有返回值,這與pop方法相反。
-
reverse:reverse方法將列表中的元素反向存放:
x=[1,2,3]
x.reverse()
x
[3,2,1]
??注意,該方法也改變了列表但不返回值(就像remove和sort)。如果需要對一個序列進行反向迭代,那么可以使用reverse函數,這個函數并不返回一個列表,而是返回一個迭代器(iterator)對象。盡管如此,使用list函數把返回的對象轉換成列表也是可行的。
>>>x=[1,2,3]
>>>list(reverse(x))
[3,2,1]
-
sort:sort方法用于在原位置對列表進行排序。在"原位置排序"意味著改變原來的列表,從而讓其中的元素能按一定的順序排列,而不是簡單地返回一個已排序的列表副本。
x=[4,5,2,1,7,9]
x.sort()
x
[1,2,4,6,7,9]
??前面介紹過了幾個改變列表卻不返回值的方法,在大多數情況下這樣的行為方式是很合常理的(例如append方法)。當用戶需要一個排好序的列表副本,同時又保留原有列表不變的時候,問題就出現了。
??如果希望元素能按特定的方式進行排序,那么可以通過compare(x,y)的形式自定義比較函數。compare(x,y)函數會在x<y時返回負數,在x>y時返回正數,如果x=y則返回0.定義好該函數后,就可以提供給sort方法作為參數了。內建函數cmp提供了比較函數的默認實現方式:
>>>cmp(42,32)
1
>>>cmp(99,100)
-1
>>>cmp(10,10)
0
>>>numbers=[5,2,9,7]
>>>numbers.sort(cmp)
>>>numbers
[2,5,7,9]
??sort方法有另外兩個可選的參數--key和reverse。如果要使用它們,就要通過名字來指定。參數key與cmp類似--必須提供一個在排序過程中使用的函數。然而,該函數并不是直接用來確定對象的大小,而是為每個元素創建一個鍵,然后所有元素根據鍵來排序。因此,如果要根據元素的長度進行排序,那么可以使用len作為鍵函數:
>>>x=['aardvark','abalone','acme','add','aerate']
>>>x.sort(key=len)
>>>x
['add','acme','aerate','abalone','aardvark']
??另外一個關鍵字參數reverse是簡單的布爾值(True或者是False),用來指明列表是否要進行反向排序。
>>>x=[4,6,2,1,7,9]
>>>x.sort(reverse=True)
>>>x
[9,7,6,4,2,1]
??cmp、key、reverse參數都可以用于sorted函數。在多數情況下,為cmp或key提供自定義函數是非常有用的。
??元組和列表一樣,也是序一種序列。唯一不同的是元組不能改變。創建元組的語法如下:
>>>1,2,3
(1,2,3)
??元組是通過圓括號括起來的,空元組可以用沒有包含內容的兩個圓括號來表示:
>>>()
()
??注意,實現包含一個值的元組有點奇特,必須加一個逗號,即使只有一個值:
>>>42
42
>>>42,
(42,)
>>>(42,)
(42,)
-
tuple:tuple函數的功能與list函數基本上是一樣的:以一個序列作為參數并把它轉換為元組。如果參數就是元組,那么該參數就會被原樣返回:
tuple([1,2,3])
x.sort()
>>>x
[1,2,4,6,7,9]
??前面介紹過了幾個改變列表卻不返回值的方法,在大多數情況下這樣的行為方式是很合常理的(例如append方法)。當用戶需要一個排好序的列表副本,同時又保留原有列表不變的時候,問題就出現了。
??如果希望元素能按特定的方式進行排序,那么可以通過compare(x,y)的形式自定義比較函數。compare(x,y)函數會在x<y時返回負數,在x>y時返回正數,如果x=y則返回0.定義好該函數后,就可以提供給sort方法作為參數了。內建函數cmp提供了比較函數的默認實現方式:
>>>cmp(42,32)
1
>>>cmp(99,100)
-1
>>>cmp(10,10)
0
>>>numbers=[5,2,9,7]
>>>numbers.sort(cmp)
>>>numbers
[2,5,7,9]
??sort方法有另外兩個可選的參數--key和reverse。如果要使用它們,就要通過名字來指定。參數key與cmp類似--必須提供一個在排序過程中使用的函數。然而,該函數并不是直接用來確定對象的大小,而是為每個元素創建一個鍵,然后所有元素根據鍵來排序。因此,如果要根據元素的長度進行排序,那么可以使用len作為鍵函數:
>>>x=['aardvark','abalone','acme','add','aerate']
>>>x.sort(key=len)
>>>x
['add','acme','aerate','abalone','aardvark']
??另外一個關鍵字參數reverse是簡單的布爾值(True或者是False),用來指明列表是否要進行反向排序。
>>>x=[4,6,2,1,7,9]
>>>x.sort(reverse=True)
>>>x
[9,7,6,4,2,1]
??cmp、key、reverse參數都可以用于sorted函數。在多數情況下,為cmp或key提供自定義函數是非常有用的。
??元組和列表一樣,也是序一種序列。唯一不同的是元組不能改變。創建元組的語法如下:
>>>1,2,3
(1,2,3)
??元組是通過圓括號括起來的,空元組可以用沒有包含內容的兩個圓括號來表示:
>>>()
()
??注意,實現包含一個值的元組有點奇特,必須加一個逗號,即使只有一個值:
>>>42
42
>>>42,
(42,)
>>>(42,)
(42,)
- tuple:tuple函數的功能與list函數基本上是一樣的:以一個序列作為參數并把它轉換為元組。如果參數就是元組,那么該參數就會被原樣返回:
>>>tuple([1,2,3])
(1,2,3)
tuple('abc')
('a','b','c')
tuple((1,2,3))
(1,2,3)
??元組其實并不復雜--除了創建元組和訪問元組元素之外,也沒有太多其他操作,可以參照其他類型的序列來實現:
>>>x=1,2,3
>>>x[1]
2
>>>x[0:2]
(1,2)
??元組的分片還是元組,就像列表的分片還是列表一樣