跳至內容

BOO大全/陣列與串列

維基教科書,自由的教學讀本

上一章:字串處理 目錄 下一章:Hashes(雜湊)


陣列與串列

[編輯]

陣列是一種容器物件,它可以有效的將同一型別的物件放在索引過的集合裏。在 Boo 裏,陣列與 CLI 的陣列相容,並且永遠從 0 開始。

# an array literal!
>>> intarr = (1,20,40,2)
(1, 20, 40, 2)
>>> intarr.Length
4
>>> len(intarr) # Python style
4
# indexing
>>> intarr[1]
20
# can modify arrays
>>> intarr[1] = 40
40
>>> intarr
(1, 40, 40, 2)
# can slice arrays
>>> intarr[1:3]
(40, 40)

在 C# 裏要建立陣列的話,必須像這樣:

arr = new int[]{1,20,40,2};

但在 Boo 裏,你不必費力地為陣列指定型別,因為 Boo 會盡可能地依據陣列裏的元素推論出正確型別。如果陣列裏的元素型別不一致,Boo 會調節為他們的父類別。下邊的例子,所有元素都是數值,但因為第三個元素有小數點,因此型別會使用 double。

>>> (1,10,2.3)
(1, 10, 2.3)

下邊的兩個例子也很適合用來說明,第二個是一個整數陣列的陣列。

>>>('one','two','three')
('one', 'two', 'three')
>>>aa = ((1,2),(3,4))
((1, 2), (3, 4))
>>>aa[0][1]
2
>>>print aa.GetType()
System.Int32[][]
>>>print aa[0].GetType()
System.Int32[]

如果型別非常地不一致的話,會使用 object 型別。

>>>bb=(1,'one',(1,2))
(1, 'one', (1, 2))
>>>print bb.GetType()
System.Object[]

當然,也可以明確地告知型別:

>>>dd=(of double:1,2,3,4,5)
(1, 2, 3, 4, 5)
>>>print dd.GetType()
System.Double[]

切割陣列的運作就與字串一樣(切割字串)﹔將索引值指定為 -1 時,表示取得最後一個元素,所以 a[-1]a[a.Length-1] 一樣。

陣列可以直接以 array 內建函數建立,裏面的元素將會被給定恰當的值。

>>>nums = array(double,20)
(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
>>>print nums.GetType()
System.Double[]

拆封值

[編輯]

陣列外的小括號並不是那麼的必要,在要把變數放到一起的時候,會很方便。

>>>x = 1.0
>>>y = 2.0
>>>res = x,y
(1, 2)
>>>print res.GetType()
System.Double[]

Boo 支援變數拆封。指派的左邊可以使用多個變數,如果運算式有順序的話,變數將會被順序指定。

>>>a,b = res
>>>print a,b
1 2
>>>y,x = x,y
>>>print x,y
2 1

在交換變數的時候,這很方便!然而,這在內部處理上,會建立一個暫存的陣列,並不是一個快的方法。

這樣的錯誤訊息很明白地指出,這樣的指派是不行的。

>>> a,b = x
----------^
ERROR: Cannot iterate over expression of type 'System.Double'.

迭代

[編輯]

如果你有個陣列或串列,迭代裏面的元素非常的簡單。傳統的方法像是這樣:

arr = (1,3,20,4,5)

for i in range(0, arr.Length):
	print arr[i]

在 Boo ,你可以這樣:

for i in arr:
	print i

這與 C# 和 VB.NET 裏的 foreach 一樣。

要迭代串列時,最好指定型別,因為 Boo 無法推論出串列裏的元素是什麼型態。(譯註:因為串列的元素型別預設是 object,除非你使用了泛型。)

list = [1,3,20,4,5]

for i as int in list:
	print 2*i

for 迴圈裏,我們可以直接使用陣列﹔如果你只是要作一些簡單的處理的話,這很方便:

for i in (1,3,20,4,5):
	print 2*i

如果你真的要讓迭代速度盡可能的快的話,使用 range 會比較好。Boo 在未來將會針對 for i in range 的情況作最佳化,並讓它能跟 while 迴圈一樣快。(譯註:這可能是過時的資訊。)

內建函數

[編輯]

Boo 提供了一些處理 enumerable 物件的內建函數。

join
[編輯]

在 Python 裏,也有一個同名的函數,當然,也一樣好用﹔它會依照指定的分隔字元將裏面的元素組合出一個字串。

>>>a = (10,20,30)
>>>join(a,',')
'10,20,30'
>>>join(x for x in range(0,10))
'0 1 2 3 4 5 6 7 8 9'
zip
[編輯]

zip 用來把兩個序列萃取為一個單一序列﹔新的序列裏,每個元素將會包含它們對應的元素,而新的序列長度將會與兩者最短的一樣。如果你不明白的話,看看代碼,代碼遠比解釋來的清楚:

>>>a = (10,20,30)
>>>b = (1,2,3)
>>>for i,j in zip(a,b): print i,j
10 1
20 2
30 3
>>>ls = [pair for pair in zip(a,b)]
>>>ls
[(10, 1), (20, 2), (30, 3)]
cat
[編輯]

cat 將會將兩個序列串起來:

>>>join(cat(a,b))
'10 20 30 1 2 3'
enumerate
[編輯]

enumerate傳回的結果與zip非常相似,但第一個值卻是索引值。

>>>for i,s in enumerate(['one','two','three']): print i,s
0 one
1 two
2 three
>>>inf = StreamReader('/net/boo/examples/macros/with/WithMacro.boo')
>>>for i,line in enumerate(inf):
... 	print i,line
... 
0 #region license
1 // Copyright (c) 2003, 2004, Rodrigo B. de Oliveira (rbo@acm.org)
2 // All rights reserved.
3 // 
iterator
[編輯]

iterator實際上並不太會被用到,但實際上你已經隱性地使用了(譯註:這一節提到的內建函數,多數都有使用 iterator。)。它被用來為物件尋找一個適當的迭代子。在 Boo 裏,輸入流可以使用 for 來迭代印出,所以要印出檔案裏的內容,可以這麼寫:

for line in inf: print line

你可以使用 iterator 輕易的將輸入放到陣列裏:

lines = array(string,iterator(inf))
reversed
[編輯]

最後,reversed回傳一個相反順序的序列。

>>> ls = [1,2,3,4]
>>> l2 = [n for n in reversed(ls)]
[4, 3, 2, 1]

串列

[編輯]

串列是經過索引的集合,類似陣列,但卻可以重新調整大小。此外,也沒有特定型別(除非使用了泛型),也比陣列來的慢。彈性往往需要代價,但代價通常是合理的。串列非常地有用,之後如果需要效能的話,你可以轉為陣列。

串列的宣告方法,就是使用 [ ] 中括號,這與 Python 一致。串列物件提供了 Add、Remove 與 Insert 方法,甚至也有類似字串的操作方法,讓你可以進行操作(也因此串列物件是可變動的)。

>>> list = [2,4,5,2]
[2, 4, 5, 2]
>>> list.Add(10)
[2, 4, 5, 2, 10]
>>> list.RemoveAt(3)
[2, 4, 5, 10]
>>> list.Remove(2)
[4, 5, 10]
>>> list.Insert(2,20)
[4, 5, 20, 10]

RemoveAtRemove 很容易造成誤解﹔前者的引數是一個索引值,而後者的引數則是一個值。這一定要搞清楚,因為使用了錯的方法不會有錯誤發生。

像字串一樣,串列也有 IndexOf 方法。Contains 方法與 in 運算子則被用來看串列裏是否有特定的元素。

>>> list.Contains(20)
true
>>> 20 in list
true
>>> list.IndexOf(20)
2

像陣列一樣,你可以將串列串到一起。+=只是加總運算式的快捷寫法。Extend方法並不建立新串列!

>>> list = list + [30,40]
(List) [4, 5, 20, 10, 30, 40, 30, 40]
>>> list += [30,40]
(List) [4, 5, 20, 10, 30, 40]
>>> list.Extend([50,60])
(List) [4, 5, 20, 10, 30, 40, 30, 40, 50,60]

取得串列長度的方法,是利用 Count,而不是 Length!這令人困擾的原因是因為 .NET 裏也是這麼用。你可以和 Python 一樣,使用 len 內建函數,在處理上就能保持一致而不會搞混了。

>>> len(list)
9
>>> list.Length
---------^
ERROR: 'Length' is not a member of 'Boo.Lang.List'.
>>> list.Count
9

你可以將串列轉換為具有特定型別的陣列,但因為型別轉換的問題,這並不一定會成功。Generator運算式提供了便捷的方法讓你進行轉換。(譯註:或參考Boo Primer的Generators章節。)

>>>a = array(int,list)
(4, 5, 20, 10, 30, 40, 30, 40, 50)
>>>array(string,['one','two','three'])
('one', 'two', 'three')
>>> list = [2,4]
[2, 4]
>>>array(string,list)
System.InvalidCastException: At least one element in the source array
could not be cast down to the destination array type.
>>>array(string,x.ToString() for x in list)
('2', '4')

如果你使用過 .NET 其他語言,這裏要提醒你,串列與 .NET 的 ArrayList 很類似,但並不是完全一樣。你當然可以使用 ArrayList,但是這會失去許多好的語法支援。

Generator運算式

[編輯]

要了解 Generator 運算式最好的方法就是多作。這是另外一個從 Python 借來的特性。它很類似串列裏的 for 述句,可以省去寫 for 迴圈的功夫。

ii = []
for i in range(0, 10):
  li.Add(i)

li = [i for i in range(0, 10)]

語法是這樣的:expression for var in expression if condition

>>>list = [2,4]
[2, 4]
>>>ls = [x.ToString() for x in list]
['2', '4']
>>>li = [i for i in range(0,10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>li = [2*i for i in range(0,10)]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
>>>li = [i for i in range(0,10) if i % 2 == 0]
[0, 2, 4, 6, 8]

Generator運算式實際上會產生一個可以迭代的物件(enumerator)。array內建函數可以使用這樣的物件,所以程式也可以寫成這樣。要注意,你不必在Generator運算式的前後加上中括號。

>>>ls=["alice","june","peter"]
['alice', 'june', 'peter']
>>>array(string,n.ToUpper() for n in ls)
('ALICE', 'JUNE', 'PETER')

Generator方法

[編輯]

對於浮點數,並沒有像 range 的函數可用。所以在處理時,得自己使用迴圈來進行小數點的累加:

x = 0.0
while x < 5.0:
  print x
  x += 0.1

打很多字並不是這段代碼的問題,而是它很容易讓人忘了要作變數累加的動作,而導致無窮迴圈。

最簡單的方式是以 generator 方法來實作 frange。其實 generator 方法就是使用 yield 來代替 return。除了我們把迴圈搬到了 frange 以外,下面的代碼作用與上面一樣。

def frange(x1 as double, x2 as double, xd as double):
  x = x1
  while x < x2:
    yield x
    x += xd
	
for x in frange(0,5.0,0.1):
  print x

上一章:字串處理 目錄 下一章:Hashes(雜湊)