0 回顾
字符串的篇幅较长,内容较多。需要明确的是,Tcl字符串的内容远远不止于此,我只是摘选了一些常见的内容。接下来我们一起聊一聊Tcl的列表。在这里我们将讨论列表的创建,修改,获取,搜索,排序以及拼接等内容。文章字数太多不利于同学们阅读,为此列表的内容我计划分成两篇文章介绍。
1 创建
列表简单来说就是多个元素的集合,该集合中的元素可以是数字,字符串,空字符串,甚至是列表(即,列表中嵌套列表)。并且列表中的元素可以重复出现。创建列表最简单的方式如下:
set list_x {John Anne Mary Jim}
# 创建列表list_x,列表中包含了4个元素,分别是John, Anne, Mary, Jim。
# 每个元素之间用空格分隔,并且采用大括号将这4个元素组成列表。
# 因此,大括号并不是列表的一部分。列表的实际内容仅仅是其中的4个元素。
通常我们采用list,concat,lrepeat命令创建列表。为了便于理解,每个命令我将分成三个步骤去介绍每一个命令,分别是命令的格式,基础示例,示例解析和特别说明。
-
list命令格式
list element0 element1 sub_list0 sub_list1
-
基础示例
set list1 [list a b {c d}] # 创建列表list1, 列表中包含了a b c d等元素。 # 结果: a b {c d} set list1 [list a b a {} {c d}] # 列表list1包含了a b a {} {c d}
-
解析
{c d}为子列表。换句话说,命令list是将所有的参数拼接成一个列表,不会做任何的处理。第2个例子展示了利用list命令创建一个包含空白字符的子列表。另外需要特别说明的是:列表中a元素重复出现,这是符合要求的。后文会介绍如何移除列表中的重复元素。
[Note] 推荐采用list的方式创建列表。 -
concat命令格式:
concat element0 element1 sub_list0 sub_list1
-
基础示例:
set new_list [concat a b {} {c d} {e f } {g {h i}}] # 列表sub_list1中包含了两个元素:e f; f后面跟了一个空格。 # 结果: a b c d e f g {h i}
-
解析
concat的工作原理:把各个列表或者字符串拼接成一个单一的列表,并且移除空列表和列表中的空元素。
{}:concat解析这个列表时发现没有元素,不会返回任何元素到列表new_list中。
{c d}:concat解析这个列表中的元素a和元素b,并且其作为列表new_list中的两个元素。
{e f }:concat解析出这个列表中的元素e和元素f,元素f后面跟随的空元素将被移除。最后,将元素e和元素f作为列表new_list的两个元素。
{g {h i}}: concat解析出这个列表中包含了元素g和子列表{h i},并将解析好的元素和子列表作为列表new_list的元素。
总结:concat会将字符或者不包含子列表的列表做简单的拼接,并且子列表保留。
[Note]个人不喜欢用这种方式创建列表,虽说它可以提供移除空字符的一些便利,但是工作原理有些复杂,不如list来的直接,干脆。
有时候我们需要创建一个列表,列表中的元素是周期出现的,若采用list去做创建,则代码往往是冗余的。如:
set new_lsit [list {a b c} {a b c} {a b c} {a b c} {a b c} {a b c} {a b c}]
# 若要创建1000个,那岂不是要写麻了🤣
显然,采用list的方式创建时非常冗余的。Tcl提供了一种便捷的方式快速的创建此种类型的列表,即lrepeat。
- lrepeat的命令格式:
lrepeat loop_num element0 element1 sub_list0 sub_list1
- 基础示例:
set new_list0 [lrepeat 3 a b c] # 结果: 列表new_list0: {a b c a b c a b c}; # a b c元素重复出现3此 set new_list1 [lrepeat 3 a {b c}] # 结果:列表new_list0: {a {b c} a {b c} a {b c}}
- 解析
lrepeat可以理解成list的升级版,保持子列表和元素不变,仅仅是元素或者子列表重复n次。对于元素是周期性出现的列表而言,强烈推荐这种用法。
2 修改
列表创建好后,我们当然需要去修改它。毕竟极少数情况下,列表是保持不变的。通常在脚本中我们需要修改列表。和修改列表相关的命令有:lrange、linsert、lreplace、lset、lappend。其中,linsert,lset,lappend是修改列表最常见的方法。
-
lrange命令格式
lrange list0 begin_idx end_idx
lrange命令返回列表list0中指定范围的元素。起始边界为begin_idx;终止边界为end_idx。 -
基础示例
# 创建列表x_list set x_list [list a b {c d} e] # 获取index 1 ~ index 3的元素 lrange $x_list 1 3 # 结果:b {c d} e # 获取index 0 ~ index 1的元素 lrange $x_list 0 1 # 结果:a b
-
解析
为了便于理解上述给出的基础示例。我们给出index和list mapping table。如下表所示:Index 0 1 2 3 Sublist/Element a b {c d} e 表中,index表示list中各个元素或者子子列表对应的索引。如元素a对应的index为0;子列表{c d}对应的index为2。
第一个示例的目的是获取index 1 ~ index 3的元素。即,表格index 1对应元素b, index 2对应子列表{c d},index 3对应元素e。第二个示例也可以从index和list mapping表中轻易地获得基础示例中的结果。 -
linsert命令格式
linsert list0 location element0 element1 element2
linsert将 element0 element1 element2插入list0的指定locatiaon,实现列表的扩展。基于此,咱们可以通过linsert的方式push元素到列表,可以push到开头,也可以push到结尾。类似的用法在ASIC和FPAG的flow中是非常常见的。 -
基础示例
# 创建列表x_list set x_list [list a b {c d} e] # 在列表x_list的第2个元素后插入元素X Y Z # 或者理解成在x_list index 2前 index1 后插入元素X Y Z linsert $x_list 2 X Y Z # 结果:a b X Y Z{c d}
-
解析
如果location是index0,则表示在x_list的开头插入,实现push_front的功能;若location是end,则在x_list的结尾插入,实现push_back的功能;若location是index1,则在index0和index1之间插入。为了便于理解,给出插入前和插入后的表格对比。
插入前的列表:Index 0 1 2 3 Sublist/Element a b {c d} e 插入后的结果:
Index 0 1 2 3 4 5 6 Sublist/Element a b X Y Z {c d} e -
lreplace命令格式
lreplace list0 index_begin index_end element0 sub_list0
将list0的index_begin和index_end之前的列表替换成element0和sub_list0。即,index_begin和index_end之间的元素被删除。若要删除index_begin和index_end之间的元素,那么 element0 sub_list0为空即可。也就是说不要有 element0 sub_list0。 -
基础示例
# 创建列表list0 set list0 [list a b {c d} e}] # 删除index 3对应的元素 lreplace $list0 3 3 # 结果: a b {c d} # 元素b和子列表{c d}被替换成子列表{W X} 元素Y 元素Z lreplace $list0 1 2 {W X} Y Z # 结果a {W X} Y Z e
-
解析
index_begin和index_en一定是成对出现的。若指向某一个元素,则index_begin=index_end;否则index_begin表示起始位置,index_end表示终止位置。
替换前的列表:Index 0 1 2 3 Sublist/Element a b {c d} e 示例1替换后的列表:
Index 0 1 2 Sublist/Element a b {c d} 示例2替换后的列表
Index 0 1 2 3 4 Sublist/Element a {W X} Y Z e 值得注意的是,lreplace在创建新列表的时候必须赋值原始列表中的元素;若列表的非常的大,那么复制原始列表将会严重的影响机器的性能。因此,对于大列表而言,不建议使用lreplace。为了解决这个问题,Tcl提供了lset命令。如下所述。
-
lset命令格式
单个元素的替换:lset list0 index0 element0
将list0中的index0对应的元素修改成elements。
嵌套元素的替换:lset list0 index0 index1 element0
将list0中index0对应子列表中的index1对应的元素进行替换
其中,list0一定是列表名,不是变量名。即,写成list0即可,一定不要写成$list0。 -
基础示例
# 创建列表person set person [list {Jane Doe} 32 female] # 将元素32修改成30 lset person 1 30 # 结果:{Jane Doe} 30 female # 将子列表中的元素Done修改成Johnson lset person 0 1 johnson # 结果:{Jane Johnson} 30 female
-
解析
无论是子列表中的元素,还是列表本身的元素,采用lset每次只能修改其中的一个。换句话说lset修改的最小粒度是元素。另外,值得注意的是,lset不能创建新的列表,只能用于修改已经存在的列表。如果列表中的元素不存在,那么Tcl解析器将返回error。 -
lappend命令格式
lappend list_name element0 sub_list0
将element0和sub_list0添加到名字为list_name的列表的末尾,类似push_end,并且依次可以push多个元素或者子列表。 -
基础示例
# 创建列表x_list set x_list [list a b {c d} e] # 将元素XX和子列表{YY ZZ}push到列表x_list的末尾 lappend x_list XX {YY ZZ} # 结果:a b {c d} e XX {YY ZZ}
-
解析
lappend后跟的第一个参数不是列表本身,而是列表的名字。它和lset都是传递列表名,而不是列表本身。另外,lappend同样使用于大型列表。