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(杂凑)