#

陣列是一種透過索引值來存取資料的資料結構,使用起來有一點像清單,但清單只能使用線性的整數索引值來存取對應的項目,而陣列則可以讓你自行指定索引值。基本上Tcl對陣列索引值的規定非常自由,甚至可以是不連續的整數或是字串。這個章節的內容會介紹Tcl的陣列的使用方法。

10.1 使用set命令建立陣列變數

使用陣列時必需先了解幾個簡單的觀念:

  1. 陣列則是以「元素」做為基本的儲存單位,這就類似清單是以項目為基本的儲存單位一般。
  2. 若你想要存取陣列裡的元素就一定要透元素的「索引值」才辦得到。對於索引值你可以把它想像是元素的指位器,透過這個指位器你才可以把資料安置在陣列裡的某個元素裡或是把資料取出使用。

基本上Tcl可以使用2種方法建立陣列變數,第一種是之前常用的set的命令,第二種是專門用來處理陣列的array命令。現在先讓我們來看看set建立陣列的用法,使用set建立陣列是很簡單的就如同建立一般的變數,只要在變數名稱後面加上一對小括號,這樣Tcl就會把這個變數當成是陣列,例如:



上面的例子建立了一個名為arr的陣列,其中0即是索引值,而apple是正真要儲存在陣列裡的資料,換句話說arr陣列目前只有一個索引值是0且內容是apple的元素。在這個例子裡Tcl偷偷做了下面兩個動作:

  1. 如果陣列變數arr不存在Tcl會自動建立它。
  2. 把apple設定給索引值為0的元素,若元素不存在Tcl會自動建立它。

如果想要取出元素的值,方法也跟變數一樣,只要前面加上$號就可以代換出來了:



程式範例:



上面的例子示範了各種建立元素的可能情況。第2行說明索引值不需要是連續的整數,第3行說明字串也可以作為索引值,第4行說明索引值可以是一串包含空白的字串,第6行說明索引值使用變數代換一樣行得通,最後1行示範使用unset命令刪除arr陣列中索引值是99的元素。

10.2 array命令基礎操作

set命令是以精準的索引值來操作陣列的元素,這樣讓陣列使用起來有點像「只是有加上小括號及一些字串的變數」,事實上真的很容易讓人產生這樣的感覺,而且使用上也感覺不出陣列的特色。如果你想要真正體會陣列的方便之處,就要使用專為陣列設計的array命令,它有下列的特點:

  1. 讓你可以一次建立非常多的元素
  2. 讓你使用搜尋的方式一次取出所有符合關鍵字的元素
  3. 讓你可以配合foreach迴圈逐一取出陣列中的元素

□ 建立陣列

array就像string一樣是一個多功能的命令,同樣也使用第一個參數來決定正真要執行的功能。接下來就讓我們從建立陣列開始,如下是array命令建立陣列的語法:

array set arrayName list

array set以清單list的奇數項目為索引值,偶數項目為元素值來建立名為arrayName的陣列。arrayName必需是一個已經存在的陣列變數或是不存在的變數名稱,但不可以是已存在的一般變數。list必需是項目數量為偶數的清單,其中奇數索引值的項目都會被當成陣列的索引值,而偶數索引值的項目都會被當成元素值。若list為空的清單array命令會建立一個空的陣列。注意!! 這一章會一直出現arrayName這個參數,如果沒有特別說明arrayName都是指要操作的目標陣列,而且它是一個陣列變數的名稱。

程式範例:



程式的第1行建立了一個名稱為arr1的陣列,並立即建立了3個元素,索引值分別是apple、book、monkey,第2行再建立2個元素,索引值分別是cat及table,第3行再加一個索引值是duck的元素,所以最後arr1總共有6個元素。第4行的parray是專門輸出陣列的命令,它可以用人性化的格式輸出陣列裡的元素。注意!! 雖然我們是依apple、book、monkey、cat、table、 duck的順序來建立陣列元素,但事實上Tcl為了效率上的考量並不會照這個順序儲存元素。換句話說使用parray列印陣列內容時,順序可能和你建立陣列元素時不一樣。

程式輸出:

arr1(apple)  = 蘋果
arr1(book)   = 書本
arr1(car)    = 車子
arr1(duck)   = 鴨子
arr1(monkey) = 猴子
arr1(table)  = 桌子

□ 取出陣列元素

接下來我們要學習取出元素的方法,在開始之前有一個必需先知道的前題,就是array命令使用搜尋的方式來找出元素,所以搜尋出來的結果可能是0~N個元素。這樣的做法雖然會比精確指定索引值慢一些,但對於統計或分析陣列中的元素則是非常有利的。如下是array命令取出元素的語法:

array get arrayName ?pattern?

array get以清單的方式回傳arrayName裡索引值符合pattern的元素。若沒有指定pattern預設會回傳整個陣列的內容。pattern使用和string match一樣的比對方法。注意!! array get回傳的清單會同時包含索引值及元素值。

程式範例:



程式的第2行因為沒有指定pattern,所以取出了整個陣列的內容。由程式輸出的第2行可以發現輸出的順序不一定等於建立的順序。第3行的bo*會符合索引值由bo開頭的所有元素。第4~6行示範使用foreach迴圈列印整個陣列的內容,因為array get的回傳值是一個清單,所以清單中奇數的項目(索引值)都會被儲存在key變數,而偶數值(元素值)則會被儲存在val變數裡。

程式輸出:

boat 小船 apple 蘋果 book 書本 monkey 猴子
boat 小船 book 書本
boat : 小船
apple : 蘋果
book : 書本
monkey : 猴子

雖然array get很貼心的幫我們同時取出索引值及元素值,但有時候在元素值非常大的時候,我們會希望先取出索引就好了,而元素的值可以等到需要候再代換就好。使用下面的命令可以做到這一點:

array names arrayName ?mode? ?pattern?

array names以清單的方式回傳arrayName裡符合pattern的索引值。若不指定pattern預設會回傳所有的索引值。mode可以用來決定pattern比對的方法,如下是mode可以使用的值:

-exact使用精確比對。
-glob使用Unix glob-style的比對方法,這是預設值。
-regexp使用正規表示式比對。

程式範例:



程式輸出:

boat : 小船
apple : 蘋果
book : 書本
monkey : 猴子


□ 其它常用的功能

上面的內容介紹了建立及取出陣列元素的方法,接下來讓我們再看看幾個常用的功能:

array exists arrayName

array exists判斷arrayName是否存在,若存在就回傳1,否則回傳0。

array size arrayName

array size回傳arrayName的長度,即陣列裡的元素總數。

array unset arrayName ?pattern?

array unset刪除arrayName中索引值符合pattern的元素。若不指定pattern則所有的元素都會被刪除。pattern使用和string match一樣的比對方法。

程式範例:



程式輸出:

1
0
4
0
0

程式的第6行使用array unset把arr1整個刪除了,所以第7行輸出的結果是0。

10.3 array進階操作

10.2的內容介紹了一些簡單的陣列操作,接下來我們要看幾個跟串繞陣列相關的功能,透過這些功能您可以更有效率的取出陣列的元素。在開始介紹它們之前請先思考一個問題,假設我們有一個陣列arr1,而且這個陣列擁有100000個元素,如果以之前寫過的程式來串繞陣列的話,我們可能會寫出這樣的程式:



現在請考思上面的程式出了什麼問題? 想想看依代換的原則 [array get arr1]會被Tcl代換,代換的結果是100000個元素的索引和值,可想而之的Tcl必需使用一塊可觀的塊記憶體空間去儲存代換出來的內容。如果你不想要讓這種問題出現,array命令提供了另外4個專門用來串繞陣列的功能,它們可以避開因代換而使用大量記憶體的問題。

array startsearch arrayName

array startsearch會初始化arrayName的搜尋,然後回傳一個搜尋用的searchId,這個searchId會記錄目前搜尋的狀態,透過它才可以正確的串繞陣列。

array nextelement arrayName searchId

array nextelement會由arrayName中回傳下一個還沒有被找出的索引值。searchId是array startsearch回傳的值。 如果所有的索引值都已經被找出了,此命令會回傳空字串。注意!! 用空字串來判別搜尋是否已經回傳所有的索引是不保險的方法,因為Tcl的陣列允許你使用空字串當元素的索引值。

array anymore arrayName searchId

array anymore會檢查arrayName中是否還有索引值還沒被找出來。如果回傳1表示還沒找完,如果回傳0表示已經找完了。searchId是array startsearch回傳的值。

array donesearch arrayName searchId

array donesearch釋放搜尋過程所佔用的資源。searchId是array startsearch回傳的值。當不再對陣列搜尋時,記得使用這個命令來釋放搜尋過程中佔用的資源。

程式範例:



程式輸出:

boat : 小船
apple : 蘋果
book : 書本
monkey : 猴子

10.4 字典程式範例

陣列的優點是可以讓你易於維護整群的資料,但很多時候我會把它拿來當對照表來使用。接下來我們將寫一個英/漢字典程式來示範對照表的用法,它只可以查5個單字,這些單字分別是:

  • apple : 蘋果
  • book : 書
  • boat : 小船
  • car : 車子
  • monkey : 猴子

程式執行的畫面如圖10-1及圖10-2,使用者在第一個文字方塊輸入英文然後按下搜尋,第二個文字方塊會顯示對應的中文或是顯示找不到。

圖 10-1 英翻中程式


圖 10-2 不認識的單字




程式的第1行建立了一個全域的陣列,它的功能是用來儲存中英文對照表。

第2~13行總共建立了2個文字標籤、2個文字方塊、2個按鈕,等6個視窗元件,然後第14~15行使用grid命令把前面建立的6個視窗元件依格子狀排放在螢幕上。grid和pack都是用來排放視窗元件的命令,關於pack及grid以後會有專門的章節來說明,目前這不是我們的重點,請先不要在意。

接下來請把重點放在第7~11行,它是按下搜尋按鈕後要執行的功能。首先程式第7行中的info exists命令可以用來判斷變數或是陣列元素是否存在,若存在就回傳1,否則回傳0。透過if命令我們可以知道使用者是不是正確的輸入了中英對照片中的單字,如果是的話就執行第8行的程式,否則就執行第10行。

::varEn變數儲存了使用者輸入的單字,即第一個文字方塊的內容,換句話說按下按鈕後$::varEn總是會被代換為目前第一個文字方塊上的文字。而::varCh儲存了第二個文字方塊的內容,第8行及第10行透過設定這個變數來改變第二個文字方塊上顯示的內容。以上就是這個程式的主要邏輯,並不會太困難吧!!

§ 為什麼總是全域變數

學過程式設計的人都會知道一項鐵則「少用全域變數」, 那為什麼我的程式中總是使用全域變數? 事實上這跟Tcl的namespace機制有關係,而關於這一點會在以後的內容會做交待,目前請先不要在意它。

19 個意見

Anonymous | 2009年8月28日 下午7:56

假設有一全域陣列
set ::arr(0) fir
set ::arr(1) sec
如何用程序帶入全域陣列名稱與索引值去列印陣列呢?
proc abc {val1 val2} {puts "$::$val1($val2)"}

abc arr 0

Dai | 2009年8月30日 下午11:56

你是想這樣做嗎?
array set ::arr [list 1 a 2 b 3 c]
proc abc {val1 val2} {puts [set ::${val1}($val2)]}
abc arr 1
也許下面的方法會好點
array set ::arr [list 1 a 2 b 3 c]
proc abc {val1 val2} {
upvar $val1 brr
puts $brr($val2)
}
abc ::arr 1

其實~ ::arr是一個全域的陣列所以我們可以在abc程序裡直接操作,只要傳索引值進去就好了

Anonymous | 2009年8月31日 上午10:25

謝謝你的回應。我知道了...

當要把陣列傳到proc時要用到upvar這個指令。
你的網站真的對想學tcl的人有很大幫助,謝囉~~

匿名 | 2011年2月15日 下午1:15

Dear Dai:
超棒的網站~
錯字提醒
□ 建立陣列 下面
array set以清單list的奇數項目為索引值,偶數項目為元素值來建立名為arryName的陣列。
應為arrayName才對

匿名 | 2011年2月15日 下午1:20

Dear Dai:
錯字提醒:
程式的第1行建立了一個名稱為arr1的陣列,並立即建立了3個元素,索引值分別是apple、book、momkey,
monkey

dai | 2011年2月19日 下午2:42

To 匿名的朋友,

已經修正了,謝謝哦!!

AllenC | 2012年3月23日 下午4:26

Dai大 您好,

想請問陣列可以依元素的值來排序嗎?

因為最近練習寫程式的時候遇到一個需求,
比如說 人<->身高 這種一對一的資料

然後需輸出一群人由高到矮的人名 跟身高

我很直覺的認為應該用陣列, 可是後來發覺陣列好像無法排序
頂多可以藉由排序索引值可達到目的...

問題是索引值只能是唯一,
如果我拿身高做索引值就會遺漏掉某些資料...

請問有沒有什麼方法呢?

dai | 2012年3月24日 下午9:56

嗯 ~ 本身tcl是沒有對元素值排序的功能,這需要自己寫程式處理!!

如果是我會用list來儲存人的資訊或是把陣列轉為list

轉完後再用lsort的命令來排序

你可以參考手冊上lsort -index 選項的使用方法

AllenC | 2012年3月27日 上午9:37

Dai大你好, 用清單+index的方式成功實現目標, 來回報一下
以下是與清單有關的code

lappend group "$get_name $get_height"
# group 的內容會是下列形式 {{allen 170} {reggie 173} {ins 176} {tony 168}...etc}

set sort_group [lsort -increasing -real -index 1 $group]
# 在此設定一個新的清單 sort_group儲存排序後的清單
# -index 1 針對每一個項目的第2個元素做排列,
# 以{allen 170} 為例,就是以170作為排序的值.

dai | 2012年3月27日 上午10:40

很棒!! 跟我心裡的答案很接近 呵~

再小修改一下就更好了,像這樣:

lappend group [list $get_name $get_height]

猜猜看 ~ 上面一行和用雙引號夾起來差在哪?

AllenC | 2012年3月28日 上午9:30

Dai大, 我來猜猜看...

又去偷翻了幾個中括號跟雙引號的定義之後,
我覺得差別應該在這裡,

用雙引號起來的, 被視做一個有空白分隔的"字串".
而使用中括號的加進去的, 本身是一個有兩個元素的清單!

dai | 2012年3月28日 上午10:06

你說的對~
用中括號建立的變數,Tcl內部會以list即清單的資料結構儲存
這樣在排序上會比較有效率 內部不用先做字串轉清單的動作
另一個不同點在這:

我的名字是 Yuan Liang,身高是179所以變數的內容可以如下..

set get_name "Yuan Liang"
set get_height 179

你試試看下面的兩行差在哪??

lappend group "$get_name $get_height"

lappend group [list $get_name $get_height]

AllenC | 2012年3月29日 上午9:52

Dai大你好,

lappend group "$get_name $get_height"
group {Yuan Liang 179}

lappend group [list $get_name $get_height]
group {{Yuan Liang} 179}

所以名字自成了一個元素, 將來在取用時會比較方便,
是這樣子嗎的?

dai | 2012年3月29日 下午10:58

嗯 ~

比較正確的說法應該是 - 可以避免出錯 !!

像 {Yuan Liang 179} 已經不是我們期待的清單結構了

AllenC | 2012年3月30日 下午1:55

了解! 感謝Dai大不辭辛勞的回覆我的疑問!
再繼續努力學習...

匿名 | 2012年7月19日 下午2:47

Dai大
感謝你的教學文件
讓我收穫不少~
以下發現一個小錯誤
--
004 set arr("This is ok.") "True"
(第4行說明索引值可以是一串包含空白的字串)
--
陣列的索引值可以是包含空白的字串沒錯
不過不能這樣寫
不然 Tcl 會以為 set 指令有四個參數而出錯
還請大大檢查一下

Jynee | 2013年4月20日 上午11:30

您好,

請問
set names() "hello"
是在設定什麼呢?

找了好久都無解.

dai | 2013年4月22日 下午1:04

這個意義是 :

建立一個名為 names 的陣列 (假設names陣列不存在的話)

然後把key為空字串的元素值設定為 "hello"

Yang Peng | 2016年11月30日 上午11:23

Error in startup script: wrong # args: should be "set varName ?newValue?"
while executing
"set arrSample("This is OK.") "True""
(file "hello.tcl" line 140)

这行确实有问题,应该是小括弧没有被优先执行。
=====
小错无碍大成,给作者手动点赞!

留下您的意見

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