#

如果一個字串使用空白字元將資料隔成多個項目,例如:"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有兩個特點:

  1. 只能用在清單變數上。
  2. 只能把新的項目串接在清單的最尾巴。

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

作者已經移除這則留言。

留下您的意見

Theme Design by devolux.org. Converted by Wordpress To Blogger for WP Blogger Themes. Sponsored by iBlogtoBlog
This template is brought to you by : allblogtools.com | Blogger Templates