# 第十章 列表
本章講述的是 Python 里面最有用的一種內置類型:列表。你還能在本章中學到更多關于類的內容,你還會看到如果同一個對象有多個名字會有什么情況發生。
## 10.1 列表也是序列
和字符串差不多,列表是一系列的數值的序列。在字符串里面,這些值是字符;在列表里面,這些值可以是任意類型的。一個列表中的值一般叫做列表的元素,有時候也叫列表項。
創建一個新的列表有好幾種方法;最簡單的方法就是把列表的元素用方括號包含起來:
```py
[10, 20, 30, 40]
['crunchy frog', 'ram bladder', 'lark vomit']
```
第一個例子建立了一個由四個整形變量組成的列表。第二個是一個由三個字符串組成的列表。
```py
['spam', 2.0, 5, [10, 20]]
```
列表內部可以包含一個列表作為元素,這種包含列表的列表也叫做網狀列表。
不含有任何元素的列表叫做空列表;可以就用空的方括號來建立一個。
你估計也會預料到,列表的值可以賦給變量:
```py
>>> cheeses = ['Cheddar', 'Edam', 'Gouda']
>>> numbers = [42, 123]
>>> empty = []
>>> print(cheeses, numbers, empty)
['Cheddar', 'Edam', 'Gouda']
[42, 123]
[]
```
## 10.2 列表元素可修改
讀取列表元素的語法就如同讀取字符串中的字符一樣-用方括號運算符就可以了。方括號內的數字用來確定索引位置。一定要記住,Python 是從零開始計數的:
```py
>>> cheeses[0]
'Cheddar'
```
和字符串不同的是,列表是可以修改的。方括號運算符放到一個賦值語句的等號左側的時候,就會把對應位置的列表元素重新賦值。
```py
>>> numbers = [42, 123]
>>> numbers[1] = 5
>>> numbers
[42, 5]
```
列表 numbers 的第『1』個元素之前是 123,現在被改為 5 了。
圖 10.1 展示了 cheeses、numbers 和空列表的狀態圖:
________________________________________

Figure 10.1: State diagram.
________________________________________
這三個列表都用標記了『list』的三個小盒子表示,盒子外面的是列表的名字,盒子內的就是列表元素了。cheese 是一個有 0,1,2 三個元素的列表。numbers 包含兩個元素;圖示表明第二個元素的值從 123 被重新賦值為 5。empty 是一個不包含元素的空列表。
列表的索引和字符串的索引的格式是一樣的:
? 任意的一個整型表達式,都可以用來作為索引編號。
? 如果你試圖讀取或者寫入一個不存在的列表元素,你就會得到一個索引錯誤 IndexError。
? 如果一個索引是負值,意味著是從列表末尾向前倒序計數查找相對應的位置。
在列表中也可以使用 in 運算符。
```py
>>> cheeses = ['Cheddar', 'Edam', 'Gouda']
>>> 'Edam' in cheeses
True
>>> 'Brie' in cheeses
False
```
## 10.3 遍歷一個列表
遍歷一個列表中所有元素的最常用的辦法就是 for 循環了。這種 for 循環和我們在遍歷一個字符串的時候用的是一樣的的語法:
```py
for cheese in cheeses:
print(cheese)
```
如果你只是要顯示一下列表元素,上面這個代碼就夠用了。但如果你還想寫入或者更新這些元素,你還是需要用索引。一般來說,這需要把兩個內置函數 range 和 len 結合起來使用:
```py
for i in range(len(numbers)):
numbers[i] = numbers[i] * 2
```
這個循環遍歷了列表,然后對每個元素都進行了更新。len 這個函數返回的是列表中元素的數量。range 返回的是列表的一系列索引,從 0 到 n-1,n 就是整個列表的長度了。每次循環的時候,i 都會得到下一個元素的索引值。在循環體內部的賦值語句每次都通過 i 作為索引來讀該元素的舊值,進行修改然后賦新值給該元素。
空列表的 for 循環中,循環體是永遠不會運行的:
```py
for x in []:
print('This never happens.')
```
盡管列表中可以辦好另外一個列表,但這種網狀的分支列表依然只會被算作一個元素。所以下面這個列表的長度是 4:
```py
['spam', 1, ['Brie', 'Roquefort', 'Pol le Veq'], [1, 2, 3]]
```
## 10.4 列表運算符
加號+運算符可以把列表拼接在一起:
```py
>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> c = a + b
>>> c
[1, 2, 3, 4, 5, 6]
```
星號*運算符可以將列表重復指定的次數:
```py
>>> [0] * 4
[0, 0, 0, 0]
>>> [1, 2, 3] * 3
[1, 2, 3, 1, 2, 3, 1, 2, 3]
```
第一個例子中,[0]這個列表被重復四次。第二個例子把列表[1,2,3]重復了三次。
## 10.5 列表切片
切片操作符也可以用到列表上:
```py
>>> t = ['a', 'b', 'c', 'd', 'e', 'f']
>>> t[1:3]
['b', 'c']
>>> t[:4]
['a', 'b', 'c', 'd']
>>> t[3:]
['d', 'e', 'f']
```
在切片運算中,如果你省略了第一個索引,切片就會從頭開始。如果省略了第二個,切片就會一直走到末尾。所以如果你把兩個都省略了,這個切片就是整個列表的一個復制了。
```py
>>> t[:]
['a', 'b', 'c', 'd', 'e', 'f']
```
因為列表是可以修改的,所以在進行運算修改列表之前,做個復制來備份經常是很有必要的。
切片運算符放到賦值語句中等號左邊的時候可以對多個元素進行更新:
```py
>>> t = ['a', 'b', 'c', 'd', 'e', 'f']
>>> t[1:3] = ['x', 'y']
>>> t
>>> t
['a', 'x', 'y', 'd', 'e', 'f']
```
## 10.6 列表的方法
Python 為操作列表提供了很多方法。比如,append 就可以在列表末尾添加一個新的元素:
```py
>>> t = ['a', 'b', 'c']
>>> t.append('d')
>>> t
['a', 'b', 'c', 'd']
```
extend 使用另一個列表做參數,然后把所有的元素添加到一個列表上。
```py
>>> t1 = ['a', 'b', 'c']
>>> t2 = ['d', 'e']
>>> t1.extend(t2)
>>> t1
['a', 'b', 'c', 'd', 'e']
```
上面這個例子中,t2 是沒有修改過的。
sort 把列表中的元素從低到高(譯者注:下面的例子中是按照 ASCII 碼的大小從小到大)排列:
```py
>>> t = ['d', 'c', 'e', 'b', 'a']
>>> t.sort()
>>> t
['a', 'b', 'c', 'd', 'e']
```
大多數列表的方法都是無返回值的;這些方法都對列表進行修改,返回的是空。如果你不小心寫出了一個 t=t.sort(),得到的結果恐怕讓你很失望。
## 10.7 Map, filter, reduce 列表中最重要的三種運算
要得到列表中所有值的綜合,你可以用下面這樣的一個循環來實現:
```py
def add_all(t):
total = 0
for x in t:
total += x
return total
```
total 的初始值為 0。每次循環的時候,x 都得到列表中一個元素的值。+=這個運算符是更新變量的一種簡寫。這個運算符是擴展了賦值語句,total += x 就等同于 total = total +x 。
隨著循環的運行,total 積累了所有元素的綜合;這種變量也叫做累(三聲)加器。
把列表中所有元素加起來是一種很常用的運算,所以 Python 提供了內置的函數 sum:
```py
>>> t = [1, 2, 3]
>>> sum(t)
6
```
把一系列列表元素組合成一個單值的運算,也叫做 reduce(這個單詞是縮減的意思)。
有時候建立一個列表需要遍歷另一個列表。比如下面的這個函數就接收一個字符串列表,將所有字符串變為大寫字母組成的,然后返回一個這些大寫字符串組成的新列表:
```py
def capitalize_all(t):
res = []
for s in t:
res.append(s.capitalize())
return res
```
res 的初始值為一個空列表;每次循環的時候,我們都把下一個元素用 append 方法拼接上去。所以 res 也算是另外一種累加器了。
像上面這個 capitalize_all 的運算也叫做一個 map(單詞意思為地圖),因為這種預算將某一函數(該例子中是 capitalize 這個方法)應用到一個序列中的每個元素上。
另外一種常見運算是從列表中選取一些元素,然后返回一個次級列表。比如,下面的函數接收一個字符串列表,然后返回一個其中只包含大寫字母的字符串所組成的列表:
```py
def only_upper(t):
res = []
for s in t:
if s.isupper():
res.append(s)
return res
```
isupper 是一個字符串方法,如果字符串中只包含大寫字母就會返回真。
only_upper 這樣的運算也叫 filter(過濾器的意思),因為這種運算篩選出特定的元素,過濾掉其他的。
常用的列表運算都可以表示成 map、filter 以及 reduce 的組合。
## 10.8 刪除元素
從一個列表中刪除元素有幾種方法。如果你知道你要刪除元素的索引,你就可以用 pop 這個方法來實現:
```py
>>> t = ['a', 'b', 'c']
>>> x = t.pop(1)
>>> t
['a', 'c']
>>> x
'b'
```
pop 修改列表,然后返回刪除的元素。如果你不指定一個索引位置,pop 就會刪除和返回最后一個元素。
如果你不需要刪掉的值了,你可以用 del 運算符來實現:
```py
>>> t = ['a', 'b', 'c']
>>> del t[1]
>>> t
['a', 'c']
```
如果你知道你要刪除的元素值,但不知道索引位置,你可以使用 remove 這個方法:
```py
>>> t = ['a', 'b', 'c']
>>> t.remove('b')
>>> t
['a', 'c']
```
remove 的返回值是空。
要刪除更多元素,可以使用 del 和切片索引:
```py
>>> t = ['a', 'b', 'c', 'd', 'e', 'f']
>>> del t[1:5]
>>> t
['a', 'f']
```
和之前我們看到的一樣,切片是含頭不含尾,上面這個例子中從第『1』到第『5』個都被切片所選中,但包含開頭的第『1』而不包含末尾的第『5』個元素。
## 10.9 列表和字符串
字符串是一系列字符的序列,而李白是一系列值的序列,但一個由字符組成的列表是不同于字符串的。要把一個字符串轉換成字符列表,你可以用 list 這個函數:
```py
>>> s = 'spam'
>>> t = list(s)
>>> t
['s', 'p', 'a', 'm']
```
list 是一個內置函數的名字了,所以你應該避免用它來作為變量名。我還建議應該盡量少用 l,因為有的字體下,l 和 1 看著很難區分。所以我都用了 t。
list 這個函數將一個字符串分開成一個個字母。如果你想把字符串切分成一個個單詞,你可以用 split 這個方法:
```py
>>> s = 'pining for the fjords'
>>> t = s.split()
>>> t
['pining', 'for', 'the', 'fjords']
```
可選的參數是定界符,是用來確定單詞邊界的。下面這個例子中就是把連接號『-』作為定界符:
```py
>>> s = 'spam-spam-spam'
>>> delimiter = '-'
>>> t = s.split(delimiter)
>>> t
['spam', 'spam', 'spam']
```
join 是與 split 功能相反的一個方法。它接收一個字符串列表,然后把所有元素拼接到一起。join 是一個字符串方法,所以必須把 join 放到定界符后面來調用,并且傳遞一個列表作為參數:
```py
>>> t = ['pining', 'for', 'the', 'fjords']
>>> delimiter = ' '
>>> s = delimiter.join(t)
>>> s
'pining for the fjords'
```
上面這個例子中,定界符是一個空格字符,所以 join 就在單詞只見放一個空格。要想把字符聚集到一切而不要空格,你就可以用空字符串""作為一個定界符了。
## 10.10 對象和值
如果我們運行下面這種賦值語句:
```py
a = 'banana'
b = 'banana'
```
我們知道了 a 和 b 都是字符串,但我們不之道他們到底是不是同一個字符串。這就有可能有兩種狀態,如圖 10.2 所示。
________________________________________

Figure 10.2: State diagram.
________________________________________
在第一種情況中,a 和 b 指向兩個不同的對象,這兩個對象有相同的值。在第二個情況下,a 和 b 都指向同一個對象。
要檢查兩個變量是否指向的是同一個對象,可以用 is 運算符。
```py
>>> a = 'banana'
>>> b = 'banana'
>>> a is b
True
```
在這個例子中,Python 只建立了一個字符串對象,然后 a 和 b 都指向它。但當你建立兩個列表的時候,你得到的就是兩個對象了:
```py
>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> a is b
False
```
所以這時候的狀態圖就應該如圖 10.3 所示的樣子了。
________________________________________

Figure 10.3: State diagram.
________________________________________
在這個情況下,我們可以說兩個列表是相等的,因為它們有相同的元素,但它們不是同一個列表,因為他們并不是同一個對象。如果兩個對象是同一個對象,那它們必然是相等的,但如果它們相等,卻未必是同一個對象。
(譯者注:同一 是 相等 的充分條件,相等 是 同一 的必要條件,僅此而已。)
目前位置,我們一直把『對象』和『值』來隨意交換使用,但實際上更確切的說法是一個對象擁有一個值。如果你計算[1,2,3],你得到一個列表對象,整個列表對象的整個值是一個整數序列。如果另外一個列表有相同的元素,我們稱之含有相同的值,但并不是相同的對象。
## 10.11 別名
如果 a 是一個對象了,然后你賦值 b=a,那么這兩個變量都指向同一個對象:
```py
>>> a = [1, 2, 3]
>>> b = a
>>> b is a
True
```
此時的狀態圖如圖 10.4 所示。
________________________________________

Figure 10.4: State diagram.
________________________________________
一個變量和一個對象的關系叫做引用。在上面這個例子中,a 和 b 是對同一對象的兩個引用。
這樣一個對象有不止一個引用,就也有了不止一個名字,所以就說這個對象有別名了。
如果一個別名對象是可修改的,那么對一個別名做出的修改就會影響到其他引用:
```py
>>> b[0] = 42
>>> a
[42, 2, 3]
```
這一性質是很有用處的,但很容易讓初學者犯錯。所以一般來說,處理可變對象的時候,還是盡量避免別名使用,這樣更安全些。
對于不可變對象比如字符串來說,別名使用就不是太大問題了。如下所示:
```py
a = 'banana'
b = 'banana'
```
a 和 b 是否指向同一個字符就基本無所謂了,幾乎不會有任何影響。
## 10.12 列表參數
當你傳遞一個列表給一個函數的時候,函數得到的是對該列表的一個引用。如果函數修改了列表,調用者會看到變化的。比如下面這個 delete_head 函數就從列表中刪除第一個元素:
```py
def delete_head(t):
del t[0]
```
其用法如下:
```py
>>> letters = ['a', 'b', 'c']
>>> delete_head(letters)
>>> letters ['b', 'c']
```
形式參數 t 和變量 letters 都是同一對象的別名。棧圖如圖 10.5 所示。
________________________________________

Figure 10.5: Stack diagram.
________________________________________
因為這個列表被兩個框架所公用,我就把它畫在了它們之間。
一定要區分修改列表的運算和產生新列表的運算,這特別重要。比如 append 方法修改一個列表,但加號+運算符是產生一個新的列表:
```py
>>> t1 = [1, 2]
>>> t2 = t1.append(3)
>>> t1
[1, 2, 3]
>>> t2
None
```
append 修改了列表,返回的是空。
```py
>>> t3 = t1 + [4]
>>> t1
[1, 2, 3]
>>> t3
[1, 2, 3, 4]
>>> t1
[1, 2, 3]
```
加號+運算符創建了新的列表,并不修改源列表。
這以區別相當重要,尤其是當你寫一些要修改列表的函數的時候。比如下面這個函數并沒有能夠刪除列表的第一個元素:
```py
def bad_delete_head(t):
t = t[1:] # WRONG!
```
切片運算符創建了新的列表,然后賦值語句讓 t 指向了這個新列表,但這不會影響調用者。
```py
>>> t4 = [1, 2, 3]
>>> bad_delete_head(t4)
>>> t4
[1, 2, 3]
```
在 bad_delete_head 這個函數開始運行的時候,t 和 t4 指向同一個列表。在結尾的時候,t 指向了新的列表,但 t4 依然還是原來那個列表,而且沒修改過。
一種替代方法是寫一個能創建并返回新列表的函數。比如 tail 這個函數就返回列表除了首個元素之外的其他所有元素:
```py
def tail(t):
return t[1:]
```
這個函數會將源列表保持原樣不做修改。下面是用法:
```py
>>> letters = ['a', 'b', 'c']
>>> rest = tail(letters)
>>> rest
['b', 'c']
```
## 10.13 調試
對列表或者其他可變對象,用的不小心的貨,就會帶來很多麻煩,需要好長時間來調試。下面是一些常見的陷阱,以及避免的方法:
### 1. 大多數列表方法都修改參數,返回空值。這正好與字符串方法相反,字符串的方法都是返回一個新字符串,保持源字符串不變。
如果你習慣些下面這種字符串代碼了:
```py
word = word.strip()
```
你估計就會寫出下面這種列表代碼:
```py
t = t.sort() # WRONG!
```
因為 sort 返回的是空值,所以對 t 的后續運算都將會失敗。
在使用列表的方法和運算符之前,你應該好好讀一下文檔,然后在交互模式里面對它們進行一下測試。
### 2. 選一種方法并堅持使用。
列表使用的問題中很大一部分都是因為有太多方法來實現目的導致的。例如要從一個列表中刪除一個元素,可以用 pop,remove,del 甚至簡單的切片操作。
要加一個元素,可以用 append 方法或者加號+運算符。假設 t 是一個列表,而 x 是一個列表元素,下面的代碼都是正確的:
```py
t.append(x)
t = t + [x]
t += [x]
```
下面這就都是錯的了:
```py
t.append([x]) # WRONG!
t = t.append(x) # WRONG!
t + [x] # WRONG!
t = t + x # WRONG!
```
在交互模式下試試上面這些例子,確保你要理解它們的作用。要注意只有最后一個會引起運行錯誤,其他的三個都是合法的,但產生錯誤的效果。
### 3. 盡量做備份,避免用別名。
如果你要用 sort 這樣的方法來修改參數,又要同時保留源列表,你可以先做個備份。
```py
>>> t = [3, 1, 2]
>>> t2 = t[:]
>>> t2.sort()
>>> t
[3, 1, 2]
>>> t2
[1, 2, 3]
```
在這個例子中,你也可以用內置函數 sorted,這個函數會返回一個新的整理過的列表,而不會影響源列表。
```py
>>> t2 = sorted(t)
>>> t
[3, 1, 2]
>>> t2
[1, 2, 3]
```
## 10.14 Glossary 術語列表
list:
A sequence of values.
>列表:一系列值的序列。
element:
One of the values in a list (or other sequence), also called items.
>元素:一個列表或者其他序列中的值,也叫項。
nested list:
A list that is an element of another list.
>網狀列表:一個作為其他列表元素的列表。
accumulator:
A variable used in a loop to add up or accumulate a result.
>累加器:一種用來在循環中累加或者拼接結果的變量。
augmented assignment:
A statement that updates the value of a variable using an operator like+=.
>增強賦值語句:使用+=這種自增運算符來更新變量值的語句。
reduce:
A processing pattern that traverses a sequence and accumulates the elements into a single result.
>reduce:一種處理模式,遍歷一個序列,把元素積累起來結合成一個單獨的結果。
map:
A processing pattern that traverses a sequence and performs an operation on each element.
>map:一種處理模式,遍歷一個序列,對每一個元素都進行某種運算。
filter:
A processing pattern that traverses a list and selects the elements that satisfy some criterion.
>filter:一種處理模式,遍歷一個列表,選取其中滿足特定規則的一些元素。
object:
Something a variable can refer to. An object has a type and a value.
>對象:變量所指向的就是對象。一個對象有特定的某個類型,以及一個值。
equivalent:
Having the same value.
>相等:有相等的值。
identical:
Being the same object (which implies equivalence).
>相同:是同一個對象(意味著必然就是相等了)。
reference:
The association between a variable and its value.
>引用:變量 a 與其值的關系。
aliasing:
A circumstance where two or more variables refer to the same object.
>別名:同一個對象有兩個或者更多變量所指向的情況。
delimiter:
A character or string used to indicate where a string should be split.
>定界符:一個字符或者字符串,用來確定字符分割時候的分界。
## 10.15 練習
你可以從 [這里](http://thinkpython2.com/code/list_exercises.py) 下載下面這些練習的樣例代碼。
### 練習 1
寫一個函數,名為 nested_sum,接收一系列的整數列表,然后把所有分支列表中的元素加起來。如下所示:
```py
>>> t = [[1, 2], [3], [4, 5, 6]]
>>> nested_sum(t)
21
```
### 練習 2
寫一個函數,明切 cumsum,接收一個數字列表,然后返回累加的總和;也就是新列表的第 i 個元素就是源列表中前 i+1 個元素的累加。如下所示:
```py
>>> t = [1, 2, 3]
>>> cumsum(t)
[1, 3, 6]
```
### 練習 3
寫一個函數,名為 middle,接收一個列表,返回一個新列表,新列表要求對源列表掐頭去尾只要中間部分。如下所示:
```py
>>> t = [1, 2, 3, 4]
>>> middle(t)
[2, 3]
```
## 練習 4
寫一個函數,名為 chop,接收一個列表,修改這個列表,掐頭去尾,返回空值。如下所示:
```py
>>> t = [1, 2, 3, 4]
>>> chop(t)
>>> t
[2, 3]
```
### 練習 5
寫一個函數,名為 is_sorted,接收一個列表作為參數,如果列表按照字母順序升序排列,就返回真,否則返回假。如下所示:
```py
>>> is_sorted([1, 2, 2])
True
>>> is_sorted(['b', 'a'])
False
```
### 練習 6
兩個單詞如果可以通過順序修改來互相轉換就互為變位詞。寫一個函數,名為 is_anagram,接收兩個字符串,如果互為變位詞就返回真。
### 練習 7
寫一個函數,名為 has_duplicates,接收一個列表,如果里面有重復出現的元素,就返回真。這個函數不能修改源列表。
### 練習 8
這個練習也可以叫做生日悖論,你可以點擊[這里](http://en.wikipedia.org/wiki/Birthday_paradox)來讀一下更多背景知識。
假如你班上有 23 個學生,這當中兩個人同一天出生的概率是多大?你可以評估一下 23 個隨機生日中有相同生日的概率。提示一下:你可以用 randint 函數來生成隨機的生日,這個函數包含在 random 模塊中。
你可以從 [這里](http://thinkpython2.com/code/birthday.py)下載我的樣例代碼。
### 練習 9
寫一個函數,讀取文件 words.txt,然后建立一個列表,這個列表中每個元素就是這個文件中的每個單詞。寫兩個版本的這樣的函數,一個使用 append 方法,另外一個用自增運算的模式:t= t + [x]。看看哪個運行時間更長?為什么會這樣?
[樣例代碼](http://thinkpython2.com/code/wordlist.py)
### 練習 10
要檢查一個單詞是不是在上面這個詞匯列表里,你可以使用 in 運算符,但可能會很慢,因為這個 in 運算符要從頭到尾來搜索整個詞匯表。
我們知道這些單詞是按照字母表順序組織的,所以我們可以加速一下,用一種對折搜索(也叫做二元搜索),這個過程就和你在現實中用字典來查單詞差不多。你在中間部分開始,看看這個要搜索的詞匯是不是在中間位置的前面。如果在前面,就又對前半部分取中間,繼續這樣來找。當然了,不在前半部分,就去后半部分找了,思路是這樣的。
不論怎樣,每次都會把搜索范圍縮減到一半。如果詞表包含了 113809 個單詞,最多就是 17 步就能找到單詞,或者能確定單詞不在詞匯表中。
那么問題來了,寫一個函數,名為 in_bisect,接收一個整理過的按照字母順序排列的列表,以及一個目標值,在列表中查找這個值,找到了就返回索引位置,找不到就返回空。
[樣例代碼](http://thinkpython2.com/code/inlist.py).
### 1 練習 11
兩個詞如果互為逆序,就稱它們是『翻轉配對』。寫一個函數來找一下在這個詞匯表中所有這樣的詞對。[樣例代碼](http://thinkpython2.com/code/reverse_pair.py)
### 1 練習 12
兩個單詞,依次拼接各自的字母,如果能組成一個新單詞,就稱之為『互鎖』。比如,shoe 和 **cold**就可以鑲嵌在一起組成組成 s**c**h**o**o**l**e**d**。(譯者注:shoe+**cold**= s**c**h**o**o**l**e**d** ) [樣例代碼](http://thinkpython2.com/code/interlock.py). 說明:這個練習受到了[這里一處例子](http://puzzlers.org)的啟發。
1. 寫一個程序,找到所有這樣的互鎖單詞對。提示:不要枚舉所有的單詞對!
2. 你能找到那種三路互鎖的單詞么;就是那種三三排列三個單詞的字母能組成一個單詞的三個詞?