

二次開發之陣列、字典和字串
與C語言中只能以整數作為下標的陣列不同,tcl 語言中的陣列索引可以是包括陣列,字母在內的任意字串。陣列和字典都是一種鍵-值對,後臺都是使用雜湊演算法(散列表)進行處理。
什麼是雜湊?雜湊可是電腦科學裡的一個偉大發明。它是由陣列、表和一些數學方法相結合構造出來的一種能夠有效支援動態資料存儲和提取的結構。維琪百科的解釋:散列表(Hash table,也叫雜湊表),是根據鍵(Key)而直接訪問在記憶體存儲位置的資料結構。也就是說,它通過計算一個關於鍵值的函數,將所需查詢的資料映射到表中一個位置來訪問記錄,這加快了查找速度。這個映射函數稱做散列函數,存放記錄的陣列稱做散清單。
一個通俗的例子是,為了查找電話簿中某人的號碼,可以創建一個按照人名首字母順序排列的表,在首字母為W的表中查找“王”姓的電話號碼,顯然比直接查找就要快得多。這裡使用人名作為關鍵字,“取首字母”是這個例子中散列函數的函數法則,存放首字母的表對應散清單。關鍵字和函數法則理論上可以任意確定。一圖頂萬言,還是看看下面這張圖吧。

第一部分:陣列
既然說了是後臺實現,所以大家在使用過程中可以不必糾結於具體的雜湊實現方式,那是語言開發人員的事。我們可以把它當成普通變數一樣用,只不過就是變數名中有括弧而已。
為了避免麻煩最好不要在鍵的索引名稱上使用空格,如果一定要用記得要特殊處理一下。另外,和其它地方的 tcl 字串一樣這裡的字串是不必放在雙引號裡面的,否則雙引號也是有效的索引的一部分。
% set altair(pre\ processer) hm
=>hm
% set altair("pre\ processer") hm
=>hm
% array name altair
=>business {"pre processer"} {pre processer} product city name
↑ 本例中如果空格不轉義將導致語法錯誤。
從結果可以看出轉義後的空格以及引號也變成了鍵的有效組成部分!
當變數的名字裡面還包含變數的時候,簡單變數需要特殊處理才能使用,點擊按鈕可以複習一下變數那一講~
但如果使用陣列就很方便啦,我們可以直接使用 $arrayname($elemname)的方法按照使用簡單變數的語法就可以得到變數 arrayname($elemname) 的值。
陣列的好處還包括可以把它看成是一組同類變數的無序集合,可以進行遍歷。
除了作為普通變數的集合外陣列常用的地方還包括定義全域變數或命名空間變數,避免過多全域變數或命名空間變數污染命名空間。例如你可以在 HyperMesh 中輸入 array get env 就可以看到 HyperMesh 眾多的環境變數。你可以用 puts $env(TCL_INCLUDE) 命令查看哪些目錄下來的 tcl 腳本是可以直接運行的。
另外,經常會使用陣列名作為參數進行函數的參數傳遞(一般 tcl 函數都是傳遞值,這裡我們用的是傳引用,而且這種方法保證原陣列不會被修改),這樣看起來簡潔多了。

下面我們來介紹一些陣列的常用操作:
1、創建一個陣列變數
方法一: 使用set
% set altair(name) {altair engineering}
=> altair engineering
% set altair(business) CAE
=> CAE
% set altair(city) shanghai
=> shanghai
% set altair(product) [list HyperMesh HyperView OptiStruct RADIOSS MotionSolve]
=> HyperMesh HyperView OptiStruct RADIOSS MotionSolve
方法二:array set
使用 array set 將清單轉化為陣列的元素,使用方法參加上圖第8行。
% array set hw {pre hm post hv solver {os rd ml}}
2、獲取所有變數的鍵-值對(無序)
% array get altair
=> business CAE product {HyperMesh HyperView OptiStruct RADIOSS MotionSolve} city shanghai name {altair engineering}
3、查看陣列中的所有元素名稱
% array names hw
=> post solver pre
4、查看陣列中某一個元素的值 /
使用陣列中某一個元素的值
% set hw(solver); #same as puts $hw(solver)
=> os rd ml
5、查看陣列中有幾個元素
% array size hw
=> 3
6、遍歷陣列
% foreach var [array names hw] {puts $hw($var)}
=> hv
=> os rd ml
=> hm
或者這樣遍歷陣列:
% foreach var [array names hw] {puts "the value of hw($var) is: $hw($var)"}
=> the value of hw(post) is: hv
=> the value of hw(solver) is: os rd ml
=> the value of hw(pre) is: hm
7、查看陣列是否已經存在
% array exist hw
=> 1
% array get hw
=> post hv solver {os rd ml} pre hm
8、刪除一個陣列
% array unset hw
% array unset altair
基本功能就是這些啦~ 接下來我們來看一個使用陣列的例子,該例子的目的是為了實現將各個單元按照單元中心距離 origin 點的距離進行排序。


upvar center center 的意思是引用調用者的 center 並可以在該程式中使用。由於 center 不是作為參數傳遞給函數的所以不用加 $ 首碼。實際使用的時候要儘量避免函數依賴於外部變數而變得難以重用,這裡的情況比較特殊。
如果遇到更加複雜的情況,字典的強大功能就會派上用場了。接下來我們來看看字典是怎麼回事。
第二部分:字典
字典是更複雜的資料結構,可以用來存儲具有層級結構的資料(注意陣列沒說可以嵌套哦),字典的結構就像一顆倒掛著的樹,就和你電腦上的目錄樹類似。比如你在 dos 中 cd 到某個目錄後輸入命令 tree 就可以得到該目錄的目錄樹(以我電腦上某個目錄為例):
簡單字典像這樣:

字典也可以非常複雜,像這樣(把老師家院子裡的棗樹搬出來用一下):

字典相關的主要操作有:
1、創建一個字典
2、查找某個鍵對應的值
3、列出所有的值
4、列出所有的鍵/遍歷所有的鍵
5、判斷某個鍵是否存在
6、查詢字典的鍵數
7、其它在這裡不介紹的高級操作,比如 dict for,dict with,dict filter 等
注意:字典可以用鍵找到對應的值,不能用值找到對應的鍵,因為鍵是唯一的而值不是(一個身份證號對應一個人名,反過來不成立)。
考慮到大部分人對字典會比較陌生,建議大家動手把下面的代碼自己敲一遍。
1、創建一個字典
% dict set colours colour1 red
或
% set colours [dict create colour1 "black" colour2 "white"]
2、查找某个键对应的值
% dict get $colours colour1
3、列出所有的值
% dict values $colours
4-1、列出所有的鍵
% dict keys $colours
4-2、遍歷所有鍵
% set colours [dict create colour1 "black" colour2 "white"]
% foreach item [dict keys $colours] {
% set value [dict get $colours $item]
% puts $value
% }
% foreach {key value} [set colours] {
% puts "$key -- $value"
% }
5、判斷某個鍵是否存在
% dict exists $colours colour1
6、查詢字典的鍵數
% dict size $colours
嵌套的字典:
% dict set comp part1 name "part1"
% dict set comp part1 id 12
% dict set comp part1 num_e 123
% dict set comp part1 color 18
% dict set comp part1 thick 1.5
添加另外一個 part 的資料:
% dict set comp part2 name "part2"
% dict set comp part2 id 23
% dict set comp part2 num_e 135
% dict set comp part2 color 15
% dict set comp part2 thick 2.0
% set comp
使用 dict set 一次只能添加一個鍵,如果希望一次添加多個鍵呢?一種方法是使用迴圈,另外一種更簡潔的方法是使用 dict merge。
% set part3 [dict create name "part3" id 323]
% set part3more [dict create num_e 3135 color 315 thick 32.0]
% set part3 [dict merge $part3 $part3more]
在 comp 中增加一個鍵 part3:
% dict set comp part3 $part3
% set comp
查找嵌套的字典某個鍵對應的值:
% dict get [dict get $comp] part1
% dict get [dict get $comp] part2 num_e
% dict get $comp part1 name
% dict get $comp part1 id
% dict get $comp part1 num_e
% dict get $comp part1 color
% dict get $comp part1 thick
% dict get $comp part2 name
% dict get $comp part2 id
% dict get $comp part2 num_e
% dict get $comp part2 color
% dict get $comp part2 thick
其它例子
創建一個字典
% set HyperWorks [dict create preprocess HyperMesh post HyperView year 31]
% set HyperWorks
% dict size $HyperWorks
% dict set HyperWorks solver1 OptiStruct
% dict set HyperWorks solver2 RADIOSS
% set HyperWorks
% dict size $HyperWorks
遍歷字典
% dict keys $HyperWorks
% foreach key [dict keys $HyperWorks] {puts [dict get $HyperWorks $key]}
% dict values $HyperWorks
% dict for {key value} $HyperWorks {
% puts "the key is: $key;\t\t\t\t\tthe value is: $value"
% }
修改字典中的一項
修改字典中的一項,並沒有一種方法可以完成所有類型的字典編輯,所以 tcl提供了不同的方法來實現不同的修改操作。
1、追加字串
% dict append HyperWorks CFD acuSolve
2、增加整型值
% dict incr HyperWorks year
3、追加列表
% dict lappend HyperWorks CFD nanofluidx
% dict get $HyperWorks CFD
% lindex [dict get $HyperWorks CFD] end
刪除一項,不會修改原字典:
% dict remove $HyperWorks solver1
% dict get $HyperWorks solver1
刪除一項,修改原字典:
% dict unset HyperWorks CFD
% dict get $HyperWorks CFD
替換一項:
% dict replace $HyperWorks year 33
修改一項:
% dict set HyperWorks year 35
以上字典的基本操作供大家練練手,下面我們來實戰一下~
實戰演練
目標:從一個csv檔中讀取載荷工況的名稱、作用點xyz座標以及xyz三分力並保存在一個字典裡面。
部分csv檔的內容如下,全檔共有18個工況。第一行是工況名,第二行的6列分別對應載荷的作用點xyz座標和三個分力,其餘類似:

首先我們把所有的行讀取到清單變數裡,代碼如下:

上面的代碼和字典無關,看不懂的可以複習一下前面的課程~
第一行的目的是為了放在該程式已經運行過一次,重新運行時先把變數清空。。
接下來我們對每一行的資料進行解析,得到一個字典:

注釋
最後當然要檢驗一下程式的結果是否和我們預期的一致:


要把這18個工況創建出來還需要一點其它語句,不如讓大家自己探索吧~
第三部分 字串
既然前面說到 tcl 中一切都是字串,那麼 tcl 具有強大的字串功能是很自然的事情了。我們定義一個字串的時候可以把字串放在雙引號或者大括弧中,區別是大括弧中無法進行替換而雙引號可以。
無論是雙引號還是大括弧中的字串都可以包含多行,像這樣:

為了方便大家學習,我把 tcl 字串操作簡單分了下類,但因為微信篇幅有限沒辦法為大家詳細介紹,大家可以自行學習哈~

本篇技巧實在太多,篇幅很長,但文章快要接近尾聲啦,大家再加把勁,加油!
修改

append
我們先從 append 講起,因為它最常用,用法也很簡單。它的優點是:跑得特別快!
% set str begin
% append str nest
=> set str begin
=> append str nest
string tolowerr/toupper/totitle
string tolower/toupper/totitle 是進行大小寫轉換
sting reverse
sting reverse 反轉字串
% string reverse altair
=> riatla
string replace
string replace 替換一段字串
% string replace HyperMesh 5 end View
=> HyperView
subst
subst 可以進行反斜線替換、變數替換、命令替換,也可以只進行部分類型的替換。因此,使用 subst 可以進行更加靈活的命令解析。很有意思的一點是,一般的 tcl 命令對大括弧都是區別對待,唯有 subst 不把它當回事。例如:
% set module mesh
% subst {name {$module}}
=> name {mesh}
format
接下來介紹 format,大家學過別的語言的話可能會問 tcl 怎麼實現字元和 ascii 碼直接的互相轉換,我們這就來告訴大家~

比較

string equal
string equal 和eq是一樣的,還有neq是判斷是否不相等(還記得嗎?數值比較要用==)
string first ,string last
string first 和 string last 只能用於查找單個字元的位置,不能用於查找一個單詞。查找單詞可以先用 string range 截取一段要比較的字串,然後 string equal 或者 string compare 進行比較。
prefix,string match
prefix,string match 以及規則運算式通常用於模式比較,簡單說就是比較字串的一部分是否相同,實際情況下這是比較常用的。
prefix 可以查找一個字串的首碼,直接看 help 就能明白。
% prefix match {apa bepa cepa} apa
=> apa
% prefix match {apa bepa cepa} a
=> apa
% prefix match -exact {apa bepa cepa} a
=> bad option "a": must be apa, bepa, or cepa
% prefix match -message "switch" {apa ada bepa cepa} a
=> ambiguous switch "a": must be apa, ada, bepa, or cepa
% prefix longest {fblocked fconfigure fcopy file fileevent flush} fc
=> fco
% prefix all {fblocked fconfigure fcopy file fileevent flush} fc
=> fconfigure fcopy
截取

上圖中的這些很容易懂,大家可以看幫助自學哈~

string index 和列表的 lindex 基本上是雷同的,也不過多介紹了哈~
檢查
string is

這裡要特別注意一下空字串,我們一般認為 { } 是不屬於整數類型的。但如果不加 -strict 選項進行控制的話 tcl 就會認為空字串也是整型(或者其它類型),所以我們最好在使用的時候始終加上 -strict
% string is integer {}
=> 1
% string is integer -strict {}
=> 0
其他

string repeat
在HyperMesh二次開發的時候經常會需要創建類似 “1 1 1 1 1 1 1 1 1 ” 這樣的字串,比如 RBE3單元從節點的權重,load 卡片的比例係數等。
這時可以使用 string repeat 免去一個迴圈:
% string repeat * 80
=> ********************************************************************************
string length
string length 就是測量一下字串有幾個字元。
scan
scan 在上面那個 ascii 碼轉換的函數中已經使用過了,和 C語言一樣,scan 可以從字串中識別需要的輸入,説明中的例子如下:
% set string "(5.2,-4e-2)"
% scan $string " (%f ,%f %c" x y last
=> 3
% set x
=> 5.2
% set y
=> -0.04
% set last
=> 41; #这里返回的是ascii码值
不過 ascii 在 tcl 中的用得很少,所以對 scan 簡單瞭解就行。
string map
string map 用來進行字元映射,有點像加密/解密的過程,我們有時用來處理tcl的特殊字元(大括弧雙引號斜杠等),先將特殊字元轉換成普通字元進行處理,處理完了再轉回去。
% string map {abc 1 ab 2 a 3 1 0} 1abcaababcabababc
=> 01321221
累了嗎?放鬆下眼睛,我們再來看下一個知識點~😏
規則運算式
以上這些字串功能都只是家常菜,我真正最喜歡的是tcl中的規則運算式。你可能又要問我什麼是規則運算式了。好吧,維琪百科的解釋是這樣的:
規則運算式是電腦科學的一個概念。規則運算式使用單個字串來描述、匹配一系列匹配某個句法規則的字串。在很多文字編輯器裡,規則運算式通常被用來檢索、替換那些匹配某個模式的文本。

tcl 對於規則運算式有非常完善的支持。
規則運算式可以作為處理字串問題的萬能鑰匙,如果你想深入瞭解規則運算式,推薦你看一下下面這本書。
雖然說規則運算式強大無比,但是同時也是很難用的,編寫過程通常需要反復調試才能得到正確的運算式。篇幅限制,無法在本期為大家介紹了,也許我們可以後面加一期專門介紹。
最後,別忘了如果使用純字串功能難以處理還可以通過 split 函數以及 file 族函數將字串轉換為清單進行處理,例如下面的例子中我們希望將 component 名字中的材料和厚度資訊剝離出去。

輸出如下:
=> part
=> part1
=> part1_2
=> part__name
這一講就到這裡啦,感謝看到這裡的你們。歡迎大家給我們留言交流,喜歡教程的話,不妨給我們點個讚或者分享出去唷~
我們下期再見~
下期預告:控制結構
介紹完控制結構和函數(其實我們每一講都在用)大家就可以搞定一般的HyperMesh二次開發問題啦~
當時是誰說要課後作業的?快來領取!
課後作業
寫一個腳本實現下圖中的功能:任選一圈或幾圈washer,在半徑方向切成2層。