4、表達式的計算


4、表達式的計算                              返回目錄頁
一、表達式
二、表達式的計算過程
三、變換規則初步
四、模式
五、變換規則和定義
六、變換規則+模式匹配

再次引用網友的話:
其實,Mathematica是一個基於規則和模式的重寫系統。藏在各類炫目功能和編程形式背後的是一個精心設計的規則替換和模式匹配引擎。Mathematica中的函數是規則,變量也是規則,甚至能夠說在Mathematica裏變量和函數根本沒有本質區別由於它們都是被附加了規則的符號而已。這在其它語言中是很難想象的事情,也正式由於這一點,不少在傳統語言中難以作到的事在Mathematica都能實現。好比:在運行過程當中修改函數的定義。
通過巧妙的假裝,這個重寫系統能模擬出函數式風格,並且模擬地很好,rule-based編程天然也是水到渠成,過程式風格也能恰好湊合,這不能不說是很特別!
( 來源於知乎:https://www.zhihu.com/question/20324243

這章才進入MMA編程的核心層面:表達式與表達式的計算。

-------------------------------------------------------------------------
一、表達式
MMA處理多種不一樣形式的對象:數學公式、列表及圖形等。
儘管它們在形式上看起來有所不一樣,可是MMA以統一的方式來表達它們。它們都是表達式。

在MMA中,表達式更重要的用途是保持一種結構,使得該結構能被其餘函數調用。
這種結構,就是樹形結構

TreeForm[x^3 + (1 + x)^2]
咱們能夠觀察到樹形結構的圖形。
固然,表達式也能夠用一種縮進的文本格式呈現,上一章中咱們已經看到了。
其實,代碼自己,也是一種樹形結構的呈現呀。不過要當心,代碼可能會是這樣的:
x^3 + (1 + x)^2 (這是爲了人們可以閱讀方便)
可是,在MMA的內部,在表達式的計算過程當中,表達式以「全名」的標準格式存放:
x^3 + (1 + x)^2 // FullForm
得:Plus[Power[x,3],Power[Plus[1,x],2]]
全名錶達式自己也是一種樹形結構的呈現方式,內層在樹葉方向,外層在樹根方向。


-------------------------------------------------------------------------
二、表達式的計算過程
MMA是解釋性語言,輸入表達式,運行,而後獲得輸出。(儘管MMA具備將部分代碼進行編譯的功能。)
在運行過程當中,即解釋語言的過程當中,表達式通過計算。
表達式的計算分爲兩種:標準的與非標準的。
非標準的包括如下等等:
x=y
不計算左邊的x。
If[p,a,b]
當p爲Ture時,計算a不計算b。反之則反之。

----------------------------------------
表達式的標準運算過程。
標準計算過程採用深度優先方式遍歷表達式樹。感性講,就是先計算表達式的內層,而後一層層向外層。
這只是講了表達式計算的順序。
具體講,表達式的計算過程,是個遞歸的過程:
一邊在規則庫中搜索規則(rule),一邊進行變換(transformation),直到無規則可用爲止。
規則,又叫變換規則(transformation rules)。
變換,又叫代碼重寫(term rewriting)。
這意味着MMA反覆進行計算直到結果再也不變化爲止。
這是一個整體的過程描述。也是MMA的核心計算方法。

至於具體到一個簡單的表達式,計算過程是:
計算表達式的頭部。
依次計算表達式的每一個元素。
使用與屬性 Orderless、Listable 和 Flat 相關的變換規則。(由於是入門教程,這個不展開)
使用已經給出規則。(好比自定義函數)
使用內部規則。(好比內置函數)
計算出結果。

這一套計算方法,有個名稱,叫:Rule-based programming
這正是MMA的特別之處,也是MMA特別強大的核心緣由。

那規則究竟是啥呢?
咱們按這樣的步驟步步細化:先講一些簡單的規則,再講模式(模式匹配爲規則服務,使變換功能更增強勁),再講一些規則以後,而後最終使變換規則與模式匹配合璧。


-------------------------------------------------------------------------
三、變換規則初步
小金魚對漁夫說:你要啥呢?
漁夫說:我要一套豪宅。
啥 -> 豪宅

----------------------------------------
變換規則的基本格式。
left-hand side -> right-hand side
簡寫爲:
lhs -> rhs

lhs -> rhs // FullForm
得:Rule[lhs,rhs]
語法糖用得比較多,比較容易閱讀。

規則自己也是表達式,能夠「批量生產」:
Table[f[i] -> i!, {i, 5}]

----------------------------------------
運用變換規則。

expr /. lhs->rhs  對 expr 運用變換規則
expr /. {lhs1->rhs1,lhs2->rhs2],...}  將一列變換規則用於expr的每一項

x + y /. x -> 3
得:3 + y
x + y /. {x -> a, y -> b}
得:a + b

還能夠這樣,獲得一個表:
x + y /. {{x -> 1, y -> 2}, {x -> 4, y -> 2}}
得:{3, 6}

Solve 和 NSolve 等函數的返回值是一列規則,每一個規則表明一個解:
Solve[x^3 - 5 x^2 + 2 x + 8 == 0, x]
得:{{x -> -1}, {x -> 2}, {x -> 4}}

重複替代運算 //. 使得規則被反覆使用直到表達式再也不變化爲止:
x^2 /. {x -> 2 + a, a -> 3}
x^2 //. {x -> 2 + a, a -> 3}

注意二者區別:
expr /. rules  在 expr 的每一項中用變換規則一次
expr //. rules 重複使用規則直到結果再也不變化爲止

----------------------------------------
計算順序。
對於Rule函數,即lhs -> rhs,在運用於表達式時,計算順序爲:
先計算表達式,再計算規則左邊與右邊,而後,表達式中與規則左邊匹配的部分,均被規則右邊替換。

舉個栗子:
Table[x, {3}] /. x -> RandomReal[]
再用Trace跟蹤一下:
Table[x, {3}] /. x -> RandomReal[] // Trace
依次計算,而後表達式中的x被隨機數替換。能夠看到,三個隨機數是相等的。

還有一種RuleDelayed函數,基本格式是:
lhs :> rhs
lhs :> rhs // FullForm
當它運用於表達式時,計算順序就不一樣了:
先計算表達式,再計算規則左邊(右邊不計算!),而後,表達式中與規則左邊匹配的部分,均被未被計算過的規則右邊替換
由於右邊在替換前未被計算過,因此叫Delayed

Table[x, {3}] /. x :> RandomReal[]
Table[x, {3}] /. x :> RandomReal[] // Trace
因此能夠看到,獲得的三個隨機數是不一樣的。

總結一下:
lhs->rhs    給出規則後就計算 rhs
lhs:>rhs    使用規則時計算 rhs


-------------------------------------------------------------------------
四、模式
小金魚對漁夫說:你要啥呢?
漁夫說:我要比一套豪宅好的東西。
_啥 -> 比一套豪宅好的東西

----------------------------------------
上面講的變換規則,其實功能很弱,爲啥呢?
由於以上的變換規則,都太單調、太死板了,要求徹底匹配。
有了模式(patterns)的概念以後,匹配的範圍就劇烈增大了。
模式純粹是爲了變換規則服務,使規則變換時匹配功能大大加強了。
之前,漁夫只能要一套豪宅。如今,有了模式匹配功能,漁夫能夠要比一套豪宅好的任何東西了。
這就是模式的意義所在。也是MMA功能強勁的根本緣由。
(有些書在講規則以前就講模式,這樣容易讓人看得一頭霧水。因此在這裏咱們開門見山:模式這一輩子,只爲規則而活。)

模式的基本格式。
模式至少包含如下三種下劃線(blank,又翻譯成空位)的一種:
_    a single blank (有時被稱做通配符(wild card)模式,由於它能夠匹配任何表達式)
__    a double blank
___    a triple blank

_ 能夠匹配任何一個,表達式。
__ 能夠匹配,一個或一個以上表達式所組成的,序列。
___ 能夠匹配,零個或零個以上表達式所組成的,序列。(這裏的逗號看似語法錯誤,實際上是故意的。。)
所謂序列(sequence),就是常常寫在[]內做爲函數參數,或者寫在{}內,做爲表元素的東西,用逗號分隔。
這樣說有點囉嗦,換種說法:一個序列由若干逗號分隔的表達式構成。

模式塊能夠單獨使用,也能夠命名。
好比命名爲x_,則能夠在變換規則的右端引用它。

----------------------------------------
能夠把下劃線放在表達式中的任何位置,來造成匹配模式。這是至關靈活的,請看:

f[n_]        變量名爲 n 的 f
f[n_,m_]    變量名爲 n 和 m 的 f
x^n_        指數爲 n 的 x 的冪
x_^n_        任何次冪的表達式
a_+b_        兩個表達式的和
{a1_,a2_}    兩個表達式組成的列表
f[n_,n_]    有兩個相同變量的 f

判斷可否匹配,主要用兩個函數:

MatchQ[x, x^n_]
得:False
雖然,x的1次方等於x,但x的內部形式並非x的1次方,因此沒法與x^_n匹配。

Cases[{3, 4, x, x^2, x^3}, x^n_]
得:{x^2,x^3}
Cases返回可以匹配的表中元素。這樣很容易看出,x與x^n_不匹配,但x^2與x^3是匹配的。

這樣看就更清楚:
{3, 4, x, x^2, x^3} /. x^n_ -> n
得:{3, 4, x, 2, 3}

Cases[{{a, b}, {}, {1, 0}}, {p_}]
Cases[{{a, b}, {}, {1, 0}}, {p__}]
Cases[{{a, b}, {}, {1, 0}}, {p___}]
觀察輸出,三種下劃線各匹配什麼序列,就很清楚。p_能夠匹配任何表達式,但不能匹配序列。

----------------------------------------
模式中的表達式類型限制。
能夠經過頭部來區分不一樣"類型"的表達式。
模式中,_h 和 x_h 表示具備頭部 h 的表達式。
例如,_Integer 表示任何整數,而 _List 表示任何列表。

x_h        具備頭部 h 的表達式
x_Integer    整數型
x_Real        實數型
x_Complex    複數型
x_List        列表型
x_Symbol    符號型

{a, 4, 5, b} /. x_Integer -> p[x]
得:{a, p[4], p[5], b}
整數四、5與x_Integer匹配,因此進行了轉換。a、b不是整數,保持原樣。

----------------------------------------
限制模式。
Mathematica 中提供了對模式進行限制的通常方法。
這能夠經過在模式後面加 /;condition 來實現。
此運算符 /; 可讀做"斜槓分號"、"每當"或"只要",其做用是當所指定的 condition 值爲 True 時模式才能使用。

pattern/;condition    當條件知足時,模式才匹配
lhs:>rhs/;condition    當條件知足時,才使用規則
lhs:=rhs/;condition    當條件知足時,才使用定義

fac[n_ /; n > 0] := n!
fac[6] + fac[-4]
得:720 + fac[-4]
n>0限制了-4與n_匹配,因此變換沒有發生。

Cases[{3, -4, 5, -2}, x_ /; x < 0]
得:{-4, -2}
x<0限制了正數三、5的匹配。

能夠將 /;condition 放在 := 定義域或 :> 規則後告訴 MMA 只有當指定的條件知足時才能使用此定義或規則。
但要注意 /; 不能放在 = 或 -> 規則後,由於這些是當即被處理的。

Table[x, {3}] /. x -> RandomReal[] /; 1 == 2
Table[x, {3}] /. x :> RandomReal[] /; 1 == 2
觀察結果,頗有意思。第一句也能運行,只是把RandomReal[] /; 1 == 2部分都當成了規則的右邊。
第二句,condition條件限制了變換。變換沒有發生,因此表是原樣的。


在 MMA 中有一類函數去測試表達式的性質。這類函數後有一個 Q,代表它們在"提問"(Question?)。
IntegerQ[expr]    整數
EvenQ[expr]    偶數
OddQ[expr]    奇數
PrimeQ[expr]    素數
NumberQ[expr]    任何數
NumericQ[expr]    數字型

{2.3, 4, 7/8, a, b} /. (x_ /; NumberQ[x]) -> x^2
僅對數字求平方。

還能夠這樣用:
MatchQ[{1, 2, 3}, _?ListQ]
True
MatchQ[{1, 2, 3}, _?NumberQ]
False

{2.3, 4, 7/8, a, b} /. x_?NumberQ -> x^2
這樣寫把/;捨棄了,代碼更緊湊,也更易讀了。

----------------------------------------
有多種供選方案的模式。
選擇模式(alternatives)是指,由幾個獨立的模式構成的模式,這一模式與表達式匹配,只要構成它的任一獨立模式與這個

表達式匹配便可。

MatchQ[x^2, x^_Real | x^_Integer]

也能夠這樣用:
{a, b, c, d} /. (a | b) -> p
a或者b,均變換成p。


-------------------------------------------------------------------------
五、變換規則和定義
MMA的符號結構支持廣義的賦值概念,您可使用MMA的模式定義指定任意類型表達式的轉換。
通常咱們將帶等號的表達式(賦值語句),稱爲「定義」。
全部定義,包括自定義函數,及內置函數,本質上也是變換規則(有時或叫重寫規則)。
表達式的計算過程,就是不斷重寫的過程,直到無重寫規則可用爲止。
這節咱們來看各類定義。

----------------------------------------
在第三節中,咱們講了關於表達式的變換規則。還有兩種重寫規則:
內置函數(built-in function)和用戶自定義重寫規則(user-defined rewriting rule)。
用戶自定義重寫規則可使用Set函數和SetDelayed函數來創建,基本格式是:
lhs = rhs
lhs := rhs

rand1[x_] := RandomInteger[{1, x}]
rand2 = RandomInteger[{1, 8}]

能夠看到,第一行沒輸出,第二行有輸出。那是由於Set函數被輸入時,右邊立刻進行計算,而有輸出。
而SetDelayed函數被輸入時,右邊是不計算的,要等到被調用(重寫)時,才進行計算。
(*
rand1[x_] := RandomInteger[{1, x}];
不少時候,能夠在結尾加上分號,增長閱讀性。常常結尾有分號的是不輸出的語句。
對於定義自己來講,最後的分號加不加都無所謂——反正右邊不計算,加了也無輸出。
而對於當即賦值如:
rand2 = RandomInteger[{1, 8}]
要看具體狀況。加不加分號右邊都計算了。加了分號無輸出,不加反之。
*)

當咱們要自定義一個函數時,並不要求左邊與右邊被求值。因此SetDelayed函數一般被用來寫一個函數的定義。
當咱們對一數值進行聲明(declaration)時,咱們並不打算對左邊進行計算,只是想給數值一個比較簡便的別名。這正是Set函數能夠爲咱們作到的。

如今咱們理解了,自定義函數也好,內置函數也好,都是重寫規則。這與C、Pascal等語言中的函數實現大相徑庭。
咱們能夠從通常的函數概念中解放出來,以寫重寫規則的方式來寫自定義函數,能夠寫出不少很是靈活的自定義函數。好比:
h[{x_, y_}] := x^y
h[{2, 3}]
得:8
也就是說,函數的參數,徹底是能夠形式多樣的。

而對於別名,咱們也知道了,這是一個重寫規則。無非是通常數值在形式上比較長,取個別名方便使用。
lis = {1, 2, 3, 4, 5, 3, 2, 4, 3};
Apply[Plus, lis]

除 Module 和 Block 等一些內部結構外,在 Mathematica 中的全部賦值都是永久的。
若沒有清除或改寫它們,在MMA的同一個進程中所賦值保持不變。
賦值的永久性意味着使用時要特別慎重。一個在使用MMA時,常犯的錯誤是在後面使用 x 時忘記或誤用了前面 x 的賦值。
因此當你發現結果極不正常時,第一個要想到的是用Clear函數清除別名。

總結一下:
lhs=rhs (當即賦值)    賦值時當即計算 rhs
lhs:=rhs (延時賦值)    每次須要 lhs 時計算 rhs


----------------------------------------
特殊形式的賦值
i++    i 加 1
i--    i 減 1
++i    先給 i 加 1
--i    先給 i 減 1
i+=di    i 加 di
i-=di    i 減 di
x*=c    x 乘以 c
x/=c    x 除以 c

這估計是向C語言學的,在形式上如出一轍。
i+=di 至關於i=i+di,其他類推。

----------------------------------------
定義帶標號的對象
能夠把表達式 a[i] 看成一個"帶索引"或"帶下標"的變量。
這與C或Pascal中的數組不是同一律念。
有時候變量多時,這種方式能夠提升程序閱讀性。固然,能夠有更多的用處。。

下標不必定是數字,能夠是任何表達式。好比:
a[one] = 1
a[two] = 2

a[i]=value    改動變量的值
a[i]    調用變量
a[i]=.    刪除變量
?a    顯示定義過的值
Clear[a]    清除定義過的值

----------------------------------------
修改內部函數。
這個,屬於插播。。
在MMA中能夠對任何表達式定義變換規則。不只能夠定義添加到 Mathematica 中去的函數,並且還能夠對MMA的內部函數進行變換,因而就能夠加強或修改內部函數的功能
這一功能是強大的,同時也具備潛在的危險。。。
由於危險,仍是本身去看幫助文檔吧:)


-------------------------------------------------------------------------
六、變換規則+模式匹配
這裏分析兩個較爲複雜的栗子。
先作深呼吸七次。(爲啥是七次?由於七是神奇數字:))

----------------------------------------
what1[x_List] := x //. {a___, b_, c___, d_, e___} /; d <= b :> {a, b, c, e}

而後,凝視這一串語法糖數分鐘,想想這個函數是幹啥的?
想不出來麼?先別看下面的分析,去作一個隨機整數表,做爲參數。而後調用一下這個函數,看看得出什麼結果?
看不出來?那產生隨機整數表n個,觀察輸出n個,而後應該就清楚了。


----------------------------------------
what2[L_List] := Map[({#, 1}) &, L] //.  {x___, {y_, i_}, {y_, j_}, z___} -> {x, {y, i + j}, z}

同理,深呼吸七次。。。
同理,觀察輸出n次。。。

----------------------------------------
Table[i, {30}] /. i -> 刷屏一頁
得:
{刷屏一頁, 刷屏一頁, 刷屏一頁, 刷屏一頁, 刷屏一頁, 刷屏一頁, 刷屏一頁, 刷屏一頁, 刷屏一頁, 刷屏一頁, 刷屏一頁,

\
刷屏一頁, 刷屏一頁, 刷屏一頁, 刷屏一頁, 刷屏一頁, 刷屏一頁, 刷屏一頁, 刷屏一頁, 刷屏一頁, 刷屏一頁, 刷屏一頁,

\
刷屏一頁, 刷屏一頁, 刷屏一頁, 刷屏一頁, 刷屏一頁, 刷屏一頁, 刷屏一頁, 刷屏一頁}







----------------------------------------
第一題分析:

lis := Table[i, {8}] /. i :> RandomInteger[{0, 8}]
what1[x_List] :=
 x //. {a___, b_, c___, d_, e___} /; d <= b :> {a, b, c, e}
Table[li = lis; {li, what1[li]}, {10}]
觀察結果,能夠看到一個遞增序列:凡是比前面任何一個數字小的數字,都會被刪除。

//. 不斷變換,直到不能變換爲止。
/; d <= b 這部分,是個條件,對 {a___, b_, c___, d_, e___}  的模式匹配進行限制。
凡是還存在着符合匹配的,就不斷進行如下轉換:
{a___, b_, c___, d_, e___} /; d <= b 轉換成 {a, b, c, e}
{a序列,b元素,c序列,d元素,e序列}中,若是d元素不大於b元素,則表在進行重寫轉換時,把d元素刪除了。
由於這個計算過程要不斷進行,因此要用//.進行不斷變換。
理解這段程序的關鍵,在於模式匹配:包括表中序列的匹配與元素的匹配。


----------------------------------------
第二題分析:

what2[L_List] := Map[({#, 1}) &, L] //. {x___, {y_, i_}, {y_, j_}, z___} -> {x, {y, i + j}, z}
lis := Table[i, {8}] /. i :> RandomInteger[{0, 8}]
Table[li = lis; {li, what2[li]}, {5}]
觀察結果知道,這是個統計連續數個數的程序。(不進行排序是由於這樣記錄了原來的順序,很容易還原)

Map[({#, 1}) &, L]
Map函數,是把無名函數 ({#, 1}) & ,分別做用於表L中的每一個元素。
這一步至關聰明,至關於起始工做完成。
得表再進行模式匹配的規則轉換:
{x___, {y_, i_}, {y_, j_}, z___} -> {x, {y, i + j}, z}
左元素相同的,連續排列的: {y_, i_}, {y_, j_}
合併成:{y, i + j}。左元素不變,右元素累加。這下明白爲啥開始的時候右元素爲1了吧?
由於轉換是不斷進行的,因此用//. ,直到再也沒有匹配,不再能轉換爲止。
理解這個程序的關鍵,又是模式匹配。

由於表是MMA中的經常使用數據結構,因此對錶進行這類模式匹配的變換操做仍是比較實用的。
程序還能夠這樣編寫?是啊,這是MMA的獨特之處。

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

有人說,MMA就是個大玩具。確實如此。
內置函數那麼多,這些函數,在運行時,是能夠修改功能的!
經過模式匹配,規則變換,能夠把函數功能發揮到極致。

看完這一章,咱們知道了:在MMA中,過程式編程和函數式編程,都是模擬出來的,MMA的核心編程方法是html

Rule-based programming(基於規則的編程方法,簡略說,就是:規則編程編程

 

++++++++++++++++++++++++++++++數組

擴展閱讀:知乎上的,Wolfram Language話題精華






                    Top




數據結構

相關文章
相關標籤/搜索