BOO大全/陣列與串列
陣列與串列
編輯陣列是一種容器物件,它可以有效的將同一型別的物件放在索引過的集合裏。在 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]
RemoveAt 與 Remove 很容易造成誤解﹔前者的引數是一個索引值,而後者的引數則是一個值。這一定要搞清楚,因為使用了錯的方法不會有錯誤發生。
像字串一樣,串列也有 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