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