本文摘要(由AI生成):
本文主要介绍了TCL语言中的数组、字典和字符串。数组是一种键-值对,可以使用任意字符串作为索引,后台使用哈希算法进行处理。字典也是键-值对,与数组类似,但使用不同的语法。字符串是TCL语言中的一种数据类型,具有强大的功能,包括字符串操作、正则表达式等。
与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
↑ 本例中如果空格不转义将导致语法错误。
从结果可以看出转义后的空格以及引号也变成了键的有效组成部分!
当变量的名字里面还包含变量的时候,简单变量需要特殊处理才能使用,点击按钮可以复习一下变量那一讲~
二次开发之 Tcl 中的变量
但如果使用数组就很方便啦,我们可以直接使用 $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
这一讲就到这里啦,感谢看到这里的你们。