位元詩人 [Groovy] 程式設計教學:建立和使用串列 (List)

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

陣列和串列的差異

Groovy 的串列在 Java 中其實對應兩種容器:

預設情形下,Groovy 的串列是 ArrayList,但可在宣告串列時指定型態。在 Groovy 中,使用這兩種容器的語法大部分是相同的,所以要知道其內部實作的差異。

連結串列內部以不在記憶體中不連續的節點來相連:

連結串列

由於節點間不連續,新增和移除節點相對容易,只要配置節點的記憶體後將該節點和相鄰節點連結即可。但要以索引 (index) 隨機存取節點的值就不方便,因為要逐一走訪這些節點。

相對來說,陣列內部則是連續配置且緊密排列的節點:

陣列

由於節點間緊密排列,以索引隨機存取的效率相當好。但是,要新增或移除節點就很不方便,要先配置一塊夠大的陣列後,再將陣列元素逐一搬動。

兩種容器各有優缺點,要視當下的情境去選擇。有關陣列和串列的討論和比較可看一些資料結構的資料。

建立串列

以下例子建立串列實字 (list literal),存取其大小和其中的元素:

def list = [1, 2, 3]

assert list.size() == 3
assert list[0] == 1

asset list instanceof java.util.ArrayList

串列以數字為索引 (index),從零開始計算 (zero-based)。

在預設情形下,Groovy 的串列是 ArrayList。但我們可以使用 as 指定串列的型態:

def list = [5, 6, 7, 8] as java.util.LinkedList

// Use list as before.
assert list.size() == 4
assert list[0] == 5

// Now list is a `java.util.LinkedList`
assert list instanceof java.util.LinkedList

這時候串列是 LinkedList

存取串列元素

除了基本的以索引存取元素外,Groovy 提供一些語法糖來存取串列元素,參考下例:

def list = ["a", "b", "c", "d", "e", "f"]

// Retrieve sublist
assert list[0..2] == ["a", "b", "c"]
assert list[0,2,4] == ["a", "c", "e"]

// Replace sublist with another sublist
list[0..2] = ["x", "y", "z"]

assert list == ["x", "y", "z", "d", "e", "f"]

// Remove sublist
list -= list[3..5]
assert list == ["x", "y", "z"]

// Insert sublist
list[1..1] = [0, 1, 2]
assert list == ["x", 0, 1, 2, "z"]

如果覺得這些語法糖過於花俏,不一定要使用,也可以用比較基本的方式來寫。

走訪串列

在 Groovy 中,可透過 for 迴圈或迭代器 (iterator) 走訪元素。下例使用 for 搭配迭代器:

/* Convert a range to a list. */
final list = ("a" .. "e").toList()

for (e in list) {
    println e
}

必要時,也可用傳統的 C 風格 for 迴圈來走訪:

final list = ("a" .. "e").toList()

for (def i = 0; i < list.size(); ++i) {
    println list[i]
}

以下範例則是直接用串列的迭代器來走訪:

[5, 6, 7, 8].each { e ->
    println e
}

Groovy 的語法比較多元,選擇最符合當下語境的即可。

利用高階函式操作串列

以下的例子進行一系列串列的操作:

def n = (1..10).toList()
    .findAll { it % 2 != 0 }      /* Filter */
    .collect { it ** 2 }          /* Map */
    .inject(0) { a, b -> a + b }  /* Reduce */

assert n == 1 ** 2 + 3 ** 2 + 5 ** 2 + 7 ** 2 + 9 ** 2

在這個例子,短短數行程式就進行了數項串列的操作。一開始時,先將 1..10 的 range 轉串列,再用 findAll 留下符合條作的元素,再用 collect 將這些元素逐一轉換,最後用 inject 將這些元素以相加合併。這算是高階函式 (higher-order function) 的應用,一開始看不懂不用太勉強。

關於作者

身為資訊領域碩士,位元詩人 (ByteBard) 認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

位元詩人喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,位元詩人將所學寫成文章,放在這個網站上和大家分享。