BOO入門/容器與轉型

BOO入門 > 容器與轉型 (上一章:流程控制-迴圈 下一章:運算子)


串列

編輯
  定義 串列(List):持有可變數量物件的鏈結串列(容器)。

串列可以很容易地增/刪裏面的元素。

// lists
print([0, 'alpha', 4.5, char('d')])
print List('abcdefghij')
l = List(range(5))
print l
l[2] = 5
print l
l[3] = 'banana'
print l
l.Add(100.1)
print l
l.Remove(1)
print l
for item in l:
    print item

輸出結果

[0, alpha, 4.5, d]
[a, b, c, d, e, f, g, h, i, j]
[0, 1, 2, 3, 4]
[0, 1, 5, 3, 4]
[0, 1, 5, 'banana', 4]
[0, 1, 5, 'banana', 4, 100.1]
[0, 5, 'banana', 4, 100.1]
0
5
'banana'
4
100.1

從例子可以看到,串列很有彈性,也很方便。 定義的方法有兩種:

  1. 使用中括號 []
  2. 將列舉子(IEnumator)或陣列傳入 List 函數中。

譯註

編輯
  1. 例子的第4行 List( range(5) ) 就是將列舉子(IEnumerator)傳入,range() 傳回的是一個 IEnumerator﹔ List( ( 1,2,3) ) 則是將陣列傳入,關於陣列的部份,下節會提到。
  2. 串列無法事先指定所要使用的型別,裏面的每個元素都是 object。因此轉換成陣列時,你需要確定每個元素都是你要的型別才能轉換,否則 Boo 會告知錯誤。
  3. 由於串列提供了 Push()、Pop() 函數,所以你可以把它當 Stack 來使用。
  4. 串列可以使用 Collect() 將符合條件的元素提取出來:
// Collect sample
def IsString( o as object ):
  s = ' '
  if o.GetType()==s.GetType():
    return true
  else:
    return false
l = [ '1','2','3', 1, 2, 3 ]
m = l.Collect( IsString )
print m

List 除了這些以外,還有一些方法可以操作,如:*、AddUnique、Add、ExtendUnique、Extend、Sort、CopyTo、Join、Clear 等等,這可以參考 boo 源碼 src/boo.lang/List.cs。

Slicing

編輯

Slicing(切割)。

Slicing 非常簡單,而且適用於字串、串列和陣列。 語法是這樣的:var[啟始:結束]。啟始與結束指不指定都沒關係,但必須是整數(負整數也行)。 只要取得一個元素的話,用 var[位置] 指定位置,如果是字串,會傳回字元﹔如果是串列,會傳回物件﹔如果是陣列,會傳回陣列裏該元素的型別。 Slicing 以 0 為基底,所以 0 是第一個,1 則是第二個,以此類推。

// slicing
list = List(range(10))
print list
print list[:5] // 取前五個
print list[2:5] // 取第三個元素到第五個元素但不包含第六個元素也就是不包含 list[5]
print list[5:] // 取第六個元素之後的全部
print list[:-2] // 後面數過來的兩個元素都不要取前面八個
print list[-4:-2] // 只取從後面數過來的第四個到從後面數過來的第二個
print list[5] // 只取第六個
print list[-8] // 只取從後面數過來第八個
print '---'
str = 'abcdefghij'
print str
print str[:5] // 取前五個
print str[2:5] // 取第三個元素到第五個元素但不包含第六個元素也就是不包含 list[5]
print str[5:] // 取第六個元素之後的全部
print str[:-2] // 後面數過來的兩個元素都不要取前面八個
print str[-4:-2] // 只取從後面數過來的第四個到從後面數過來的第二個
print str[5] // 只取第六個
print str[-8] // 只取從後面數過來第八個

輸出結果

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4]
[2, 3, 4]
[5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7]
[6, 7]
5
2
---
abcdefghij
abcde
cde
fghij
abcdefgh
gh
f
c

我希望你能了解,Slicing 非常有用,可以讓你用很具可讀性的語法來取得字串、串列或陣列裏的其中一部分。

陣列

編輯
  定義 陣列:陣列是一種固定大小的容器,通常用來存放相同型別的物件。

與串列不同,陣列無法改變大小,只能被切割(Slice)。

陣列可以用三種方法來定義:

  1. 使用小括號:()
    • 如果有 0 個元素,可以這樣定義:(,)
    • 如果有 1 個元素,可以這樣定義:(member,)
    • 如果有 2 個元素,可以這樣定義:(one, two)
  2. 將串列或 IEnumerator 傳入 array 函數。
  3. 將型別與大小傳入 array():array(type, size)
// 陣列
print((0, 'alpha', 4.5, char('d')))
print array('abcdefghij')
l = array(range(5))
print l
l[2] = 5
print l
l[3] = 'banana'

輸出結果

(0, alpha, 4.5, d)
(a, b, c, d, e, f, g, h, i, j)
(0, 1, 2, 3, 4)
(0, 1, 5, 3, 4)
ERROR: Cannot convert 'System.String' to 'System.Int32'.

陣列不像串列,裏面的元素並不一定要是 object。通常會有明確的型別,以 array(range(5)) 這個例子來說,會自動產生一個整數的陣列。

譯註:以上面的例子,(0,'alpha',4.5,char('d')) 則是一個 object 的陣列,裏面的元素型別都是 object。

轉換串列為陣列

編輯

當你建立了一個裏面都是整數的串列,而你想轉換成陣列時,你必須明確指定要轉成什麼型別。

// 串列轉陣列
list = [] 
for i in range(5):
    list.Add(i)
    print list
a = array(int, list)
print a
a[2] += 5
print a
list[2] += 5

輸出結果

[0]
[0, 1]
[0, 1, 2]
[0, 1, 2, 3]
[0, 1, 2, 3, 4]
(0, 1, 2, 3, 4)
(0, 1, 7, 3, 4)
ERROR: Operator '+' cannot be used with a left-hand side of type 'System.Object' and a right-hand side of type 'System.Int32'

list[2]+=5 會出現錯誤,因為 List 裏每個元素的型別都是 object,並非整數,雖然實際上放的是整數。

譯註:如果你想要萃取 List 裏屬於某個型別的元素,可以先使用 Collect:

def IsInteger( o as object ):
  i=0
  if o.GetType()==i.GetType():
    return true
  else:
    return false

list = [ 1, 2, 3, 4, 5, 'a1', 'a2', 'a3', 'a4', 'a5' ]
a = list.Collect( IsInteger ).ToArray( int )  // 或者這樣寫 a = array(int, list.Collect( IsInteger ) )
print a

轉型

編輯
  定義 型別轉換:將變數的資料型別轉換到另一種資料型別以繞過資料型別上的限制。

串列裏僅能存放物件型別,你可以將之轉換為它們真正的型別,然後操作它們。 假定你想轉換為不適合的型別,例如字串轉為整數,Boo 將會告知錯誤。

有兩種方法可以將物件轉換為其他資料型別:

  1. 使用 var as <type> 的語法
  2. 使用 cast(<type>, var) 函數
// casting example
list = List(range(5))
print list
for item in list:
    print cast(int, item) * 5
print '---'
for item as int in list:
    print item * item

輸出結果

0
5
10
15
20
---
0
1
4
9
16
  建議 不要過於頻繁地轉換。如果你要時常地進行轉換,想想是否有更好的方法。

即將出現的新功能:泛型(Generics) 泛型(Generics),將會是 .Net framework 2.0 的一部分,2.0將允許你建立一個以特定型別為基礎的串列。所以,很快地,你將省掉轉換串列項目型別的功夫。

Hashes

編輯
  定義 Hash(雜湊):經過索引的串列,而不只是在固定範圍裏的循序整數,可以透過鍵值快速取得所需的物件。

Hashes 在某些語言裏被稱為"字典",它與串列非常類似,最大的差別在於 Hashes 可以藉着整數或字串的鍵值快速存取物件。

在 Boo 裏,可以用三種方法來定義 Hashes:

  1. 使用大括號 {}
  2. 建立類別並使其繼承 IEnumerator 或 IDictionary。
  3. 使用 Hash 函數 (譯註:原來並沒有提及此項,而只有在下面的範例中說明。)
// hash example
hash = {'a': 1, 'b': 2, 'monkey': 3, 42: 'the answer'}
print hash['a']
print hash[42]
print '---'
for item in hash:
    print item.Key, '=>', item.Value

# 同樣的 hash 也可以從串列裡產生出來,使用 Hash():
ll = [ ('a',1), ('b',2), ('monkey',3), (42, "the answer") ]
hash = Hash(ll)

輸出結果

1
the answer
---
a => 1
b => 2
monkey => 3
42 => the answer

泛型(Generic)

編輯

Boo 在 .Net framework 2.0 推出後,也加入了對泛型的支援,語法是這樣子的(參考自:Boo Generic Syntax):

// 泛型(Generic)
MyType of MyGenericParam
// 有多個泛型(Generic)參數時
MyType[of X,Y,Z]

來看看例子:

import System.Collections.Generic

l = List[of int]()
l.Add( 10 )
l.Add( 20 )
for i in l:
  print i
l.Add( "hello" )  // 這行將會發生錯誤告訴你不可以加入字串型別
// 範例取自原始碼 tests/testcase/net2/generics/generic-array-1.boo
import System.Collections.Generic

arr = array(List[of int], 3)  // 產生三個元素的陣列元素的型別是以整數為基礎的串列
arr[0] = List[of int]()  // 將第一個元素初始化
arr[0].Add( 1 )  // 加入一個整數
print arr[0].Count  // 得到 1

泛型也可以作用於方法(method)上,也就是所謂的泛型方法(Generic method):

// 範例取自原始碼 tests/testcase/net2/generics/generic-method-1.boo
def Method[of T](parameter as T):
	print parameter.GetType()
	print parameter

Method[of int](42)

練習

編輯
  1. 產生一個包含 1000 個 fibonacci 數的串列 (看看你能不能在四行之內完成它!)