如果一個字串使用空白字元將資料隔成多個項目,例如:"apple book car duck",像這種格式的字串內容,Tcl稱之為清單(List)。對於清單式的資料Tcl提供了很多方便的操作命令,讓你可以快速的排序、搜尋及取出清單中項目。本篇文章介紹Tcl裡常用的清單命令並示範它們的使用方法。
6.1 建立清單
前面有說到清單是以空白當分隔的字串,所以你還是可以把清單當成是字串,只是Tcl對這種清單式的字串提供了額外的功能,讓你可以很容易的以項目為單位來操作這種資料,而不是以字元為單位。下面的程式展示了三種建立清單的方法,第一種使用雙引號來包夾字串,它總共隔出了3個項目。第2種改採用大括號來包夾字串,然後隔出了4個項目。最後一種使用list命令及命令代換來建立清單。原則上你可以任意的使用上面的三種方法來建立清單,但要注意使用大括號會取消代換的功能,所以如果你有變數需要當成清單的項目,請考慮使用另外兩種方法,不然變數的內容不會被代換。例如下面的$var不會被代換。
如果項目本身也有包含空白,請用雙引號或大括號包夾項目,例如:
最後請特別注意,下面的程式是一個常見的錯誤,第2行你可能預期list1包含了4個項目,但其實包含了5個。因為根據變數代換的原則,第2行的$var被代換後,字串的結果是"pen pencil eraser pencil case",所以list1事實上包含了5個項目。
以下是比較正確的方法:
§ 關於清單
雖然上述的三種方法都可以用來建立清單,而且使用上的感覺也都一樣,但是實際上使用list命令建立的清單,Tcl內部會以不一樣的方法處理。所以如果你可以確定新建立的清單,不會被自己拿來當字串使用,請盡可能使用list命令,因為這樣會比較有效率。 以前我都叫清單為「串列」因為這樣比較符合Tcl內部的資料結構,不過後來覺得就字面上來說「清單」好像也滿貼切的,所以就都用清單了。
6.2 取出清單中的項目
Tcl提供了「索引」及「範圍」兩種方法來取出清單中的項目,「索引」的方法可以用來取出1個項目,而「範圍」的方法則可以取出項目的子集合。6.2.1 取出清單中的單一項目
lindex命令可以用來取出清單中特定的項目,它的語法如下:lindex list ?index?
lindex執行後回傳符合索引位置的項目。其中list是要操作的清單,index是整數的索引位置。如果不指定index的話會回傳所有的項目。程式6-1示範lindex常見的用法。程式的第1行先建立了一個3個項目清單,第2行用lindex命令取出$list中索引值是0的項目。注意哦!!清單的索引值由0開始算,所以取出來的項目是pen。第3行取出索引為2的項目,所以輸出是eraser,第4行說明lindex允許使用end來表示最後一個索引值,第5行則是由最後一個索引值往前算2個,所以是倒數第3個項目。
程式輸出:
pen eraser eraser pen
6.2.2 取出清單的子集合
lrange命令可以用來取出清單中特定範圍的項目群,它的語法如下:lrange list first last
lrange執行後會回傳取出一個範圍的項目群,然後回傳一個新的清單。list是要操作的清單,first是要取出的開始位置,last是要取出的結束位置。如同index命令first及last必需是零以上的整數,而且可以使用end來表示最後一個項目的索引值。如下是它的使用範例:
程式輸出:
apple book cute duck duck eat fruit green apple book cute duck eat fruit green cute duck eat eat fruit green
6.3 把項目加到已存在的清單
如果要把新的項目加入已存在的清單,可以使用linsert及lappend這兩個命令。lappend可以把新的項目附加在清單的尾巴。而linsert可以把新的項目插入清單的任意位置。6.3.1 附加項目至清單的尾巴
lappend可以用來把新的項目附加至清單變數的尾巴,lappend的語法如下:lappend varName ?value value value ...?
lappend把value的值逐一附加在varName的尾巴,最後回傳附加後的結果。varName必需是一個變數的名稱,如果變數不存在lappend會自動建立。如果沒有指定任何value則不附加任何項目。以下是lappend的使用方法:
程式輸出如下:
pen pencil pen pencil eraser {pencil case}
6.3.2 插入項目至清單的任意位置
lappend有兩個特點:- 只能用在清單變數上。
- 只能把新的項目串接在清單的最尾巴。
linsert和lappend不同,它可以把新的項目插入到清單的任意位置,下面是它的語法:
linsert list index element ?element element ...?
linsert執行後把項目element逐一插入清單list索引值為index的位置,然後回傳插入後新的清單。請注意linsert是對清單的值操作而不是對清單變數操作,所以它不會修改到變數的內容。以下是linsert示範用法:
程式輸出如下:
pen pen2 pencil eraser pen pencil eraser pen pen2 pen3 pencil eraser
linsert同樣可以使用end來表示最後一個項目的索引值。特別注意!! 執行linsert並不會修改到清單變數。所以從程式第3行的輸出可以發現,$list1並沒有因為第2行的程式而被改變。第4行把linsert執行的結果重新設定給list1變數,所以第5行的輸出可以看出list1變數被改變了。
6.4 其它常用的清單操作命令
接下來我們要繼續測試幾個常用的清單命令,包含計算項目數量用的llength、排序用的lsort及搜尋用的lsearch。6.4.1 計算清單包含的項目數量
如果你想知道清單到底包含幾個項目可以使用如下的llength命令:程式輸出如下:
3
6.4.2 排序清單中的項目
lsort排序清單中所有的項目,並回傳排序後新的清單,它的語法如下:lsort ?options? list
list是要被排序的清單,options欄位用來調整排序時的方法,以下是options欄位常用的參數:
表6-1 lsort可用的選項
-ascii | 使用Unicode內部的編碼來排序,這是預設的方法。 |
-dictionary | 使用像字典一樣的排序方式。 |
-integer | 把所有清單中的項目轉換為整數排序。 |
-real | 把所有清單中的項目轉換為浮點數排序。 |
-command command | 使用自己提供的命令來比較清單的內容,lsort會不斷執行command命令並把2個項目傳入,如果第1個項目大於第2個command需要回傳1,如果2個項目相同command需要回傳0,如果第1個項目小於第2個項目command要回傳-1。 |
-increasing | 排序的結果使用遞增的方式,這是預設的方法。 |
-decreasing | 排序的結果使用遞減的方式。 |
-index index1 | 如果指定這個參數,每一個項目的內容都會被當成是子清單,然後lsort會對每個子清單索引位置為index1的項目做排序。 |
-nocase | 排序時把英文字母大小寫當作一樣。 |
-unique | 找出清單裡最長且不重複的子清單集合。 |
它的示範如下:
程式輸出如下:
apple book cute duck eat fruit green green fruit eat duck cute book apple a1 a10 a100 1 2 3 4 5 11 1 2 3 4
6.4.3 搜尋清單中的項目
lsearch命令可以用來搜尋清單中符合關鍵字的項目,並回傳第一個找到的索引位置。它的語法如下:lsearch ?options? list pattern
lsearch傳回清單list中第一個符合pattern的項目索引值,如果找不到符合的項目就回傳-1。其中list是目標要搜尋的清單,pattern是要搜尋的關鍵字,options可以用來指定搜尋時的方法。以下是options欄位常用的參數:
表6-2 lsearch可用的選項
-all | 如果指定這個參數lsearch會找出所有符合索引的位置,並把位置以清單的型式回傳。 |
-ascii | 先把清單項目使用Unicode內部的編碼來排序,然後再執行搜尋動作。 |
-decreasing | 先把清單做遞減排序,然後再執行搜尋動作。 |
-dictionary | 先把清單做字典方式排序,然後再執行搜尋動作。 |
-exact | 使用精確比對。 |
-glob | 使用unix glob方式的比對,這是預設值。 |
-increasing | 先把l清單做遞增排序,然後再執行搜尋動作。 |
-inline | 回傳時改用符合的項目內容取代符合的項目索引,如果配合-all,會回傳所有符合的項目清單。 |
-integer | 把所有的項目轉成整數來排序,然後再執行搜尋動作。 |
-not | 反相搜尋,找出第一個不符合pattern的項目。 |
-real | 把所有的項目轉成浮點數型態來排序,然後再執行搜尋動作。 |
-regexp | 使用正規表示式比對。 |
-sorte | 讓lserch在搜尋前跳過排序的動作,並使用exact方式比對。 |
-start | 指定啟始的搜尋位置。 |
lsearch示範使用如下:
程式輸出如下:
2 2 5 b35 a20 a20 c47 0 2 5
本章回顧
如果一個字串使用空白字元將字串隔成多個項目,像這種格式的字串內容,Tcl稱之為清單(List)。對於清單式的資料Tcl提供了很多方便的操作命令,讓你可以快速的排序、搜尋及取出清單中項目。建立清單的方法像這樣:常用來操作清單的命令整理如下:
命令 | 說明 |
lappend varName ?value value value ...? | lappend把value的值逐一附加在varName的尾巴,最後回傳附加後的結果。varName必需是一個變數的名稱。 |
lindex list ?index? | lindex取出list索引位置index的項目。 |
insert list index element ?element element ...? | linsert把項目element逐一插入list索引值為index的位置,然後回傳插入後新的清單。 |
lrange list first last | lrange取出list範圍first到last的項目群,然後回傳新的清單。 |
lsearch ?options? list pattern | lsearch傳回list中第一個符合pattern的項目索引值,如果找不到符合的項目就回傳-1。 |
lsort ?options? list | lsort排序list中所有的項目,並回傳排序後新的清單。 |
按右上方的「#」號切換側邊欄
29 個意見
匿名 | 2011年3月10日 下午5:56
good
匿名 | 2011年4月11日 下午5:16
6.3.2的linsert與法範例 "insert list index element ?element element ...?" 少了一個l 變成insert了?
dai | 2011年4月16日 上午9:03
To匿名的朋友...
改好了謝謝哦!!
匿名 | 2011年5月11日 下午5:19
感謝!! 這些教學資料很有價值 大大實在了不起
dai | 2011年5月12日 上午9:52
哈 ~ 你客氣了!!
dai | 2011年5月15日 下午2:05
哈 ~ 你太客氣了!!
Unknown | 2011年7月16日 下午1:50
請問6.3.1的例子,程式輸出的第二行為何有大括號?
dai | 2011年7月18日 晚上10:15
那是因為list1的第3個項目有空含一個空白,puts命令輸出在console的時候
為了讓你方便看清礎,所以就在項目的前後加上大括號。
匿名 | 2013年1月22日 晚上7:45
請問是否有指令可以將整個list作變換?
例如 list1 {apple banana orange} 整個轉換乘 {蘋果 香蕉 橘子}
謝謝!
kw-sh | 2013年1月23日 上午10:49
to Dai:
請教一下
set var "pencil case"
var不是當作a string item?!
是的話
set list1 "pen pencil eraser $var"
結果應該是下面
pen pencil eraser {pencil case}
dai | 2013年1月23日 上午11:12
To 上上樓,
請試試這個例子:
set l [list apple banana orange]
puts [string map [list apple 蘋果 banana 香蕉 orange 橘子] $l]
dai | 2013年1月23日 上午11:16
hi kw-sh,
"pen pencil eraser $var" 相當於C的 sprinf("pen pencil eraser %s" , var)
如果想要變成 pen pencil eraser {pencil case} 可以這樣處理
set l [list pen pencil eraser $var]
kw-sh | 2013年1月30日 上午10:11
TO Dai:
set var "pencil case"
set list1 "pen pencil eraser $var"
我的疑問是指
list1 0 = pen
list1 1 = pencil
list1 2 = eraser
list1 3 = $var => list1 3 = pencil case ??
是因為TCL的代換關係?
Thank you for your answers.
jimmy | 2013年1月31日 下午2:44
To Dai ,
我有一個問題困惑很久,寫出來都有問題,想問dai 大大,要如何執行才不會有問題,謝謝~
問題:
step1: 同時執行test.exe
step2: 開兩個serial port (Comport)
step3: 利用兩個comport同時進行以下動作
step4: 同時從50跑到70,每次從50開始必須讀5次,判斷只要有1次顯示Fail就是Fail,反之為Pass
ex:Com1: 50-65 Pass , 66~ 70 Fail
Com2: 50-62 Pass ,63 ~70 Fail,
step5: 如取出最後判斷為Pass的值(抓出最佳的值),如Com1:65 ,Com2: 62
dai | 2013年2月1日 下午5:12
hi jimmy,
嗯...我有點看不懂問題,「跑」是指讀取嗎? 每次讀進來的資料格式是?
讀取依序是像下方這樣?
50
Fail
Fail
Pass
Pass
Fail
51
Fail
Pass
Pass
Pass
Pass
....
65
Pass
Pass
Pass
Pass
Pass
jimmy | 2013年2月4日 上午9:32
Hi Dai,對的~讀取依序是像上方那樣
dai | 2013年2月4日 下午1:52
Hi jimmy,
因為你讀進來的資料量不大,所以我會先把所有資料都讀到陣列裡,再來分析比較方便
1. 先用一個陣列 Data 來儲存所有的資料,陣列的內容類似
- Data(50) [list Fail Fail Fail Fail Pass]
- Data(51) [list Pass Pass Pass Pass Pass]
- ....
- Data(N) [list .....]
程式大概如下:
array set Data [list]
.... open comm port ....
set idx ""
set rspCut 0
while {![eof $comm]} {
set data [string trim [gets $comm]]
if {[string is integer $data]} {
set idx $data
set rspCut 0
continue
}
if {$idx == ""} {continue}
lappend Data($idx) $data
incr rspCut
if {$rspCut == 5 && $idx == 70} {break}
}
2. 一但資料存儲成我們想要的格式就好處理了,例如這樣可以找到全是Pass的編號(50~70)
foreach {key} [lsort -increasing [array names Data]] {
set val Data($key)
if {[lsearch -exact $val "Fail" ] == -1} {
puts [format "編號 %s 全是Pass" $key]
}
}
匿名 | 2013年4月3日 下午4:02
To kw-sh
list1 3 = pencil case
又因為 pencil 與 case 中間有空白
所以會被認定為是兩個項目
-----------------------------------
set var "pencil case"
set list1 "pen pencil eraser $var"
我的疑問是指
list1 0 = pen
list1 1 = pencil
list1 2 = eraser
list1 3 = $var => list1 3 = pencil case ??
dai | 2013年4月3日 晚上8:41
嗯~跟據代換的規則
set list1 "pen pencil eraser $var"
會先代換為
set list1 "pen pencil eraser pencil case"
所以 list 3 是pencil 而不是$var的內容
代換的細節可以參考 「04. Tcl - 命令模型」
Unknown | 2014年1月3日 下午3:51
Hi Dai,
先謝謝您提供這麼好的網頁學習Tcl的環境.
不過關於lappend & linsert 我有個疑問.
在您的例子中
lappend list1 pencil 會把pencil append到list1上面, 感覺起來是改變list1的值了.
那如果不小心寫成
lappend $list1 pencil看起來他會回傳pencil 而非pen pencil,
但是puts list1回報出來的值是pen. 也就是list1的內容沒有被改動.
但是linsert
linsert $list1 1 pen2會回傳pen pen2 pencil eraser
但是puts list1的值會得到pen pencil eraser, list1的內容沒有被改動.
如果不小心寫成
linsert list1 1 pen2 會回傳list2 pen2 (中間那個1不知道跑哪去了)
put $list1 一樣會發現list1的值沒有改變.
覺得兩個很相似的command似乎行為上不是很一樣,
這個要怎麼解釋才不會讓自己容易搞錯呢?
Thanks
匿名 | 2014年1月6日 晚上8:58
這真的是一個好問題 ~ 我開始學tcl的時候也時常會弄錯 ~
可以這樣記:
在list的操作命令中,只有lappend及lset這兩個命令是直接對「變數內容」操作,所以這兩個命令會使用「變數名稱」,其它的list命令是對「變數值」操作。
ex.
lappend var 1 2 3
結果是 var 變數的內容為 [list 1 2 3]
set v "level"
lappend $v a b c
因為$v會先被代換為level,所以結果是level的變數值為 [list a b c],$v的值則不改變
linsert $level end d
這是對變數值操作,所以level先被代換為 [list a b c],再由尾巴補上 "d"
linsert list end d
這裡的list被當成是值[list list],再由尾巴補上 d,結果是 [list list d]
Unknown | 2014年1月7日 上午10:00
Hi Dai,
Thanks for your answer. I got it.
阿倫 | 2014年5月8日 下午3:46
Hi, Dai,
請教一下, tcl對於清單的操作中,
是否能從清單移除某特定項目之功能?
匿名 | 2014年5月9日 下午2:58
嗯 ~ Tcl本身建立的命令中,並沒有這樣的功能,
匿名 | 2014年5月9日 下午4:48
set list "red pink green yellow"
proc lremove {list which} {
set no [lsearch $list $which]
if {$no>=0} {set list [lreplace $list $no $no]}
return $list
}
wm title . [lremove $list green]
匿名 | 2016年10月22日 中午12:35
lsearch 的 "讓lserch在搜尋前跳過排序的動作" 應該是 -sorted 喔, 精確的說是 "在假定清單已經排序的狀況下搜尋(可能是二分搜尋)"
匿名 | 2017年7月6日 下午6:52
超有用 感謝 實習差點被電飛 這篇救了我QQ
Unknown | 2017年7月10日 上午9:58
Unknown | 2017年7月10日 上午10:00
留下您的意見