Mathematica/列表

維基教科書,自由的教學讀本
跳至導覽 跳至搜尋

引言[編輯]

列表在Mathematica中是最重要的數據結構,同時在一些函數式程式語言中比如LISP也是這樣。任何複雜數據結構都能夠被表示為一些(可能是複雜的和嵌套的)列表。例如,n維數組可以被表示成一個具有深度n[1]的列表。任何樹狀結構[2]可以看成是一個列表。

列表可以在程序執行的過程中動態地生成,正確書寫的函數可以作用在任意長度的列表上,而並沒有把列表的長度看成一個直接的參數。這樣得到了一個相當「乾淨」的代碼,同時容易書寫。列表的另一個優點在於通常可以容易地調試作用在其上的函數,我們將會看到許多具有這種特徵的例子。本章我們將會介紹幾個用來產生和處理列表的內置函數。

使用列表的一般準則[編輯]

當我們在Mathematica中使用列表時,特別是規模大的列表,遵循以下規則是很有用的:我們必須把任何列表看成一個簡單的單元,避免把它們碎片化的操作,例如數組索引化(array indexing)。換句話說,最好的編程方式是嘗試使用一些操作,使其能把列表看成一個整體來進行處理。這種方法在函數式編程中是特別重要的,它對於提高代碼效率有非常重大的意義。我們將介紹大量例子來闡述這個問題,尤其是在介紹函數式編程的第五章。

列表的內容[編輯]

列表並不要求只能包含一種類型的元素,列表裏面可以包含任何Mathematica表達式,甚至是這些表達式任意混合的產物。這些表達式可以是列表或者普通的Mathematica表達式,可以有任意的大小和深度。本質上,加上頭部List之後,列表是普通的Mathematica表達式的一種特別形式。

In[]:= Clear[x];
       List[Sin[x],Cos[x],Tan[x]]
Out[]= {Sin[x],Cos[x],Tan[x]}

列表的產生[編輯]

有很多方式可以產生列表。

手動產生列表[編輯]

首先,當然是可以手工產生列表。也就是說,程序中一些固定列表還是要靠程式設計師自己定義。比如:

In[]:= Clear[testlist,a,b,c,d,e];
       testlist={a,b,c,d,e}
       Clear[testlist];
Out[]= {a,b,c,d,e}

用Range產生等距離數字列表[編輯]

實際上產生等距離數字列表是很有必要的。特別是在函數式編程方法中,因為它們不是以循環的方式產生於這樣的列表。這樣的等距離數字列表可以使用內置函數Range。例子:

In[]:= Range[5]
       Range[2,10]
       Range[2,11,3]
       Range[0,1,0.1]
Out[]= {1,2,3,4,5}
Out[]= {2,3,4,5,6,7,8,9,10}
Out[]= {2,5,8,11}
Out[]= {0.,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.}

用Table產生列表[編輯]

在我們需要具有更一般性質的列表時,經常使用內置函數Table。例子:

In[]:= Table[1,{i,1,10}]
       Table[i^2,{i,1,10}]
       Table[i*j,{i,1,3},{j,1,3}]
       Table[i+j,{i,1,4},{j,1,i}]
       Table[i+j+k,{i,1,2},{j,1,3},{k,1,3}]
       Table[Sin[i],{i,1,10}]
Out[]= {1,1,1,1,1,1,1,1,1,1}
Out[]= {1,4,9,16,25,36,49,64,81,100}
Out[]= {{1,2,3},{2,4,6},{3,6,9}}
Out[]= {{2},{3,4},{4,5,6},{5,6,7,8}}
Out[]= {{{3,4,5},{4,5,6},{5,6,7}},{{4,5,6},{5,6,7},{6,7,8}}}
Out[]= {Sin[1],Sin[2],Sin[3],Sin[4],Sin[5],Sin[6],Sin[7],Sin[8],Sin[9],Sin[10]}

就如這些例子所示,在Table中用於設定迭代邊界的列表的數目可以有多個。填充入列表的元素可以是以迭代體的計數器為自變量的函數。當迭代體多於一個時,我們將獲得一個嵌套列表,而這個列表的最外層則是由最內層的迭代體產生的。就如我們所見,較外層迭代體的邊界可能是依賴於較內層迭代體裏的變量(和計數器是一個東西)的,因此我們可以用Table得到具有不等長子列表的列表。[3]這就是我們開始看到的,列表比多維數組更加一般,因為子列表不一定要有一樣的維數。而且,認識到用Table產生的列表不一定是數字列表,它們可以是函數列表:

In[]:= Clear[i,x];
       Table[x^i,{i,1,10}]
Out[]= {x,x^2,x^3,x^4,x^5,x^6,x^7,x^8,x^9,x^10}

在這裏作為例子,我們產生一個3*3單項式列表[4]

In[]:= Clear[i,j,x];
       Table[x^{i+j},{i,1,3},{j,1,3}]
Out[]= {{{x^2},{x^3},{x^4}},{{x^3},{x^4},{x^5}},{{x^4},{x^5},{x^6}}}

關於Table的另一個闡述就是它是一個局部結構(scoping construct),具有這樣的意義:它確定了它的迭代變量(iterator variables)。它有效地使用了Block[] 這樣的局部結構來實現。結果就是通常得伴隨着Block[]的使用(將會在4.8節介紹)。特別地,我們天真地希望符號<f[i]>在下面的例子中出現5次,但是在<f>的定義中用到了全局變量<i>

In[]:= Clear[f,i];
       f:=i^2;
       Table[f,{i,5}]
Out[]= {1,4,9,16,25}

而全局變量<i>仍然沒有得到任何值:

In[]:= i
Out[]= i

最後關於Table要說的是,雖然它是一種局部結構,內置函數Return[]卻不能被用來退出(break out of it),不像其他一些我們遇到的局部結構:

In[]:= Table[If[i>3,Return[],i],{i,10}]
Out[]= {1,2,3,Return[],Return[],Return[],Return[],Return[],Return[],Return[]}

小議Range的普適性[編輯]

就像第一個和第二個例子所呈現的,我們仍然可以用Range產生相同的結果:

In[]:= Range[10]^0
       Range[10]^2
Out[]= {1,1,1,1,1,1,1,1,1,1}
Out[]= {1,4,9,16,25,36,49,64,81,100}

其他的例子也可以使用Range和一點點函數式編程的方法:

In[]:= (Range[3]*#)&/@Range[3]
Out[]= {{1,2,3},{2,4,6},{3,6,9}}
In[]:= Range[#+1,2*#]&/@Range[4]
Out[]= {{2},{3,4},{4,5,6},{5,6,7,8}}
In[]:= Nest[Partition[#,3,1]&,Range[3,8],2]
Out[]= {{{3,4,5},{4,5,6},{5,6,7}},{{4,5,6},{5,6,7},{6,7,8}}}
In[]:= Map[Sin,Range[10]]
Out[]= {Sin[1],Sin[2],Sin[3],Sin[4],Sin[5],Sin[6],Sin[7],Sin[8],Sin[9],Sin[10]}
In[]:= Clear[x];
       x^Range[10]
Out[]= {x,x^2,x^3,x^4,x^5,x^6,x^7,x^8,x^9,x^10}

上面的例子對於現在的我們來說並不容易理解。在這裏我們涉及這部分是想表明Range是很有用的,並且澄清Table的角色。實際上,Table可以認為是一種優化的循環。通常用Table產生列表比Do,For或者While 要更快一些。但是在函數式編程中,Table並不常用,不像Range,現在我們可以看到為什麼我們能夠得到同樣的結果,並且,後者運行起來會更快。

在循環結構中產生列表[編輯]

用Append或者Prepend產生列表[編輯]

這種循環產生列表的方式是可能的,它很接近過程式程式語言。在Mathematica中,通常這是一種效率最低的方式。同時Table實際上就是一種循環,但是它優化了列表產生過程,從而快一點。從一個空列表開始,使用循環直接產生列表需要內置函數Append,Prepend,AppendTo,PrependTo。Append的作用是在列表的後面添加一個元素,這就是我們即將解釋對於較大型列表來說,Append效率低的原因所在。讓我們用例子來闡述一下。這裏是一個簡單的用Append產生的列表:

In[]:= For[testlist={};i=1,i<=10,i++,testlist=Append[testlist,Sin[i]]];
       testlist
Out[]= {Sin[1],Sin[2],Sin[3],Sin[4],Sin[5],Sin[6],Sin[7],Sin[8],Sin[9],Sin[10]}

然後,這是用Range

In[]:= Sin[Range[10]]
Out[]= {Sin[1],Sin[2],Sin[3],Sin[4],Sin[5],Sin[6],Sin[7],Sin[8],Sin[9],Sin[10]}

題外話:myTiming:一個計量運行時間的函數[編輯]

在這裏,我介紹一個自己定義的實用函數myTiming,接下來的內容中我將大量地在各種性能測量中使用它:

Clear[myTiming];
myTiming[x_]:= Module[
                     {z = 0, y = 0, timelim = 0.1, p, q, iterlist = (Power[10, #]& /@ Range[0, 10]),
                        nm = If[ToExpression[StringTake[$Version, 1]] < 6, 2, 1]
                      },
                     Catch[If[(z = Nest[First, Timing[(y++; Do[x, {#}]);], nm]) > timelim,
                     Throw[{z, y}]]& /@ iterlist] /. {p_, q_} :> p / iterlist[[q]]
                    ];
              Attributes[myTiming]={HoldAll};

這個代碼現在還有一點複雜,但是當讀完整本書,再回來看它是一個不錯的主意,因為它闡述了很多關鍵點。不管怎樣,現在我們只是純粹的使用者。

效率陷阱:用Append構造列表[編輯]

讓我們用Range產生列表逝去的時間與之做個對比,使用函數myTiming:

In[]:= myTiming[For[testlist={};i=1,i<1000,i++,testlist=Append[testlist,Sin[i]]];]
Out[]= 0.19

In[]:= myTiming[Sin[Range[1000]];]
Out[]= 0.0013

我們看到構造一個Sin的(符號值)列表,同樣是1000個自然數,Append比Range慢100倍。但是真相是計算的複雜性是不同的,較大的計算在於列表,更多的內部演算在於使用了Append。我們也可以看到Table是怎麼運作的:

In[]:= myTiming[Table[Sin[i],{i,1000}];]
Out[]= 0.0023

還是比Range慢一點。

除了慢一點以外,用循環方式的產生過程會引進一個全局的副作用——變量testlist。因此,為了使代碼更加clean[5],人們會通過把循環放在一個額外的模塊化結構中,這樣,代碼就更笨了。

因此,我會徹底地勸阻讀者在一個循環中直接產生列表。第一,我們幾乎完全有更好的設計,方法來避免這個問題。第二,確實是存在變通的方案來得到一個線性的時間績效來處理這個問題,比如使用嵌套列表(在下面將要進行討論),或者是索引變量。最後,從版本5開始,有特別的內置函數Reap和Sow被引進特別地用來有效率的生成和收集在計算中起媒介作用的結果——我們會在第二部分有一整章的內容介紹這兩個函數。

列表的完全形式[編輯]

讓我再一次強調列表的內部形式滿足Mathematica內部的標準表達式的一般要求。例如簡單的和嵌套的列表:

In[]:= Clear[testlist,complextestlist];
       testlist=Range[10]
       complextestlist=Range/@Range[5]
Out[]= {1,2,3,4,5,6,7,8,9,10}
Out[]= {{1},{1,2},{1,2,3},{1,2,3,4},{1,2,3,4,5}}

接下來的是它們的完全形式(full forms):

In[]:= FullForm[testlist]
Out[]//FullForm= List[1,2,3,4,5,6,7,8,9,10]
In[]:= FullForm[complextestlist]
Out[]//FullForm= List[List[1],List[1,2],List[1,2,3],List[1,2,3,4],List[1,2,3,4,5]]

這表明了,特別地,列表索引化可以與更一般的Mathematica表達式的索引用相同的方式執行,這在第一章已經強調過了。我們會在下一部分使用這個事實。

Clear[testlist,complextestlist];

使用列表和它們的零件[編輯]

列表索引和用Part取出元素[編輯]

簡單列表[編輯]

考慮一個簡單列表:

In[]:= Clear[testlist];
       testlist=Range[1,20,3]
Out[]= {1,4,7,10,13,16,19}

然後我們取出一個元素,可以按以下顯示內容來完成:

In[]:= testlist[[3]]
Out[]= 7

或者,同樣地,就像這樣:

In[]:= Part[testlist,3]
Out[]= 7

現在,我們需要取出第二,四,五個元素:

In[]:= testlist[[{2,4,5}]]
Out[]= {4,10,13}

或者,這樣:

In[]:= Part[testlist,{2,4,5}]
Out[]= {4,10,13}

列表元素能夠從列表的末尾計算。這樣的話,它們的位置按照慣例就是負的:

In[]:= testlist[[-1]]
Out[]= 19

關於嵌套列表[編輯]

考慮一個更複雜的列表:

In[]:= complextestlist=Range[5*#,5*#+4]&/@Range[5]
Out[]= {{5,6,7,8,9},{10,11,12,13,14},{15,16,17,18,19},{20,21,22,23,24},{25,26,27,28,29}}

它的每一層的元素(層是指到樹幹的距離[6]見1.1.7)是它的子列表:

In[]:= complextestlist[[2]]
Out[]= {10,11,12,13,14}
In[]:= complextestlist[[{1,4,5}]]
Out[]= {{5,6,7,8,9},{20,21,22,23,24},{25,26,27,28,29}}

為了得到列表中的數字元素,我們需要用兩個數字來組成索引(a 2-number indexing)(因為所有子列表具有一層深度,如果它們有深度n的話,那麼我們就需要用n+1個數字來組成的索引。並且不同的子列表深度可以是不同的,因而所需要用來組成索引的數字個數也是不同的。)

In[]:= complextestlist[[1,1]]
Out[]= 5

這就意味着「第一個元素的第一個元素」(我們可以把該列表看成為一個2維數組)。注意到語句「In[]:= complextestlist[[{1,1}]] 」將會在語法上理解為原來列表的第一個元素重複兩次,也就是第一個子列表重複兩次:

In[]:= complextestlist[[{1,1}]]
Out[]= {{5,6,7,8,9},{5,6,7,8,9}}

對於簡單列表也是同樣的道理:

In[]:= complextestlist[[-1]]
       complextestlist[[-1,-1]]
Out[]= {25,26,27,28,29}
Out[]= 29

Clear[testlist,complextestlist];

Extract[編輯]

這個函數與Part類似,但它還具有其他的功能,有時候非常有用,但是目前對我們來說並不重要。現在重要的是,它可以一次性在不同的層中取出幾個元素(Part也可以取出幾個元素,但它們必須是在同一層中)。而且,Extract有一個不同的語法特徵——在比第一層更深的另一層中取出一個元素(並且當我們每次取出不止一個元素時),被取出的元素的位置要以列表的形式來表示:

In[]:= testlist=Range[1,20,3];
       complextestlist=Range[5*#,5*#+4]&/@Range[5]
Out[]= {{5,6,7,8,9},{10,11,12,13,14},{15,16,17,18,19},{20,21,22,23,24},{25,26,27,28,29}}
In[]:= Extract[testlist,1]
       Extract[complextestlist,1]
       Extract[complextestlist,{1,2}]
Out[]= 1
Out[]= {5,6,7,8,9}
Out[]= 6
In[]:= Extract[complextestlist,{{1,2},{3},{4,5}}]
Out[]= {6,{15,16,17,18,19},24}

Take和Drop[編輯]

這兩個函數用來提取和扔掉一個列表裏的幾個元素。這是我們的列表:

In[]:= testlist=Range[1,20,3]
       complextestlist=Range[5*#,5*#+4]&/@Range[5]
Out[]= {1,4,7,10,13,16,19}
Out[]= {{5,6,7,8,9},{10,11,12,13,14},{15,16,17,18,19},{20,21,22,23,24},{25,26,27,28,29}}

接下來是一些例子關於Take和Drop怎樣工作的:

In[]:= Take[testlist,3]
Out[]= {1,4,7}
In[]:= Take[testlist,{2,4}]
Out[]= {4,7,10}
In[]:= Take[testlist,-3]
Out[]= {13,16,19}
In[]:= Take[testlist,{-4,-3}]
Out[]= {10,13}
In[]:= Drop[testlist,3]
Out[]= {10,13,16,19}
In[]:= Drop[testlist,{2,4}]
Out[]= {1,13,16,19}
In[]:= Drop[testlist,-3]
Out[]= {1,4,7,10}
In[]:= Drop[testlist,{-4,-3}]
Out[]= {1,4,7,16,19}

Take和Drop也有一些擴展的功能,能夠自動地對於嵌套列表進行結構上的操作,比如從矩陣中抽取子矩陣。我們並不在這裏敘述,詳見Mathematica幫助文檔。

First,Rest,Last 和Most[編輯]

這一組函數原則上是多餘的,因為

First[list]實際上等價於list[[1]],

Rest[list]等價於Drop[list,1],

Last[list]等價於list[[-1]],

Most[list]等價於Drop[list,-1]。

然而它們可以改善代碼的閱讀性。

Length[編輯]

該函數返回列表的長度,比如:

In[]:= testlist=Range[1,20,3]
       complextestlist=Range[5*#,5*#+4]&/@Range[5]
Out[]= {1,4,7,10,13,16,19}
Out[]= {{5,6,7,8,9},{10,11,12,13,14},{15,16,17,18,19},{20,21,22,23,24},{25,26,27,28,29}}
In[]:= {Length[testlist],Length[complextestlist]}
Out[]= {7,5}

如果我們想要計算complextestlist子列表的長度,可以這樣:

In[]:= Table[Length[complextestlist[[i]]],{i,1,Length[complextestlist]}]
Out[]= {5,5,5,5,5}

明顯地,i用來表示子列表的索引,因此它取遍1到complextestlist的長度,同時操作Part([[i]])作用其上,表示抽取子列表。

用Part修改列表元素[編輯]

Part的簡單使用[編輯]

如果需要替代列表中的一些元素,前提已知了一些新表達式或者變量的位置,這樣可以直接完成。這是我們的列表:

In[]:= Clear[a];
       testlist=Range[1,20,3]
       complextestlist=Range[5*#,5*#+4]&/@Range[5]
Out[]= {1,4,7,10,13,16,19}
Out[]= {{5,6,7,8,9},{10,11,12,13,14},{15,16,17,18,19},{20,21,22,23,24},{25,26,27,28,29}}

現在我們要在位置{5}用a替代:

In[]:= testlist[[5]]=a;
       testlist
Out[]= {1,4,7,10,a,16,19}

接着,在列表complextestlist中的每一個子列表中用a隨機替代一個元素:

In[]:= For[i=1,i<=Length[complextestlist],i++,complextestlist[[i,Random[Integer,{1,5}]]]=a];
       complextestlist
Out[]= {{5,6,7,8,a},{10,11,a,13,14},{15,16,17,18,a},{20,21,a,23,24},{25,26,27,a,29}}


注意到上述這樣的修改只有在列表被儲存在變量里才有可能實現(在C語言裏,可以說成是左值(L-value))。特別地,如下的輸入會出錯:

In[]:= Range[10][[3]]=a
Set::setps: Range[10] in the part assignment is not a symbol. >>
Out[]= a

本質上,在上述所有例子中函數Part被用做表達式的修改。我們也認識到用Part來修改列表元素會引發副作用,因為就是原來儲存列表的變量被修改,而不是修改列表的一個複製品。

用Part高效修改大型結構[編輯]

有一點我想提及,Part有一個非常強大的擴展功能,它可以一次性修改很多元素。比如,如果我想修改complextestlist的每一個子列表中的第二個元素,分別用b,c,d,e,f來替代,我可以這樣做:

In[]:= Part[complextestlist,All,2]={b,c,d,e,f};
      complextestlist
Out[]= {{5,b,7,8,a},{10,c,a,13,14},{15,d,17,18,a},{20,e,a,23,24},{25,f,27,a,29}}

這表明可以一次性修改很多元素是極其方便的。但是,它局限於當這些部分是來自於一些特殊的結構位置(就像子矩陣)的情況。若欲獲悉更多細節,參見Mathematica幫助文檔,或者是Ted Ersek的網站[7]

ReplacePart[編輯]

這是另一個用來修改列表元素的內置函數。但是與上面的直接修改元素的方式和功能上(比如用Part)有很大的差別。認識到作為Part作用後的結果,原來的列表已經被修改了。然而,在Mathematica的更多程序中,我們更希望不去修改原來的列表,而只是改動它的複製品,以保持原來列表不變。這種編程風格可以說更簡潔但是要求更多的內存因為原來的對象被複製了。大多數的內置函數採取這樣的方式。特別地,ReplacePart也是這樣工作的:

In[]:= Clear[a];
       testlist=Range[1,20,3]
Out[]= {1,4,7,10,13,16,19}
In[]:= ReplacePart[testlist,a,5]
Out[]= {1,4,7,10,a,16,19}

但是原來的列表並沒有發生改變:

In[]:= testlist
Out[]= {1,4,7,10,13,16,19}

這意味着ReplacePart並不要求左值(L-value)來對一個對象進行操作:

In[]:= ReplacePart[Range[10],a,5]
Out[]= {1,2,3,4,a,6,7,8,9,10}

咦,沒有出錯哦,因為它沒有改變原來的列表只是改變它的複製品。

ReplacePart可以一次性改變多個元素,即使這些元素在表達式不同的層上,例子:

In[]:= ReplacePart[Range[10],a,{{2},{5},{8}}]
Out[]= {1,a,3,4,a,6,7,a,9,10}

我們發現關於元素位置的語法表示和Extract是一樣的。在Mathematica幫助文檔里關於ReplacePart有更詳細的內容。

在這裏,我必須提及的是,如果你想一次性在一個大型列表裏改變大量的元素,ReplacePart可能會運行得非常慢。在附錄C中會有更多的細節闡述。

我並沒有馬上介紹使用ReplacePart一個接一個地(one by one)改變列表元素(也就是說,在一個循環里)而是一次性改變。理由就是既然它有效地複製整個列表,然後再在複製品上執行修改操作,那麼它將在這種連續的方式中複製整個列表,而複製的次數和要做修改的次數是一樣的。這就嚴重導致了效率低下,就像Append和Prepend一樣。在另一些情況中,我們還是可以有效地利用Part一次性修改大量元素,我們將會在第六章考慮這個問題(見6.5.5.1)

Position[編輯]

Position用來確定元素在列表(或者是更普遍的Mathematica表達式)中的位置。這些元素可以匹配一個已給的表達式,或者確切地說,通過模式。

Position的基本用法[編輯]

最簡單地,Position的用法結構是Position[list,element]。讓我們來給幾個例子: 首先,我們初始化我們的列表:

In[]:= testlist=Range[1,20,3]
       complextestlist=Range[5*#,5*#+4]&/@Range[5]
Out[]= {1,4,7,10,13,16,19}
Out[]= {{5,6,7,8,9},{10,11,12,13,14},{15,16,17,18,19},{20,21,22,23,24},{25,26,27,28,29}}

我們將要用Position最簡單的用法(沒有涉及模式)來得到數字「4」在這個列表裏的所謂位置:

In[]:= Position[testlist,4]
Out[]= {{2}}

這表示數字「4」在列表testlist中的第二個位置

In[]:= Position[complextestlist,12]
Out[]= {{2,3}}

這表明數字「12」在列表complextestlist的第二個元素的第三個位置上(即complextestlist的第二個子列表的第三個位置上)。

Position能夠和Extract一起使用,因為它們使用一樣的位置描述方式:

In[]:= Extract[complextestlist,Position[complextestlist,10]]
Out[]= {10}

Position的瑣碎使用[編輯]

這個看起來並不起眼,但是我還是希望能夠取出包含數字「10」的整個子列表。我們所要做的就是建立一個新的位置列表,採取扔掉原來位置列表中的最後的元素的方式。

In[]:= Map[Most,Position[complextestlist,10]]
Out[]= {{2}}

Map的作用會在隨後得到闡述,現在關鍵的是它使得如果位置列表包含幾個位置,最後一個元素將會被扔掉。比如,我們定義另一個嵌套列表,這個列表中的一些元素會出現不止一次:

In[]:= complextestlist1=Range/@Range[6]
Out[]= {{1},{1,2},{1,2,3},{1,2,3,4},{1,2,3,4,5},{1,2,3,4,5,6}}

這裏是數字「4」的所有位置列表:

In[]:= plist=Position[complextestlist1,4]
Out[]= {{4,4},{5,4},{6,4}}

如果我們利用這些位置列表,再結合Extract,我們會取出3次數字「4」:

In[]:= Extract[complextestlist1,plist]
Out[]= {4,4,4}

然而,如果我們想要取出所有包含數字「4」的所有子列表,我們需要更進一步。為了只的到子列表的位置,我們不得不扔掉列表plist中的最後一個元素:

In[]:= Map[Most,plist]
Out[]= {{4},{5},{6}}

現在,我們可以取出子列表了:

In[]:= Extract[complextestlist1,Map[Most,plist]]
Out[]= {{1,2,3,4},{1,2,3,4,5},{1,2,3,4,5,6}}

例子:取出含有已給元素的子列表[編輯]

現在我們將會寫出我們的第一個「serious」的函數:它將從一個列表中取出含有已給元素的子列表。

Clear[sublistsWithElement];
sublistsWithElement[main_List,elem_]:=Extract[main,Map[Most,Position[main,elem]]];

作為例子,這些是在列表complextestlist中含有數字「5」的子列表:

In[]:= sublistsWithElement[complextestlist1,5]
Out[]= {{1,2,3,4,5},{1,2,3,4,5,6}}

我藉助這個機會闡明幾點在Mathematica中用戶自定義函數的一般特徵。第一,函數的形成:從一些簡單的測試例子開始通常是最好的,就像上面我們一步步演繹的方式來,之後在把所有的東西打包成一個函數。第二,我們看到在一個函數定義中左邊的參數中含有一個下劃線。這是一個使用模式的標誌。目前為止,我將會簡要地描述模式x_代表「any expression」然後自動地使得「x」在右邊。而SetDelayed(:=)在定義中使用了(在函數中是很正常的)。模式「x_h」表示任何頭部為h的表達式(「anything with the head h」)。因此上面出現的模式main_List會與任何列表匹配,而不可以是其他更一般頭部不是List的表達式。它構成了一個簡單的類型約束。

另一些內容關於函數Position:記住Position是一個已優化的,運行得很快的函數是很重要的,它仍然是一個「general-purpose」函數。特別地,如果你把一個列表按照某些條件分類,對於在大型列表中搜尋一個元素,採取二進制的搜索方式要比Position(具有線性的複雜性)快很多。這種二進制的搜索方式可以在Mathematica中實施(當然它有對數式的複雜性)。

更複雜的例子——具有奇數個奇數元素的子列表[編輯]

問題: 我們將要闡述Position和模式在一個稍微複雜的例子上一起使用。請你忽略還不熟悉的語法結構,而是要更加注意概念上的部分和把這些看成為一個說明。這個問題是從列表complextestlist1中取出所有含有奇數個奇數元素的子列表。我們的解決方案會一步步來:

In[]:= complextestlist1=Range/@Range[6]
Out[]= {{1},{1,2},{1,2,3},{1,2,3,4},{1,2,3,4,5},{1,2,3,4,5,6}}

解決方案的形成: 步驟一:找到所有奇數元素的位置

In[]:= step1list=Position[complextestlist1,_?OddQ]
Out[]= {{1,1},{2,1},{3,1},{3,3},{4,1},{4,3},{5,1},{5,3},{5,5},{6,1},{6,3},{6,5}}

在每一個小子列表中,我們已經知道,位置列表的第一個元素給出了子列表在complextestlist1出現的位置,第二個元素給出了奇數元素在子列表中的位置。

步驟二:我們把這些聯繫着同一個子列表的位置結合起來——它們具有相同的第一個元素:

In[]:= step2list=Split[step1list,First[#1]==First[#2]&]
Out[]= {{{1,1}},{{2,1}},{{3,1},{3,3}},{{4,1},{4,3}},{{5,1},{5,3},{5,5}},{{6,1},{6,3},{6,5}}}

函數Split是另一個我們馬上就要談到的內置函數,它的作用是把一個列表按照「相同」的元素分離開形成子列表。「相同」的定義可以由用戶自定義。特別地,在我們的例子中,我們告訴Split按照如果step1list的兩個子列表具有相等的第一個元素,那麼這兩個子列表就是「相同」的。那麼現在它們被組合在一個額外的列表裏。

步驟三:留下第一個元素:

In[]:= step3list=Map[First,step2list,{2}]
Out[]= {{1},{2},{3,3},{4,4},{5,5,5},{6,6,6}}

步驟四:在上面的列表裏,只能留下具有奇數個元素的子列表(這些子列表的長度相當於complextestlist1的子列表裏的奇數元素的個數,並且還是這些奇數元素在complextestlist1的位置)

In[]:= step4list=Cases[step3list,x_List/;OddQ[Length[x]]]
Out[]= {{1},{2},{5,5,5},{6,6,6}}

函數Cases用來找到列表裏所有匹配模式的列表 [8],以後我們會談到它。

步驟五:用它們的第一個元素代替所有的子列表

In[]:= step5list=Union[Flatten[step4list]]
Out[]= {1,2,5,6}

函數Flatten壓平任何列表,函數Union可以除掉重複的元素並且對得到的結果進行排序。

In[]:= complextestlist1[[step5list]]
Out[]= {{1},{1,2},{1,2,3,4,5},{1,2,3,4,5,6}}

把代碼打包正一個函數: 我們可以把所有的步驟壓縮成一個簡單的函數:

Clear[oddSublists];
oddSublists[x_List]:=Part[x,Union[Flatten[Cases[Map
[First,Split[Position[x,_?OddQ],First[#1]==First[#2]&],{2}],y_List/;OddQ[Length[y]]]]]]

我們來檢驗一下:

In[]:= oddSublists[complextestlist1]
Out[]= {{1},{1,2},{1,2,3,4,5},{1,2,3,4,5,6}}

一個可選擇的函數編程手段: 這是一個更簡單但是不明顯的方式來書寫上面的函數oddSublists,它運用了混合的規則和函數式編程風格。在這裏我只是作為一個說明罷了:

Clear[oddSublistsNew];
oddSublistsNew[x_List]:=Map[If[EvenQ[Count[#,_?OddQ]],#/.#->Sequence[],#]&,x];

檢驗一下:

In[]:= oddSublistsNew[complextestlist1]
Out[]= {{1},{1,2},{1,2,3,4,5},{1,2,3,4,5,6}}

第一點,雖然質疑這種編程風格相對於一個傳統的過程式編程方式(以嵌套循環為基礎)首先變得意味深長的複雜,但是在這裏我的主要目的是說明函數Position的使用,也許「give a flavor of a few others」。

第二點,這樣書寫會更短,便於快速書寫。

一個過程式編程版本: 這比基於兩個嵌套循環有更少的錯誤傾向:

Clear[oddSublistsProc];
oddSublistsProc[x_List]:=Module[
                                {pos={},ctr,i,j},For[i=1,i<=Length[x],i++,
                                 For[j=1;ctr=0,j<=Length[x[[i]]],j++,If[OddQ[x[[i,j]]],ctr++];];
                               If[OddQ[ctr],AppendTo[pos,i]];];Return[x[[pos]]]
                               ];

檢驗:

In[]:= oddSublistsProc[complextestlist1]
Out[]= {{1},{1,2},{1,2,3,4,5},{1,2,3,4,5,6}}

這樣的代碼除了「clumsier」之外,它還使用了AppendTo來在列表裏添加元素,這樣使得代碼對於大型列表的效率很低,就像我們在前面的例子中談到的。

Clear[complextestlist1,step1list,step2list,step3list,step4list,
step5list,oddSublists,oddSublistsNew,oddSublistsProc];

列表元素的添加和刪除[編輯]

Append,Prepend,AppendTo和PrependTo[編輯]

這裏面的一些函數我們已經遇到過了。它們在列表的末尾或者開頭出添加一個元素,例子:

In[]:= Clear[a];
       testlist=Range[5]
Out[]= {1,2,3,4,5}
In[]:= Append[testlist,a]
Out[]= {1,2,3,4,5,a}
In[]:= Prepend[testlist,a]
Out[]= {a,1,2,3,4,5}
In[]:= testlist
Out[]= {1,2,3,4,5}


最後一個結果表明列表testlist並沒有發生改變。正如我們討論的,沒有副作用在Mathematica的內置函數中是特有的。在這裏,Append和Prepend加工列表testlist的複製品並修改這個複製品。如果我們要修改原來的列表,我們還可以這樣寫:

In[]:= testlist=Append[testlist,a];
       testlist
Out[]= {1,2,3,4,5,a}

或者,等價地,用函數AppendTo,確實可以實現的:

In[]:= testlist=Range[5]
Out[]= {1,2,3,4,5}
In[]:= AppendTo[testlist,a];
       testlist
Out[]= {1,2,3,4,5,a}

Prepend和PrependTo是完全類似的。同時,回憶起先前的討論,我們會懷疑AppendTo或者PrependTo作用在一個列表上,而這樣的作用方式不是被分配給任何變量(不是一種左值(L-value))是一個錯誤。然而我們是對的:[9]

In[]:= Append[Range[5],a]
Out[]= {1,2,3,4,5,a}
In[]:= AppendTo[Range[5],a]
Set::write: Tag Range in Range[5] is Protected. >>
Out[]= {1,2,3,4,5,a}

我們已經討論過,最好避免使用這些函數(Append等等)來修改大型列表。隨後我們會考慮幾個更有效方法。

Clear[testlist];

Insert和Delete[編輯]

從它們的名字看就再明顯不過了,這兩函數用來在一個列表裏插入和刪除一個元素,或者是在更一般的Mathematica表達式里作用。它們的操作詳見Mathematica幫助文檔。我將提供一些簡單的例子。Insert的格式是Insert[list,new,pos]——它會將新元素nes插到列表list的位置pos上。Delete的格式是Delete[list,pos],它把列表list位置pos上的元素刪除。例子:

In[]:= Clear[a];
       testlist=Range[3,15,2]
Out[]= {3,5,7,9,11,13,15}
In[]:= Delete[testlist,4]
Out[]= {3,5,7,11,13,15}
In[]:= Insert[testlist,a,5]
Out[]= {3,5,7,9,a,11,13,15}

再一次注意到,這兩函數作用在列表的複製品上,所以原來的列表沒有改變:

In[]:= testlist
Out[]= {3,5,7,9,11,13,15}

同時,這兩函數可以作用在嵌套列表裏(或者更一般的Mathematica表示式),並且位置將是一個索引列表。而且,它們會接受一些位置組成的列表而不僅僅只是一個位置——在這中情況里,一個元素會馬上被插入(刪除)到許多位置。

然而,對於Insert的情況,同時插入大量元素時它會變得非常緩慢。在附錄C中會有更多詳細介紹。

關於嵌套列表[編輯]

經常會涉及到處理嵌套列表,嵌套列表就是元素是列表的一種列表。我們曾經見過一些嵌套列表。讓我們強調一下,總之,嵌套列表和多維數組不是完全相同的,實際上它更一般,因為每一層子列表的長度可以不同。關於一般的嵌套列表,我們唯一可以說的是它代表樹。

這裏我們只考慮一些特殊作用的函數,它們是為了高效處理某些特殊類型的嵌套列表而設計的。

Partition[編輯]

該函數用來把列表切割成一個個子列表(可以是重疊)。最簡單的形式是Partition[list,size,shift]。它把列表切成長度為size的子列表,然後依據shift來移動到下一個子列表。如果參數shift沒有給出,那麼列表將會被切割成沒有重疊的子列表。還是看例子理解吧:

一個簡單的例子[編輯]

In[]:= testlist=Table[Sqrt[i],{i,1,10}]
Out[]={1,Sqrt[2],Sqrt[3],2,Sqrt[5],Sqrt[6],Sqrt[7],2 Sqrt[2],3,Sqrt[10]}
In[]:= Partition[testlist,3]
Out[]={{1,Sqrt[2],Sqrt[3]},{2,Sqrt[5],Sqrt[6]},{Sqrt[7],2 Sqrt[2],3}}
In[]:= Partition[testlist,7]
Out[]= {{1,Sqrt[2],Sqrt[3],2,Sqrt[5],Sqrt[6],Sqrt[7]}}

在最後的例子中,剩下的部分少於7個,所以剩下的部分就被「吃光」了。現在我們來看看有重疊部分的例子:

In[]:= Partition[testlist,7,1]
Out[]= {{1,Sqrt[2],Sqrt[3],2,Sqrt[5],Sqrt[6],Sqrt[7]},
 {Sqrt[2],Sqrt[3],2,Sqrt[5],Sqrt[6],Sqrt[7],2 Sqrt[2]},
 {Sqrt[3],2,Sqrt[5],Sqrt[6],Sqrt[7],2 Sqrt[2],3},
 {2,Sqrt[5],Sqrt[6],Sqrt[7],2 Sqrt[2],3,Sqrt[10]}}

實際運用的例子:列表的moving average的計算[編輯]

這個例子是基於在Wagner』96里的一個簡單討論 問題: 一個列表的m-moving average是這樣的平均值:一個列表中的一個元素,考慮個鄰居,它和鄰居們的平均值(意味着這個平均值是在點(元素)上定義的,這個點(元素)至少有m個左鄰右舍)。因此,moving average實際上是一些平均值組成的列表,長度為len-2m ,而len是原來列表的長度。

形成解決方案: 為了解決這個問題,我們首先定義一個輔助的函數,這個函數計算一個列表的平均值。然而,它說明我們的函數也可以作用在一個數字列表上。這次把列表裏所有的元素求和(包括具有相同個數的元素)。所以:

Clear[listAverage];
listAverage[x_List]:=Apply[Plus,x]/Length[x];

表達式Apply[Plus,x]計算列表裏的每一個元素的和,它的意思會在第五章介紹。

現在我們定義另一個輔助函數:

Clear[neighborLists];
neighborLists[x_List,m_Integer]:=Partition[x,Length[x]-2*m,1];

比如:

In[]:= neighborLists[testlist,1]
Out[]={{1,Sqrt[2],Sqrt[3],2,Sqrt[5],Sqrt[6],Sqrt[7],2 Sqrt[2]},
 {Sqrt[2],Sqrt[3],2,Sqrt[5],Sqrt[6],Sqrt[7],2Sqrt[2],3},
 {Sqrt[3],2,Sqrt[5],Sqrt[6],Sqrt[7],2 Sqrt[2],3,Sqrt[10]}}

現在我們意識到中間的那個列表代表「middle points」的列表,第一個列表和最後一個列表就是這些「middle points」的最近的鄰居。因此,剩下唯一要做的是使用我們自定義的函數listAverage作用在其結果上:

In[]:= listAverage[neighborLists[testlist,1]]
Out[]= {1/3 (1+Sqrt[2]+Sqrt[3]),1/3 (2+Sqrt[2]+Sqrt[3]),1/3 (2+Sqrt[3]+Sqrt[5]),1/3 (2+Sqrt[5]+Sqrt[6]),
 1/3 (Sqrt[5]+Sqrt[6]+Sqrt[7]),1/3 (2 Sqrt[2]+Sqrt[6]+Sqrt[7]),1/3 (3+2 Sqrt[2]+Sqrt[7]),1/3 (3+2 Sqrt[2]+Sqrt[10])}

把代碼打包成一個函數: 因此,我們最終的函數movingAverage就是這樣:

Clear[movingAverage,neighborLists,listAverage];
neighborLists[x_List,m_Integer]:=Partition[x,Length[x]-2*m,1];listAverage[x_List]:=Apply[Plus,x]/Length[x];
movingAverage[x_List,m_Integer]:=listAverage[neighborLists[x,m]];

作為例子,我們計算兩邊各有2個鄰居的moving average:

In[]:= movingAverage[testlist,2]
Out[]= {1/5 (3+Sqrt[2]+Sqrt[3]+Sqrt[5]),1/5 (2+Sqrt[2]+Sqrt[3]+Sqrt[5]+Sqrt[6]),
1/5 (2+Sqrt[3]+Sqrt[5]+Sqrt[6]+Sqrt[7]),1/5 (2+2 Sqrt[2]+Sqrt[5]+Sqrt[6]+Sqrt[7]),
1/5 (3+2 Sqrt[2]+Sqrt[5]+Sqrt[6]+Sqrt[7]),1/5 (3+2Sqrt[2]+Sqrt[6]+Sqrt[7]+Sqrt[10])}

使用函數式編程方式消除輔助函數: 在函數式程序語法的幫助下,我麼可以寫出下面簡單的函數並且消除對輔助函數的需要:

Clear[movingAverage];
movingAverage[x_List,m_Integer]:=(Plus@@#)/Length[#]&@Partition[x,Length[x]-2*m,1];

檢驗:

In[]:= movingAverage[testlist,2]
Out[]= {1/5 (3+Sqrt[2]+Sqrt[3]+Sqrt[5]),1/5 (2+Sqrt[2]+Sqrt[3]+Sqrt[5]+Sqrt[6]),
1/5 (2+Sqrt[3]+Sqrt[5]+Sqrt[6]+Sqrt[7]),1/5 (2+2 Sqrt[2]+Sqrt[5]+Sqrt[6]+Sqrt[7]),
1/5 (3+2 Sqrt[2]+Sqrt[5]+Sqrt[6]+Sqrt[7]),1/5 (3+2 Sqrt[2]+Sqrt[6]+Sqrt[7]+Sqrt[10])}

一個過程式程序版本: 這個是同樣的東西,用過程式程序實現:

movingAverageProc[x_List,m_Integer]:=Module[
                                            {i,j,ln=Length[x],aver,sum},aver=Table[0,{ln-2*m}];For[i=m+1,i<=ln-                      m,i++,sum=0;
                                            For[j=i-m,j<=i+m,j++,sum=sum+x[[j]]];aver[[i-m]]=sum/(2*m+1)];aver];

檢驗:

In[]:= movingAverageProc[testlist,2]
Out[]= {1/5 (3+Sqrt[2]+Sqrt[3]+Sqrt[5]),1/5 (2+Sqrt[2]+Sqrt[3]+Sqrt[5]+Sqrt[6]),
1/5 (2+Sqrt[3]+Sqrt[5]+Sqrt[6]+Sqrt[7]),1/5 (2+2Sqrt[2]+Sqrt[5]+Sqrt[6]+Sqrt[7]),
1/5 (3+2 Sqrt[2]+Sqrt[5]+Sqrt[6]+Sqrt[7]),1/5 (3+2 Sqrt[2]+Sqrt[6]+Sqrt[7]+Sqrt[10])}

效率對比: 過程式程序的問題不僅僅是代碼更長,而是更容易出錯(數組界限(array bounds),變量初始化等等)。另外,它的效率極低。讓我們用大型列表來對比一下:

In[]:= Timing[movingAverage[Range[10000],10];]
Out[]= {0.016,Null}
In[]:= Timing[movingAverageProc[Range[10000],10];]
Out[]= {1.172,Null}

當然,我們也可以通過更加方便的內置函數來完成這一過程甚至可以拓展成MovingAverage的樣子。例如RotateRight,Take或者ListCorrelate,但是這些就太像MovingAverage本身了。 這裏也給出例子:

ma1[x_List,m_Integer]:=ListCorrelate[ConstantArray[1,m],x]/m

ma2[x_List,ker_List]:=ListCorrelate[ker,x]/Total@ker

ma3[x_List,m_Integer]:=Average[RotateRight[x,#]&/@Range[0,m-1]] [ [m;;] ]

這兩個函數在快速解決和計算各類問題的時候也非常有用,尤其是讓一個元素和左右元素有關聯的時候這樣的整體式的處理方法會很快和有效。

在這裏,我們看到了100倍的差距(因為列表的長度)!更多的是,這不是一個不變的因素,但是隨着列表長度的增加差距會進一步拉大。當然,在過程式程式語言例如C』語言裏,後者的程序更自然而且更快。但在Mathematica卻不是這樣!然而,我們還是可以通過函數式編程方法得到簡潔,快速,高雅的代碼。

Clear[testlist];

Transpose[編輯]

這是最有用的內置函數之一。它的名字來源於代表2維列表的列表——矩陣,它執行調換操作(transposition operation)。然而,我們並不總是一定要把2維數組翻譯成列表,特別如果該數組裏的元素是不同類型的。這也表明了Transpose可以完成大量的任務。讓我們先從數字列表開始,我們現在有一個已知的以列表作為元素的列表(它們可以是列表本身,但是這不影響我們):

簡單例子:調換一個簡單列表[編輯]

In[]:= testlist=Table[i+j,{i,1,2},{j,1,3}]
Out[]= {{2,3,4},{3,4,5}}

然後,

In[]:= Transpose[testlist]
Out[]= {{2,3},{3,4},{4,5}}

例子:調換一個列表的列表[編輯]

另一個例子:

In[]:= testlist=Table[{i,j},{i,1,2},{j,1,3}]
Out[]= {{{1,1},{1,2},{1,3}},{{2,1},{2,2},{2,3}}}

這是一個2維數組。

In[]:= Transpose[testlist]
Out[]= {{{1,1},{2,1}},{{1,2},{2,2}},{{1,3},{2,3}}}

例子:成績和姓名結合[編輯]

另一個例子:我們已經有了考試的一些分數——作為第一個列表,和學生的姓名——作為第二個列表。我們想做一個簡單的裂變就像這個樣子{{学生1,成绩1}……}。

Clear[names,scores];
names={"Smith","Johnson","Peterson"};
scores={70,90,50};

然後我們這樣做:

In[]:= Transpose[{names,scores}]
Out[]= {{Smith,70},{Johnson,90},{Peterson,50}}

但是我們用Transpose,再結合函數式程序會得到更多,因為Transpose確實是在高效率結構重整中頻繁使用。在本章接下來的內容里,我們將看到很多關於使用它的例子。

當然,我們也可以通過Thread來完成: Thread[{names,scores}] <\code>

Flatten[編輯]

这个函数用来破坏嵌套列表的结构,因为它搬走所有内部的花括号并将任何复杂的嵌套列表转换成一个平坦的列表。

简单例子:压平一个嵌套列表[編輯]

In[]:= teselist=Table[{i,j},{i,1,2},{j,1,3}]
Out[]= {{{1,1},{1,2},{1,3}},{{2,1},{2,2},{2,3}}}
In[]:= Flatten[testlist]
Out[]= {1,1,1,2,1,3,2,1,2,2,2,3}

壓平列表的特定層[編輯]

我們可以令Flatten更仁慈些和有選擇性的,通過命令它去破壞表達式的某個層。被破壞的層可以在Flatten的可選擇的第二個參數中給出。比如:

In[]:= Flatten[testlist,1]
Out[]= {{1,1},{1,2},{1,3},{2,1},{2,2},{2,3}}

例子:一層層地壓平一個嵌套列表 另一個例子:

In[]:= testlist=Table[{i,j,k},{i,1,2},{j,1,2},{k,1,3}]
Out[]= {{{{1,1,1},{1,1,2},{1,1,3}},{{1,2,1},{1,2,2},{1,2,3}}},{{{2,1,1},{2,1,2},{2,1,3}},{{2,2,1},{2,2,2},{2,2,3}}}}
In[]:= Flatten[testlist,1]
Out[]= {{{1,1,1},{1,1,2},{1,1,3}},{{1,2,1},{1,2,2},{1,2,3}},
 {{2,1,1},{2,1,2},{2,1,3}},{{2,2,1},{2,2,2},{2,2,3}}}
In[]:= Flatten[testlist,2]
Out[]= {{1,1,1},{1,1,2},{1,1,3},{1,2,1},{1,2,2},{1,2,3},{2,1,1},{2,1,2},{2,1,3},{2,2,1},{2,2,2},{2,2,3}}
In[]:= Flatten[testlist,3]
Out[]= {1,1,1,1,1,2,1,1,3,1,2,1,1,2,2,1,2,3,2,1,1,2,1,2,2,1,3,2,2,1,2,2,2,2,2,3}

實際上,會更頻繁地用Flatten[expr]來得到一個完全平坦的列表。或者用Flatten[expr,1]來去掉一些內部的花括號當我們只需要一些起媒介作用的步驟而不再做其他操作。

應用:計算的二次規範的任意秩張量(向量,矩陣等)[編輯]

問題和解決方案 這裏我們會展示Flatten是怎樣戲劇性地簡化任意秩的張量的二次範數計算的用處。令人吃驚的是我們將不需要把張量的秩作為一個單獨的參數。所以,我麼將從這個代碼開始:

Clear[tensorNorm];
tensorNorm[x_List]:=Sqrt[Plus@@Flatten[x^2]];

這裏說明了這個簡潔的代碼是解決問題的主要部分。

解剖代碼: 讓我們先用一個例子來說明它是怎麼工作的。這是我們的矩陣:

In[]:= testmatr=Table[i+j,{i,1,3},{j,1,3}]
Out[]= {{2,3,4},{3,4,5},{4,5,6}}

範數就是對矩陣的所有元素的平方和開根號。首先,我們要利用一個事實:算數操作比如(raising to some power)能夠作用在整個列表上。因為它們能夠自動地貫穿整個列表(具有這種性質的函數說成是Listable)。因此,我們首先平方所有元素:

In[]:= testmatr^2
Out[]= {{4,9,16},{9,16,25},{16,25,36}}

既然我們並不關心元素確切的位置而只需要對它們求和,我們用Flatten來去掉內部的花括號:

In[]:= Flatten[testmatr^2]
Out[]= {4,9,16,9,16,25,16,25,36}

現在我們有了所有的元素組成的列表了,而且我們已經看到這個可以靠Plus@@來建造:

In[]:= Plus@@Flatten[testmatr^2]
Out[]= 156

最後,我們還要開平方:

In[]:= Sqrt[Plus@@Flatten[testmatr^2]]
Out[]= 2 Sqrt[39]

哈,我們得到函數代碼啦。我們看到函數很漂亮地作用具有任意秩的張量上而沒有作修改!沒有了Flatten是很難做到的,尤其是在像C語言的那些語言裏,我們會需要嵌套循環來完成。(在C語言裏,也是有一個叫做壓平數組的技巧,但是它存在於利用row-major order,而這個row-major order儲存在內存中並且 多維數組 雖然它能夠發揮作用,但那將是非法的如果你想嚴格遵照C語言的標準)。[10]


Clear[tensorNorm,testmatr];

應用:用Flatten(相當地)快地產生列表[編輯]

就像我們曾經談到的,在效率方面,在循環中直接生成列表可能是最糟糕的方式。我們可以使用Flatten來對這個過程進行提速,這是可以考慮的。我們想生成一個從1到10的列表(最簡單的,當然,只要Range[10]就能完成),我們用接下來的方式來做:

步驟一:我們生成一個嵌套列表(這種列表在Mathematica中也叫做linked lists):

In[]:= For[testlist={};i=1,i<=10,i++,testlist={testlist,i}];
       testlist
Out[]= {{{{{{{{{{{},1},2},3},4},5},6},7},8},9},10}

步驟二:我們使用Flatten:

In[]:= Flatten[testlist]
Out[]= {1,2,3,4,5,6,7,8,9,10}

讓我們來用之前使用Append的方式來對比一下運行時間

In[]:= For[testlist={};i=1,i<5000,i++,AppendTo[testlist,i]];//Timing
Out[]= {0.25 Second,Null}

現在是我們的新方法:

In[]:= (For[testlist={};i=1,i<5000,i++,testlist={testlist,i}];Flatten[testlist];)//Timing
Out[]= {0.016 Second,Null}

我們看到差距至少就在於命令的長度。即使這個方法本身並不是最有效率的,但是我們將會看到嵌套列表是怎樣被用來戲劇性地在某些問題中提高效率。

Clear[testlist];

列表間的集合運算[編輯]

經常會用到兩個或兩個以上列表的併集,交集和補集,也經常要去掉列表中的重複的元素。這些可以用內置函數Join,Intersection,Complement和Union來完成。

Join[編輯]

函數Join把兩個或兩個以上的列表連接起來。格式是Join[list1,…,listn],<list1,…,listn>是列表,不要求具有相同的深度和結構。如果列表包含相同的元素,這些相同的元素並沒有被刪除,也就是列表被連接起來,而在它們的內部結構中沒有更多的修改。例子:

Clear[a,b,c,d,e,f];
In[]:= Join[{a,b,c},{d,e,f}]
Out[]= {a,b,c,d,e,f}
In[]:= Join[{{a,b},{c,d}},{e,f}]
Out[]= {{a,b},{c,d},e,f}

Join從左往右把列表連接在一起,沒有對列表進行任何排序和變換。

Intersection[編輯]

函數Intersection找到兩個或兩個以上列表的共同元素,也就是說它將得到屬於所有列表的所有元素。格式是:Intersection[list1,…,listn]。例子:

In[]:= Clear[a,b,c,d,e,f];
       Intersection[{a,b,c},{b,c,d,e}]
Out[]= {b,c}
In[]:= Intersection[{a,b,c,d,e,f},{a,b,c},{c,d,e}]
Out[]= {c}
In[]:= Intersection[{a,b,c},{d,e,f}]
Out[]= {}

在最後的例子中我們得到一個空列表,因為兩個列表的交集是空集。

函數Intersection有一個選項SameTest,這個選項可以用來提供一個定製的「sameness」函數——即我們可以定義「same」的概念,與默認情況下是不同的。請看函數Union關於這個選項運用的例子。同時,有了這個選項,Intersection會變得很慢,比它單純的形式更慢。在附錄C中有詳細的描述。

Complement[編輯]

函數Complement[listAll,list1,…listn]給出其他列表<list1,…listn>的併集在列表<listAll>中的補足元素。換句話說,它返回在列表<listAll>中的而不再任何一個列表<listk>的元素。而且Complement還對結果排序。例子:

In[]:= Complement[{a,b,c,d,e,f},{b,c,e}]
Out[]= {a,d,f}
In[]:= Complement[{a,b,c,d,e,f},{b,c,e},{d,e,f}]
Out[]= {a}
In[]:= Complement[{a,b,c,d,e,f},{b,c,e},{d,e,f},{a,b,c}]
Out[]= {}

函數Complement,就像Intersection一樣也有選項SameTest。它允許我們定義關於「same」的觀念。所有的我對Intersection關於這個選項的註解,將在這本書里應用。

和列表排序相關的函數[編輯]

這裏我們討論非常和列表排序有關的有用的內置函數。函數Sort對列表進行排序。函數Union去除列表中的重複元素並且對結果排序。函數Split將列表分割為由相同的靠近的元素構成的子列表。對這三個函數都可以定義「sameness」的概念,而與默認的定義不同。下面,我們給出更多的細節和每個函數用法的例子。

Sort[編輯]

基礎Sort[編輯]

這個函數用來對列表進行排序。比如:

In[]:= Clear[a,b,c,d,e,f,g,t];
       Sort[{a,d,e,b,g,t,f,d,a,b,f,c}]
Out[]= {a,a,b,b,c,d,d,e,f,f,g,t}


In[]:= Sort[{5,7,2,4,3,1}]
Out[]= {1,2,3,4,5,7}

函數Sort可以對任意Mathematica表達式進行排序。由於疏忽(by default),Sort是依照詞典編撰對符號進行排序,按數字的升序進行或者依照列表的第一個元素。總之,這就是Mathematica中的標準排序順序,查詢Mathematica幫助文檔可以得到更多信息。

作為例子,我們對一個嵌套正數列表進行排序:

In[]:= nested=Table[Random[Integer,{1,15}],{5},{Random[Integer,{3,10}]}]
Out[]= {{13,3,11,7},{15,15,14,11,15,14},{11,10,2},{11,12,9,11,1,4},{7,4,15,11,9}}
In[]:= Sort[nested]
Out[]= {{11,10,2},{13,3,11,7},{7,4,15,11,9},{11,12,9,11,1,4},{15,15,14,11,15,14}}

我們看到排序是在按照子列表的第一個元素進行的。[11]

按照用戶定義的排序函數進行排序[編輯]

作為一個選項的第二個參數,Sort接受一個對比函數來替代默認函數。例子:

In[]:= Sort[{5,7,2,4,3,1},Greater]
Out[]= {7,5,4,3,2,1}

我們可以按照子列表的長度來排序嵌套列表。首先定義一個排序函數:

Clear[sortFunction];
sortFunction[x_List,y_List]:=Length[x]<Length[y];

然後我們來進行排序:

In[]:= Sort[nested,sortFunction]
Out[]= {{11,10,2},{13,3,11,7},{7,4,15,11,9},{11,12,9,11,1,4},{15,15,14,11,15,14}}

初識純函數[編輯]

Mathematica提供一個途徑來構造和使用函數,而沒有給出這些函數的名稱或者單獨的定義,這些就叫做「純函數」(pure function)(在一些語言中也叫做 函數(lambda function))。我們將系統性地介紹它,但是現在我們只是為了理解上面的排序過程才提及純函數。

In[]:= Sort[nested,Length[#1]<Length[#2]&]
Out[]= {{11,10,2},{13,3,11,7},{7,4,15,11,9},{11,12,9,11,1,4},{15,15,14,11,15,14}}

任何可以返回True或者False的帶有兩個變量的函數都可以稱為排序函數。這是假定了給出True或者False依賴於那一個元素更「大」。

我必須提到人們使用Sort——定義了排序函數,會使得程序變慢,在附錄C中有詳細討論。

Ordering[編輯]

該函數給出列表中元素按Sort順序排列的位置。它裏頭也可以有用戶定義的「純」形式——定義對比函數。它給出的信息比僅僅只有Sort的情況下更多,特別是結合Orderding和Part來排序列表。

列表:

In[]:= listtosort={a,d,e,b,g,t,f,d,a,b,f,c}
Out[]= {a,d,e,b,g,t,f,d,a,b,f,c}
In[]:= Ordering[listtosort]
Out[]= {1,9,4,10,12,2,8,3,7,11,5,6}
In[]:= listtosort[[Ordering[listtosort]]]
Out[]= {a,a,b,b,c,d,d,e,f,f,g,t}

Ordering是一個很有用的函數,正是因為它給出的信息比僅僅只有Sort的情況下更多,而且和Sort本身具有一樣的效率。我們將會在第六章看到它的例子。

Union[編輯]

函數Union格式是Union[list]返回的列表<list>所有不同元素的標準排序結果。

基礎Union[編輯]

在它的基本形式里,Union把列表看成一個單獨的變量,在一個列表裏返回已排序的唯一的元素。排序過程是在Mathematica默認排序函數下進行的(按照詞典編撰順序對符號表達式排序,對數字進行升序排列,依照列表的第一個元素等等)。例子:

In[]:= Union[{a,d,e,b,g,t,f,d,a,b,f,c}]
Out[]= {a,b,c,d,e,f,g,t}
In[]:= testlist=Table[Random[Integer,{1,10}],{15}]
Out[]= {9,7,4,3,1,1,8,2,2,10,7,4,9,1,4}
In[]:= Union[testlist]
Out[]= {1,2,3,4,7,8,9,10}

Union對結果進行排序這一事實,本質上是和Union內部算法有關係的。如果元素沒有被排序,人們可以寫出一個傳統的union函數(我們會在5.2.6.2.5考慮一些這方面的實現),這個函數將會比內置函數Union慢。

帶有SameTest選項的Union[編輯]

函數Union也有選項SameTest,它允許我們給出關於那些元素是「一樣」的定義。比如,我們認為只要兩個元素都是以3為模,那麼它們就是相同的:

In[]:= Union[testlist,SameTest->(Mod[#1-#2,3]==0&)]
Out[]= {1,2,3}

帶有SameTest選項的Union函數會變得很慢,比不帶選項的Union慢的多。在附錄C有更多的討論。

Split[編輯]

該函數用來把列表切成幾個子列表,以致於每個子列表的元素是相同的。它也可以接受「sameness」函數作為第二個可選的參數。它從頭到尾查看列表,並比較相鄰的元素,使用默認「sameness」函數(SameQ)或者是我們自己給出的「sameness」函數。每當相鄰的兩個元素不是一樣的,它就把已經是相同的元素放進一個列表裏,然後再重新開始另一個子列表。

基礎Split[編輯]

在基本形式中,Split把列表切成子列表,當只有一個參數時,使用SameQ來斷定元素的比較。這裏我們引入一個列表和它的排序結果:

In[]:= testlist=Table[Random[Integer,{1,15}],{20}]
       sorted=Sort[testlist]
Out[]= {8,12,10,3,13,15,13,6,6,2,4,9,5,11,6,10,7,4,15,5}
Out[]= {2,3,4,4,5,5,6,6,6,7,8,9,10,10,11,12,13,13,15,15}

因為一般在一個沒有被排序的列表裏相鄰的元素是不同的,我們看到大多數子列表會含有一個單獨的元素;

In[]:= Split[testlist]
Out[]= {{8},{12},{10},{3},{13},{15},{13},{6,6},{2},{4},{9},{5},{11},{6},{10},{7},{4},{15},{5}}

而一個已經排序的列表卻不是這樣:

In[]:= Split[sorted]
Out[]= {{2},{3},{4,4},{5,5},{6,6,6},{7},{8},{9},{10,10},{11},{12},{13,13},{15,15}}

帶有用戶定義的sameness函數的Split[編輯]

我們現在可以定義兩個元素的「相同」,如果它們除以3有相同的餘數.然而,在使用Split把相同的元素放在一起之前,我們將會用不同的排序函數來排序列表。以致於當相鄰的兩個數除以3有相同的餘數時,就把它們放在一個已經排好序的列表裏:

In[]:= mod3sorted=Sort[testlist,Mod[#1,3]<Mod[#2,3]&]
Out[]= {15,6,9,6,6,15,3,12,4,7,10,4,13,13,10,5,11,5,2,8}

現在我們可以split這個列表了:

In[]:= Split[mod3sorted,Mod[#1,3]==Mod[#2,3]&]
Out[]= {{15,6,9,6,6,15,3,12},{4,7,10,4,13,13,10},{5,11,5,2,8}}

Split是一個很有用的函數。因為它對列表執行一個簡單的掃視,而且只是比較相鄰的元素,具有線性的複雜性。再者,因為比較的次數等於列表的長度減一,和用戶定義的sameness函數(在函數Sort和Union那裏曾經討論過的)聯繫在一起時,它並不會碰到嚴重的性能上的障礙。

例子:run-length 編碼[編輯]

Split的一個標準應用是run-length編碼。給出一個含有儘可能重複元素的列表,這個編碼由用一個像{{num1,freqk1},…}列表替換原來的列表。<freqk>給出<numk>出現的總次數。比如,以我們第一個例子的結果來說明:我們需要做的是把每一個子列表變成我們剛才所說的那種形式,可以這樣做:

Clear[runEncodeSplit];
runEncodeSplit[spl_List]:=Table[{spl[[i,1]],Length[spl[[i]]]},{i,Length[spl]}];
Clear[runEncode];
runEncode[x_List]:=runEncodeSplit[Split[x]];

檢驗:

In[]:= runEncode[sorted]
Out[]= {{2,1},{3,1},{4,2},{5,2},{6,3},{7,1},{8,1},{9,1},{10,2},{11,1},{12,1},{13,2},{15,2}}

用函數式編程方法,我們可以去掉一個輔助函數runEncodeSplit:

Clear[runEncodeFP];
runEncodeFP[x_List]:=Map[{First[#],Length[#]}&,Split[x]];

檢驗:

In[]:= runEncodeFP[testlist]
Out[]= {{8,1},{12,1},{10,1},{3,1},{13,1},{15,1},{13,1},{6,2},{2,1},{4,1},
 {9,1},{5,1},{11,1},{6,1},{10,1},{7,1},{4,1},{15,1},{5,1}}[12]

例子:計算相同元素的頻率[編輯]

作為另一個與Split有關的應用,我們將會結合Sort來實現一個可以計算列表中相同元素的頻率的函數。如果我們注意到我們剛才已經對原來的列表進行排序了,就是剛才的runEncode函數作用在一個已經排好序的列表上,這是極其容易的。

Clear[frequencies];
frequencies[x_List]:=runEncode[Sort[x]];

檢驗:

In[]:= frequencies[testlist]
Out[]= {{2,1},{3,1},{4,2},{5,2},{6,3},{7,1},{8,1},{9,1},{10,2},{11,1},{12,1},{13,2},{15,2}}

實際上,本質上函數Frequencies被應用在附加的『Statistics『DataManipulation程序包中。

在很多其他情況下Split是很有用的,我們在接下來的章節中會給出更深入的例子。 Clear[testlist,sorted,mod3sorted,listDivide,frequencies,runEncodeSplit,runEncode,runEncodeFP];

小結:[編輯]

本章我們介紹了列表——在Mathematica中數據結構最重要的磚塊。我們考慮了幾種列表操作比如生成列表,元素的取出,添加,替換和刪除,在列表中定位具有某種特性的元素,還有幾種特別的函數:在一個或集合列表上進行快速的結構性操作。還有一些關於排序列表的函數。一下內置函數得到詳細的討論:Range,Table,Part,Extract,Take,Drop,First,Rest,Most,Last,Position,Length,Append,Prepend,AppendTo,PrependTo,Partition,Transpose,Flatten,Join,Union,Intersection,Complement,Sort,Split。

有了這些函數武裝起來的我們,已經對解決各種各樣的問題有了很大的幫助。然而,我們需要serious program building另一個主要的組成部分——對Mathematica函數的理解:它們是什麼?怎麼定義它們?等等。這是下一章的主題。

註記[編輯]

  1. 詳見Mathematica幫助文檔 ref/Depth
  2. 詳見Mathematica幫助文檔tutorial/ExpressionsAsTrees和ref/TreeForm
  3. 這裏譯得不好!原文:As these examples show,Table accepts one or more lists which indicate the iteration bounds,but we can fill the lists with some functions computed on the counters being iterated.In cases when we have more than one iterator, we create a nested list where the innermost iterators correspond to the outermost lists.As we see,the bounds of the more outermost iterators may depend on the variables of more innermost ones,in which case the lists will have sublists of different lengths.
  4. 原文在是用matrix的,但是考慮到在Mathematica中矩陣和列表還是有區別的,為了避免麻煩,故譯成列表。
  5. 此處的clean不能光是理解為「簡潔」,還有「避免產生其他的麻煩」的意思。
  6. 參見Mathematica幫助文檔tutorial/LevelsInExpressions中的「從樹結構的觀點看層次時,將樹的頂部看成第0層,一個表達式中的某一項所在的層次簡單地說就是它到頂層的距離.」準確些。
  7. Ted Ersek's Mathematica Tricks - Verbeia.com
  8. 原文:Cases is the command used to find the list of all occurrences of some expression or pattern in a larger expression.
  9. 我不確定這樣翻譯是否正確,原文:The situation with Prepend and PrenpendTo is completely analogous.And also,recalling the previous discussions, we may suspect that the application of AppendTo of PrependTo to a list which is not assigned to any variable(not an L-value)is a mistake,and we will be correct:
  10. 這裏出現嚴重的問題!!!原文:in C,there is also a technique called flattening an array,which consists in exploiting the row-major order in which it is stored in memory and going through the multidimensional array with just a pointer to an integer(or whatever is the type of the smallest array .
  11. 原文最後一個程序結果是有問題的,譯者已修改。參見Mathematica幫助文檔ref/Sort,對於表達式排序,Sort 首先將較短表達式的放在前面,然後按深度優先的方式比較子集。
  12. 這裏的testlist=Table[Random[Integer,{1,15}],{20}],即testlist={8,12,10,3,13,15,13,6,6,2,4,9,5,11,6,10,7,4,15,5}