二次開發的教程已經出了幾期了,大家是不是對二次開發的瞭解越來越深入了呢?從這一講開始,我們要越來越多地和 HyperMesh 打交道啦。
通過上一期的基礎培訓,相信大家應該瞭解了一些程式設計的基本概念了吧,比如變數,字串,if 和 for 等等。如果忘記了趕緊複習一下上期內容吧~
此外,上一期的小提示不知道大家還記得嗎?為了方便大家識別,我們把輸入部分放在%後面,把輸出放在=> 後面,這篇教程也同樣適用哈。
言歸正傳,一般的程式設計語言中有很多種資料類型:字元、字串、布林型、整型、實型、陣列、結構、類等等,而且一種變數還分成很多子類,比如整型又分為普通整型、長整型、枚舉型,普通整型又分為帶符號的和不帶符號的等等。
Tcl中的變數也可以分為以下幾種類型:字串、整型、實型、清單、陣列、字典。
是不是感覺看起來很複雜?其實也不儘然,下面我們通過舉例詳細說明一下~
Tcl 用 set 命令定義變數
► 字串:% set name HyperWorks
► 整型: % set num 3
► 實型: % set num 3.2
► 陣列: % set hw(pre) HyperMesh
► 字典: % set dict1 [dict create pre hypermesh post hyperview solver RADIOSS solvers optistruct]
但是在腳本層面上你可以認為 Tcl 只有一種資料類型,就是字串。
例如下面這條命令定義了一個變數,變數的值是 a 1 b 2 c 3:
% set var1 {a 1 b 2 c 3}
那麼現在的var1這個變數裡面到底存儲的是什麼類型的數值呢?先不急著回答,我們先用幾個命令來試試~
% string range $var1 0 3
=> a 1
► 結論1:var1是字串類型
% lindex $var1 1
=> 1
► 結論2:var1是列表類型
% dict get $var1 a
=> 1
► 結論3:var1是字典類型
WHAT?!既是字串又是清單又是字典?什麼情況?
因為 Tcl 是弱類型的程式設計語言。在 Tcl 中一切都是字串,必要的時候會自動轉換成其它相應的類型。弱類型的好處是程式設計具有很大的靈活性,付出的代價是程式跑得又慢佔用的記憶體又多而且出錯的可能性也更大。好消息是,Tcl 語言的開發者也知道這個情況,這就是上一講中為什麼讓大家把 expr後面的運算式放在 { } 裡面的原因,這樣可以減少資料類型轉換,加快計算速度。
一般的程式設計語言中的變數名只允許字母數位和底線,但是 Tcl 不受此限制。(你可以隨意給變數取名,但這通常是個非常壞的主意!)
% set abc 123
=> 123
% set 123 abc
=> abc
% set {a b c} 333
=> 333
► 判斷普通變數是否存在使用 info exist
% info exist {a b c}
=> 1
% puts ${a b c}
=> 333
► 刪除變數使用 unset,如果直接使用未定義的變數,會報錯。
% unset abc
% puts $abc
=> can't read "abc": no such variable
► 如果同時定義3個變數 comp_1、comp_11 和 {comp_1 1}
% set comp_1 shell
% set comp_11 solid
% set {comp_1 1} mix
那麼
% puts $comp_1
% puts $comp_11
% puts “$comp_1 1”
分別會輸出什麼結果呢,我們來看看:
% puts $comp_1
=> shell
% puts $comp_11
=> solid
% puts “$comp_1 1”
=> shell 1
► 如果comp_11未定義呢?下麵哪條命令可行?不妨自己試一試,失敗是成功之母喲,也正好考察考察你是否理解了上一講的內容~
% puts $comp_1 1
% puts ${comp_1 1}
% puts $comp_1\ 1
% set var_name {comp_1 1}; puts $$var_name
% set var_name “comp_1 1”; puts $[set var_name]
% set var_name “comp_1 1”; puts [set [set var_name]]
► 為了避免類似問題,當不確定時就把變數名放在 { } 裡面((但是如果裡面有變數就不能這麼幹了,最好用陣列或者字典替代)。
列表類型
接下來介紹列表類型,這是最最最重要的類型。因為 HyperMesh 幾乎總是返回列表,同時列表也是用起來最方便的一種資料類型。
針對一個變數的操作通常包括創建,刪除,查詢(是否存在),修改(或者部分修改)以及遍歷等。當然,不同類型之間會有一些差別。
列表是什麼?
清單就是有順序的一列資料。
創建一個清單變數
➡ 1.簡單的列表常量可以用 { } 定義
% set lst {1 2 3}
➡ 2.一般建議用 list 命令定義
% set lst [list 1 2 3]
列表可以嵌套:
% set lst2 [list {1 2 3} {4 5 6} {a b c}]
% lindex $lst2 1
=> 4 5 6
% lindex $lst2 1 1
=> 5
用過 python 的同學可能都會比較遺憾 tcl 裡面沒有 range 函數。
別急,我們可以自己發明一個!
(點擊圖片查看高清大圖)
➡ 3. 之前提到過,大部分情況下 HyperMesh 的 API 直接把列表作為命令的輸出給你了,這時你就不必自己創建了,是不是很棒?
下一講我們將介紹怎麼使用 HyperMesh 的 API 得到想要的資料,請持續關注我們唷~
➡ 4. 最後還有一種創建的方法是用 split 命令將字串轉變為清單:
% set string1 “HyperMesh HyperView HyperGraph”
% set newlst [split $string1]
► 同樣的,刪除清單變數也是unset命令。
02
列表的常用操作
➡ 1. 從清單取數據
► llength 命令可以獲取清單裡有多少個元素,如果是多級清單只對第一級進行查詢。
► 取一個清單元素用 lindex
% set lst2 [list {1 2 3} {4 5 6} {a b c}]
% lindex $lst2 1 1
等價於
% lindex [lindex $lst2 1] 1
等價的下面這一句是不是明顯簡潔多了?取一段資料用 lrange,用得不多,就不展開講了。
➡ 2.對列表進行排序
► 一般使用 lsort ,它非常高效而且有很多功能強大的選項,比如 -unique 選項可以篩掉重複元素。其中特別重要的是 -command 選項,它可以實現自訂規則的排序,比如將 HyperMesh 模型中的部分節點或者單元按照到原點的距離進行排序(代碼見下圖)。
► 這個自訂函數接受兩個參數(a,b),返回值1(a>=b), -1(a<b)
上圖第11~12行的目的是獲取使用者框選的節點id號,函數還沒有講到看不懂就先跳過吧。運行情況如下(圖中黃色的節點就是原點位置):
► 此外還有一個專門的函數 lreverse 用於反轉清單:
% lreverse {O K}
=> K O
➡ 3.修改現有列表
► 在清單後面追加資料的速度很快,但是要在開始或中間增加或刪除一個就要慢得多,這也是我們經常在程式中看到 lappend 的原因。
% puts time {lappend lst1 9}
=> 23 microseconds per iteration
% puts time {linsert $lst1 9 10000}
=> 35403 microseconds per iteration
► 上面的 $lst1 是一個用 range 函數創建的100萬個元素的清單,linsert 花費的時間大約是 lappend 的1500倍,而且列表越長,差別越大。
► lset 直接修改清單中的某一個元素,lreplace 和 linsert 用法也是類似的,這些命令平時用到的都不多可以留待大家課後自學。
% set lst {1 2 3}
% lset lst 1 9;把第一個元素改成9,別忘了tcl列表的下標也是從0開始的哦
=> 1 9 3
➡ 4.連接兩個列表
► 方法一:concat
第一層是一個包含6個元素的清單,第二層的每個元素是具有3個元素的清單。
% set lst1 [list {1 2 3} {4 5 6} {7 8 9}]
% set lst2 [list {a b c} {d e f} {g h p}]
% concat $lst1 $lst2
=> {1 2 3} {4 5 6} {7 8 9} {a b c} {d e f} {g h p}
► 方法二:list
% list $lst1 $lst2
=> {{1 2 3} {4 5 6} {7 8 9}} {{a b c} {d e f} {g h p}}
這裡的 $lst1 是 {{1 2 3} {4 5 6} {7 8 9}}, $lst2是{{a b c} {d e f} {g h p}} 。
list 和 concat 的區別:
list 命令就是把成員用一個 { } 包起來,concat 就是把兩個 list 的最外層 { } 先剝掉然後再把所有成員用 { } 包起來。list 命令和 concat 的差別就是 list 命令不給成員脫衣服了,直接用 { } 把他們給包了起來~
► 理解清單結構
第一個輸出中的
{1 2 3} {4 5 6} {7 8 9} {a b c} {d e f} {g h p}
實際上是
{{1 2 3} {4 5 6} {7 8 9} {a b c} {d e f} {g h p}}
第一層是一個6個元素的清單(也就是剝掉一層大括弧),第二層的每個元素又是一個具有三個元素的清單。
第一個輸出中的
{{1 2 3} {4 5 6} {7 8 9}} {{a b c} {d e f} {g h p}}
實際上是:
{ {{1 2 3} {4 5 6} {7 8 9} } { {a b c} {d e f} {g h p} } }
第一層是一個2個元素的清單,分別是 {{1 2 3} {4 5 6} {7 8 9}} 和 {{a b c} {d e f} {g h p}},
第二層每個元素又是一個具有3個元素的清單,分別是 {1 2 3} 和 {4 5 6} 和 {7 8 9} ,
第三層每個元素又是一個具有3個元素的清單,分別是1 和 2 和 3。
理解清單的結構非常關鍵,比如下方這條HyperMesh的命令(4365是某節點的id號)。
% hm_nodevalue 4365
輸出如下:
那我們如果想得到x座標是不是可以使用lindex $coor 0呢?
答案是不行,問題的原因是 HyperMesh 自動把最外層的{}去掉了,如果你看到有一層 { } ,那麼實際上就是有兩層 { },從下面的簡單示例也看得出來。
總而言之,定義的時候你需要加上最外層 { } ,顯示時 HyperMesh 會自動把最外層的 { } 去掉。
再往下看你會發現我們自訂的 flatten 非常樂意為你解除這個煩惱。
03
使用清單對多個變數進行賦值
(這樣至少可以少寫幾行代碼)
% set lst {1 2 3}
lassign $lst x y z
puts “x=$x y=$y z=$z”
=> x=1 y=2 z=3
來個複雜一點的
% set lst123 [list {1 2 3} {4 5 6} {7 8 9}]
% lassign $lst123 v1 v2 v3
% puts “v1=$v1 v2=$v2 v3=$v3”
=> v1=1 2 3 v2=4 5 6 v3=7 8 9
有沒有被忽悠的感覺?
有沒有辦法一下子把123456789分別賦給9個不同的變數呢?
tcl沒有現成的功能,不過我們還是可以自己寫一個函數。這個函數簡單到難以置信。
快接著往下看!
然後就可以執行下面的操作,把一個嵌套列表拍平,也就是把所有 { } 剝掉然後再用 { } 包起來。
% set nestedlist {1 {2 3} {4 {5 6}} {{7 8 9}} {10} {}}
% set flattened [flatten $nestedlist]
=> 1 2 3 4 5 6 7 8 9 10
然後就簡單了:
% lassign $flattened v1 v2 v3 v4 v5 v6 v7 v8 v9
% puts “v1=$v1 v2=$v2 v3=$v3 v4=$v4 v5=$v5 v6=$v6 v7=$v7 v8=$v8 v9=$v9”
當然,你也可以用一個迴圈來達到相同的目的,但是使程式便於理解和易於維護的關鍵就是——不要有太多的迴圈和嵌套,用一條命令代替一個迴圈,完美!
再重複一遍,不要有太多的迴圈和嵌套~
所以接下來我們介紹最後一個列表的命令 lmap 。這個命令在 tcl 8.6 的版本才加入的,而目前的 HyperMesh 對應的 tcl 版本是8.5,所以,很抱歉不支持該命令。
但是,不要捉急,我們依然可以自己發明一個。下面的這段代碼來自wiki,有點抽象,睜大眼睛,爭取看懂它哦。
接下來你就可以進行這樣的命令
set res [lmap x {1 2 3} y {10 20 30} {expr {$x*$y}}]
如果使用迴圈的等價命令為:
這一講到這裡就結束啦~這期是不是含金量超級高呢~ 感謝看到這裡的你,更感謝辛苦付出的教程作者~
留言列表