1. 程式人生 > >【練習題】第十章--列表(Think Python)

【練習題】第十章--列表(Think Python)

列表

在列表裡面,這些值可以是任意型別的。一個列表中的值一般叫做列表的元素,有時候也叫列表項。

列表內部可以包含一個列表作為元素,這種包含列表的列表也叫做網狀列表:

['spam', 2.0, 5, [10, 20]]

列表元素可修改

和字串不同的是,列表是可以修改的。

列表的索引和字串的索引的格式是一樣的:

• 任意的一個整型表示式,都可以用來作為索引編號。

• 如果你試圖讀取或者寫入一個不存在的列表元素,你就會得到一個索引錯誤 IndexError。

• 如果一個索引是負值,意味著是從列表末尾向前倒序計數查詢相對應的位置。

在列表中也可以使用 in 運算子。

>>> cheeses = ['Cheddar', 'Edam', 'Gouda']
>>> 'Edam' in cheeses
True
>>> 'Brie' in cheeses
False

遍歷一個列表

遍歷一個列表中所有元素的最常用的辦法就是 for 迴圈了。這種 for 迴圈和我們在遍歷一個字串的時候用的是一樣的的語法:

for cheese in cheeses:
    print(cheese)

如果你只是要顯示一下列表元素,上面這個程式碼就夠用了。但如果你還想寫入或者更新這些元素,你還是需要用索引。一般來說,這需要把兩個內建函式 range 和 len 結合起來使用:

for i in range(len(numbers)):
    numbers[i] = numbers[i] * 2

空列表的 for 迴圈中,迴圈體是永遠不會執行的:

for x in []:
    print('This never happens.')

儘管列表中可以辦好另外一個列表,但這種網狀的分支列表依然只會被算作一個元素。所以下面這個列表的長度是4:

['spam', 1, ['Brie', 'Roquefort', 'Pol le Veq'], [1, 2, 3]]

列表運算子

+、*------和字串的一樣

列表切片

在切片運算中,如果你省略了第一個索引,切片就會從頭開始。如果省略了第二個,切片就會一直走到末尾。所以如果你把兩個都省略了,這個切片就是整個列表的一個複製了。

>>> t[:]
['a', 'b', 'c', 'd', 'e', 'f']

列表的方法

t.append():可以在列表末尾新增一個新的元素

t.extend():使用另一個列表做引數,然後把所有的元素新增到一個列表上

t.sort():把列表中的元素從低到高(譯者注:下面的例子中是按照 ASCII 碼的大小從小到大)排列

Map, filter, reduce 列表中最重要的三種運算

reduce:

把一系列列表元素組合成一個單值的運算,也叫做 reduce(這個單詞是縮減的意思)。

sum(t):將t列表中所有元素加起來

>>> t = [1, 2, 3]
>>> sum(t)
6

map:

將某一函式(該例子中是 capitalize 這個方法)應用到一個序列中的每個元素上。

def capitalize_all(t):
    res = []
    for s in t:
        res.append(s.capitalize())#capitalize()-轉換為大寫
    return res

 filter:

從列表中選取一些元素,然後返回一個次級列表。

比如,下面的函式接收一個字串列表,然後返回一個其中只包含大寫字母的字串所組成的列表:

def only_upper(t):
    res = []
    for s in t:
        if s.isupper():
            res.append(s)
    return res

isupper 是一個字串方法,如果字串中只包含大寫字母就會返回真。

only_upper 這樣的運算也叫 filter(過濾器的意思),因為這種運算篩選出特定的元素,過濾掉其他的。

常用的列表運算都可以表示成 map、filter 以及 reduce 的組合。

刪除元素

從一個列表中刪除元素有幾種方法。如果你知道你要刪除元素的索引,你就可以用 pop這個方法來實現:

>>> t = ['a', 'b', 'c']
>>> x = t.pop(1)
>>> t
['a', 'c']
>>> x
'b'

pop 修改列表,然後返回刪除的元素。如果你不指定一個索引位置,pop 就會刪除和返回最後一個元素。

如果你不需要刪掉的值了,你可以用 del 運算子來實現:

>>> t = ['a', 'b', 'c']
>>> del t[1]
>>> t
['a', 'c']

如果你知道你要刪除的元素值,但不知道索引位置,你可以使用 remove 這個方法:

>>> t = ['a', 'b', 'c']
>>> t.remove('b')
>>> t
['a', 'c']

remove 的返回值是空。

要刪除更多元素,可以使用 del 和切片索引

>>> t = ['a', 'b', 'c', 'd', 'e', 'f']
>>> del t[1:5]
>>> t
['a', 'f']

和之前我們看到的一樣,切片是含頭不含尾,上面這個例子中從第『1』到第『5』個都被切片所選中,但包含開頭的第『1』而不包含末尾的第『5』個元素。

列表和字串

字串是一系列字元的序列,而列表是一系列值的序列,但一個由字元組成的列表是不同於字串的。要把一個字串轉換成字元列表,你可以用 list 這個函式:

>>> s = 'spam'
>>> t = list(s)
>>> t
['s', 'p', 'a', 'm']

 想把字串切分成一個個單詞,你可以用 split 這個方法:

>>> s = 'pining for the fjords'
>>> t = s.split()
>>> t
['pining', 'for', 'the', 'fjords']

可選的引數是定界符,是用來確定單詞邊界的。下面這個例子中就是把連線號『-』作為定界符:

>>> s = 'spam-spam-spam'
>>> delimiter = '-'
>>> t = s.split(delimiter)
>>> t
['spam', 'spam', 'spam']

 

join 是與 split 功能相反的一個方法。它接收一個字串列表,然後把所有元素拼接到一起。join 是一個字串方法,所以必須把 join 放到定界符後面來呼叫,並且傳遞一個列表作為引數

>>> t = ['pining', 'for', 'the', 'fjords']
>>> delimiter = ' '
>>> s = delimiter.join(t)
>>> s
'pining for the fjords'

上面這個例子中,定界符是一個空格字元,所以 join 就在單詞只見放一個空格。要想把字元聚集到一切而不要空格,你就可以用空字串""作為一個定界符了。

物件和值

要檢查兩個變數是否指向的是同一個物件,可以用 is 運算子。

>>> a = 'banana'
>>> b = 'banana'#a和b指向兩個不同的物件,這兩個物件有相同的值。在第二個情況下,a 和 b 都指向同一個物件。
>>> a is b
True

當建立兩個列表時,得到的就是兩個物件:

>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> a is b
False

一個物件擁有一個值。如果你計算[1,2,3],你得到一個列表物件,整個列表物件的整個值是一個整數序列。如果另外一個列表有相同的元素,我們稱之含有相同的值,但並不是相同的物件。 

別名

如果 a 是一個物件了,然後你賦值 b=a,那麼這兩個變數都指向同一個物件:

>>> a = [1, 2, 3]
>>> b = a
>>> b is a
True

這兩個變數亦成為物件的引用。所以對其中一個引用的修改,會使得物件發生改變,進而使得另一個引用(即別名)發生改變:

>>> b[0] = 42
>>> a
[42, 2, 3]

列表引數 

當你傳遞一個列表給一個函式的時候,函式得到的是對該列表的一個引用。如果函式修改了列表,呼叫者會看到變化的。比如下面這個 delete_head 函式就從列表中刪除第一個元素:

def delete_head(t):
    del t[0]

其用法如下:

>>> letters = ['a', 'b', 'c']
>>> delete_head(letters)
>>> letters ['b', 'c']

形式引數 t 和變數 letters 都是同一物件的別名。

一定要區分修改列表的運算和產生新列表的運算,這特別重要。比如 append 方法修改一個列表,但加號+運算子是產生一個新的列表

>>> t1 = [1, 2]
>>> t2 = t1.append(3)
>>> t1
[1, 2, 3]
>>> t2
None

append 修改了列表,返回的是空。

>>> t3 = t1 + [4]
>>> t1
[1, 2, 3]
>>> t3
[1, 2, 3, 4]
>>> t1
[1, 2, 3]

加號+運算子建立了新的列表,並不修改源列表。

加以區別相當重要,尤其是當你寫一些要修改列表的函式的時候。比如下面這個函式並沒有能夠刪除列表的第一個元素:

def bad_delete_head(t):
    t = t[1:]              # WRONG!

切片運算子建立了新的列表,然後賦值語句讓 t 指向了這個新列表,但這不會影響呼叫者:

>>> t4 = [1, 2, 3]
>>> bad_delete_head(t4)
>>> t4
[1, 2, 3]

在bad_delete_head這個函式開始執行的時候,t 和 t4指向同一個列表。在結尾的時候,t 指向了新的列表(此列表為t4[1:]),但 t4依然還是原來那個列表,而且沒修改過。

一種替代方法是寫一個能建立並返回新列表的函式。比如 tail 這個函式就返回列表除了首個元素之外的其他所有元素:

def tail(t):
    return t[1:]

這個函式會將源列表保持原樣不做修改。下面是用法:

>>> letters = ['a', 'b', 'c']
>>> rest = tail(letters)
>>> rest
['b', 'c']

 除錯

1. 大多數列表方法都修改引數,返回空值。這正好與字串方法相反,字串的方法都是返回一個新字串,保持源字串不變。

如果你習慣些下面這種字串程式碼了:

word = word.strip()

你估計就會寫出下面這種列表程式碼:

t = t.sort()           # WRONG!

因為 sort 返回的是空值,所以對 t 的後續運算都將會失敗。

在使用列表的方法和運算子之前,你應該好好讀一下文件,然後在互動模式裡面對它們進行一下測試。

2. 選一種方法並堅持使用。

列表使用的問題中很大一部分都是因為有太多方法來實現目的導致的。例如要從一個列表中刪除一個元素,可以用 pop,remove,del 甚至簡單的切片操作。

要加一個元素,可以用 append 方法或者加號+運算子。假設 t 是一個列表,而 x 是一個列表元素,下面的程式碼都是正確的:

t.append(x)
t = t + [x]
t += [x]

下面這就都是錯的了:

t.append([x])          # WRONG!
t = t.append(x)        # WRONG!
t + [x]                # WRONG!
t = t + x              # WRONG!

在互動模式下試試上面這些例子,確保你要理解它們的作用。要注意只有最後一個會引起執行錯誤,其他的三個都是合法的,但產生錯誤的效果。

3. 儘量做備份,避免用別名。

如果你要用 sort 這樣的方法來修改引數,又要同時保留源列表,你可以先做個備份。

>>> t = [3, 1, 2]
>>> t2 = t[:]
>>> t2.sort()
>>> t
[3, 1, 2]
>>> t2
[1, 2, 3]

在這個例子中,你也可以用內建函式sorted,這個函式會返回一個新的整理過的列表,而不會影響源列表。

>>> t2 = sorted(t)
>>> t
[3, 1, 2]
>>> t2
[1, 2, 3]

練習1 :

寫一個函式,名為 nested_sum,接收一系列的整數列表,然後把所有分支列表中的元素加起來。如下所示:

>>> t = [[1, 2], [3], [4, 5, 6]]
>>> nested_sum(t)
21

mydode (我這個厲害一點,任何整數型網狀列表都可以累加):

def nested_sum(t):
    a=t[:]
    for i in range(len(a)):
        if isinstance(a[i],list):
             a[i]=nested_sum(a[i])
    return sum(a)

t=[[1,2],[1,2],2,3]
print(nested_sum(t))

練習2:

寫一個函式,明切 cumsum,接收一個數字列表,然後返回累加的總和;也就是新列表的第 i 個元素就是源列表中前 i+1個元素的累加。如下所示:

>>> t = [1, 2, 3]
>>> cumsum(t)
[1, 3, 6]

 mycode:

def cumsum(t):
    for i in range(len(t)):
        t[i]=sum(t[:i+1])

練習3 :

寫一個函式,名為 middle,接收一個列表,返回一個新列表,新列表要求對源列表掐頭去尾只要中間部分。如下所示:

>>> t = [1, 2, 3, 4]
>>> middle(t)
[2, 3]

mycode:

def middle(t):
    n=len(t)
    return t[1:(n-1)]

練習4:

寫一個函式,名為 chop,接收一個列表,修改這個列表,掐頭去尾,返回空值。如下所示:

>>> t = [1, 2, 3, 4]
>>> chop(t)
>>> t
[2, 3]

 mycode:

def chop(t):
    del t[0]
    del t[-1]
    return None

練習5:

寫一個函式,名為 is_sorted,接收一個列表作為引數,如果列表按照字母順序升序排列,就返回真,否則返回假。如下所示:

>>> is_sorted([1, 2, 2])
True
>>> is_sorted(['b', 'a'])
False

mycode:

def is_sorted(t):
    a=t[:]
    a.sort()
    if t==a:
        return True
    else:
        return False

練習6:

兩個單詞如果可以通過順序修改來互相轉換就互為變位詞。寫一個函式,名為 is_anagram,接收兩個字串,如果互為變位詞就返回真

def is_anagram(word1,word2):
    word1_dup=list(word1)
    word2_dup=list(word2)
    word1_dup.sort()
    word2_dup.sort()
    if word1_dup==word2_dup:
        return True
    else:
        return False

練習7:

寫一個函式,名為 has_duplicates,接收一個列表,如果裡面有重複出現的元素,就返回真。這個函式不能修改源列表。

mycode:

def has_duplicates(t):
    for i in t:
        if t.count(i)>1:
            return True
    return False

附一個大神的總結:Python統計列表元素出現次數--https://blog.csdn.net/weixin_40604987/article/details/79292493

練習8:

假如你班上有23個學生,這當中兩個人同一天出生的概率是多大?你可以評估一下23個隨機生日中有相同生日的概率。提示一下:你可以用 randint 函式來生成隨機的生日,這個函式包含在 random 模組中。

mycode:

def random_bdays(n):
    t=[]
    for i in range (n):
        bday=random.randin(1,365)
        t.append(bday)
    return t

andanswer:

def count_matches(num_students, num_simulations):
    """Generates a sample of birthdays and counts duplicates.

    num_students: how many students in the group
    num_samples: how many groups to simulate

    returns: int
    """
    count = 0
    for i in range(num_simulations):
        t = random_bdays(num_students)
        if has_duplicates(t):
            count += 1
    return count


def main():
    """Runs the birthday simulation and prints the number of matches."""
    num_students = 23
    num_simulations = 1000
    count = count_matches(num_students, num_simulations)

    print('After %d simulations' % num_simulations)
    print('with %d students' % num_students)
    print('there were %d simulations with at least one match' % count)


練習9:

寫一個函式,讀取檔案 words.txt,然後建立一個列表,這個列表中每個元素就是這個檔案中的每個單詞。寫兩個版本的這樣的函式,一個使用 append 方法,另外一個用自增運算的模式:t= t + [x]。看看哪個執行時間更長?為什麼會這樣?

mycode:

import time
def read_append(txt):
    fin=open(txt)
    t=[]
    for word in fin:
        t.append(word.strip())
    return t

def read_incre(txt):
    fin=open(txt)
    t=[]
    for word in fin:
        t=t+[word.strip()]
    return t

txt='words.txt'
fin=open(txt)
t0=time.time()
read_append(txt)
print('time for append:',(time.time()-t0))
t1=time.time()
read_incre(txt)
print('time for incre:',(time.time()-t1))

原因解析:

append()  :向列表尾部追加一個新元素,列表只佔一個索引位,在原有列表上增加。

+  :實際上是生成了一個新的列表存這兩個列表的和,只能用在兩個列表相加上。所以它的耗時需要很長!

+=:和append()差不多,但慢一些

練習10:

要檢查一個單詞是不是在上面這個詞彙列表裡,你可以使用 in 運算子,但可能會很慢,因為這個 in 運算子要從頭到尾來搜尋整個詞彙表。

我們知道這些單詞是按照字母表順序組織的,所以我們可以加速一下,用一種對摺搜尋(也叫做二元搜尋),這個過程就和你在現實中用字典來查單詞差不多。你在中間部分開始,看看這個要搜尋的詞彙是不是在中間位置的前面。如果在前面,就又對前半部分取中間,繼續這樣來找。當然了,不在前半部分,就去後半部分找了,思路是這樣的。

不論怎樣,每次都會把搜尋範圍縮減到一半。如果詞表包含了113809個單詞,最多就是17步就能找到單詞,或者能確定單詞不在詞彙表中。

那麼問題來了,寫一個函式,名為 in_bisect,接收一個整理過的按照字母順序排列的列表,以及一個目標值,在列表中查詢這個值,找到了就返回索引位置,找不到就返回空。

mycode:

def in_bisect(t,word):
    first=0
    last=len(t)-1
    print('last is '+str(last))
    i=int((first+last)/2)
    count=0
    while word!=t[i]:
        if i==first:
            return 'None'
        if word<t[i]:
            last=i
            i=int((first+last)/2)
        elif word>t[i]:
            first=i
            i=int((first+last)/2)
        count=count+1
    return i

fin=open('words.txt')
words=fin.read()
t=words.split('\n')
word='zz'
print('the word--'+word+' is '+str(in_bisect(t,word)))

練習11:

兩個詞如果互為逆序,就稱它們是『翻轉配對』。寫一個函式來找一下在這個詞彙表中所有這樣的詞對。

def reverse_pair(word_list, word):
    rev_word = word[::-1]
    return in_bisect(word_list, rev_word)
    
fin=open('words.txt')
t=(fin.read()).split('\n')
for word in t:
    if reverse_pair(t,word)!='None':
        print(word)

練習12:

兩個單詞,依次拼接各自的字母,如果能組成一個新單詞,就稱之為『互鎖』。比如,shoe 和 cold就可以鑲嵌在一起組成組成 schooled。(譯者注:shoe+cold= schooled樣例程式碼. 說明:這個練習受到了這裡一處例子的啟發。

  1. 寫一個程式,找到所有這樣的互鎖單詞對。提示:不要列舉所有的單詞對!

  2. 你能找到那種三路互鎖的單詞麼;就是那種三三排列三個單詞的字母能組成一個單詞的三個詞?