2012年10月30日星期二

2 列表(Lists)和表格(Tables)

2 列表(Lists)和表格(Tables)

这是对<Learning J>(by Roger Stokes)的尝试翻译。

Roger Stokes的版权声明如下:
Copyright © Roger Stokes 2012. This material may be freely reproduced, provided that this copyright notice is also reproduced.
感谢作者的无私奉献,请您转载时也注明以上版权,也请同时注明转载地址http://corwindong.blogspot.com,我将不胜感激。

----------------------------------------------------------------------------------

计算需要数据,目前为止我们只看到单个数字或者一列数字。我们也可以把其他东西作为数据,例如表格。列表和表格统称为“阵列”。

2.1 表格

一个2行3列的表格可以通过 $ 函数构建:
   table =: 2 3   $   5 6 7  8 9 10
   table
5 6  7
8 9 10
表达式(x $ y)构造了一个表格。表格的维数通过列表x给出,x中第一个元素是表格的行数,
第二个元素是表格的列数。表格的元素由列表y给出。
 
y中的元素按顺序填充表格的第一行、第二行等等。列表y必须至少包含一个元素。如果y中
的元素数目不够填充整个表格,那么将从头开始重新使用y。
2 4 $ 5 6 7 8 92 2 $ 1
5 6 7 8
9 5 6 7
1 1
1 1

函数 $ 提供了一种构建表格的方法,还有很多其他的方式,参看第5章。

如同之前我们看到的列表一样,函数也可以应用于整个表格:
table10 * tabletable + table
5 6  7
8 9 10
50 60  70
80 90 100
10 12 14
16 18 20


也可以一个参数是表格,另一个是列表:
table0 1 * table
5 6  7
8 9 10
0 0  0
8 9 10
在这个例子中,列表0 1的元素和表格的行进行匹配,0匹配第一行,1匹配第二行。参数之间也存在其他的匹配方式,参见第7章。

2.2 阵列

表格有两个维度:行和列。类似的,列表可以看作只有一个维度。
我们还可以构造类似表格但维数大于2的数据。$ 函数的左参数可以是一个包含任意维数的列表。拥有维数的数据对象统称为“阵列”。下面示范一维、二维、三维阵列:
3 $ 12 3 $ 5 6 72 2 3 $ 5 6 7 8
1 1 15 6 7
5 6 7
5 6 7
8 5 6

7 8 5
6 7 8
上例中的三维阵列有2个平面,每个平面2行,3列,两个平面上下挨着显示。

回忆一下,一元函数 # 给出一个列表的长度:
# 6 7# 6 7 8
23
一元模式的 $ 给出阵列的维度表:
L =: 5 6 7$ LT =: 2 3 $ 1$ T
5 6 731 1 1
1 1 1
2 3
因此,如果x是一个阵列,表达式(# $ x)将得到x维度表的长度,也就是x的维数。列表的维数是1,表格的维数是2,等等。
L$ L# $ LT$T# $ T
5 6 7311 1 1
1 1 1
2 32


如果x只是一个数字,那么表达式(# $ x)的值为0:
   # $ 17
0
这意味着:尽管表格的维数是2,列表的是1,但单个数字没有维数,因为它的维度计数是0。一个维数为0的数据对象被称为标量。我们说“阵列”是有维数的数据对象,标量也是阵列,是维数为0的特殊阵列。

表达式(# $ 17)值为0。由此我们得出:既然标量的维数为0,那么它的维度表($ 17)必然长度为0(空列表)。既然一个长度为2的列表可以通过表达式2 $ 99来构造,那么长度为0的列表就可以通过 0 $ 99(或者 0 $ 任意数字)来构造。
空列表的值显示时没有输出:
2 $ 990 $ 99$ 17
99 99


需要注意的是,标量(例如17)和只有一个元素的列表(例如 $ 17),或者一个1行1列的表格(比如 1 1 $ 17)是不同的。标量维度为0,列表维度为1,表格维度为2。但是这三个显示时看起来一样:
   S =: 17
   L =: 1 $ 17
   T =: 1 1 $ 17
SLT# $ S# $ L# $ T
171717012


一个表格可能只有一列,但维数依然为2。例如:t有3行1列:
t =: 3 1 $ 5 6 7$ t# $ t
5
6
7
3 12


2.3 术语:秩(Rank)和形状(Shape)

我们之前所说的“维数”在J中叫做“秩”,因此单个数字称为0秩阵列,一列数字称为1秩阵列,等等。一个阵列的维度列表称为阵列的“形状”。

我们使用的“列表”和“表格”(元素为数字)在数学中称为“向量”和“矩阵”。有着3个或者更多维度的阵列(也可以说,秩为3或者更高的阵列)称为“报表”。

下面的表格总结了和阵列相关的术语与函数:
+--------+--------+-----------+------+
|        | Example| Shape     | Rank |
+--------+--------+-----------+------+
|        | x      | $ x       | # $ x|
+--------+--------+-----------+------+
| Scalar | 6      | empty list| 0    |
+--------+--------+-----------+------+
| List   | 4 5 6  | 3         | 1    |
+--------+--------+-----------+------+
| Table  |0 1 2   | 2 3       | 2    |
|        |3 4 5   |           |      |
+--------+--------+-----------+------+
| Report |0  1  2 | 2 2 3     | 3    |
|        |3  4  5 |           |      |
|        |        |           |      |
|        |6  7  8 |           |      |
|        |9 10 11 |           |      |
+--------+--------+-----------+------+ 
这张表格实际上是用一小段J代码生成的是一个真实的“表格”,也就是我们之前讨论的那
种表格。它的形状是6 4。然而,它不仅仅是数字表格,它还包含词语,数字列表等等。下来
我们讨论非数字阵列。  
 

2.4 字符阵列

字符包括字母、标点符号、数字字符等等。我们可以构造字符阵列,如同构造数字阵列一样。一列字符输入时使用单引号引起来,但是显示时没有单引号,例如:
   title =: 'My Ten Years in a Quandary'
   title
My Ten Years in a Quandary
一列字符称为字符串。字符串中的单引号需要输入两个连续的单引号来表示:
   'What''s new?'
What's new?


一个空的(长度为0)的字符串通过两个连续的单引号表示,显示时什么也没有:
''# ''
0


2.5 一些阵列函数

现在是时候探究一下操纵阵列的函数了。J有着非常丰富的阵列函数:这里我们仅仅展示一小部分。

2.5.1 连接(Joining)

内置函数 ,(逗号)称为“追加”。它将事物连接起来构成列表。
a =: 'rear'b =: 'ranged'a,b
rearrangedrearranged


“追加”函数连接列表或者单个元素。
x =: 1 2 30 , xx , 00 , 0x , x
1 2 30 1 2 31 2 3 00 01 2 3 1 2 3


“追加”函数也可以将两个表格首尾相连构成更长的表格:
T1=: 2 3 $ 'catdog'T2=: 2 3 $ 'ratpig'T1,T2
cat
dog
rat
pig
cat
dog
rat
pig

关于“追加”函数更多的信息请参考第5章。


2.5.2 元素

数字列表的元素是这些数字,表格的元素是表格的行。3维阵列的元素是它的平面。更一般地,我们说一个阵列的元素就是该阵列第一个维度上顺序出现的东西。阵列也就是阵列元素的列表。

回忆一下内置函数 #(“总数”)用来求列表的长度。
x# x
1 2 33


一般来说,# 计算阵列的元素数目,也就是说,它给出的是阵列的第一维度:
T1$ T1# T1
cat
dog
2 32


# T1也是T1维度表的第一个元素。标量,维度为0,被当作单独一个元素:
   # 6
1


再回到之前给出的“追加”例子:
T1T2T1 , T2
cat
dog
rat
pig
cat
dog
rat
pig
现在我们可以看出(x, y)实质上就是一个顺序包含了x和y元素的列表。

再来看一个展示“元素”这个概念用途的例子,回忆一下函数 +/+ 插入列表的元素之间。
+/ 1 2 31 + 2 + 3
66

现在我们可以说 +/ 其实+ 插入阵列的元素之间。下面这个例子中的元素是阵列的行:
T =: 3 2 $ 1 2 3 4 5 6+/ T1 2 + 3 4 + 5 6
1 2
3 4
5 6
9 129 12


2.5.3 选择

现在我们看看如何从列表中选取元素。列表的位置从0开始:0,1,2等等 。通过位置选择元素的函数是 { (左大括号,叫做“来自”)。
Y =: 'abcd'0 { Y1 { Y3 { Y
abcdabd


位置号也称为索引。{ 函数的左参数可以是单个索引,也可以是索引列表:
Y0 { Y0 1 { Y3 0 1 { Y
abcdaabdab


看看内置函数 i.(i点号)。表达式(i. n)从0开始生成n个连续数字:
i. 4i. 61 + i. 3
0 1 2 30 1 2 3 4 51 2 3


如果x是个列表,那么表达式(i. # x)生成列表x的所有索引:
x =: 'park'# xi. # x
park40 1 2 3


如果 i. 参数是列表,那么将生成相应的阵列:
   i. 2 3
0 1 2
3 4 5


i. 还有二元模式,叫做“求索引”。表达式(x i. y)找到y在x中的索引位置:
   'park' i. 'k'
3
如果y不在x中,那么将返回x的最大索引加1:
   'park' i. 'j'
4
 
更多的索引方法请参考第6章。
 

2.5.4 相等和匹配

如果我们要检查两个阵列是否相等,可以使用内置函数 -:(减号 冒号,叫做“匹配”)。“匹配”函数测试它的两个参数是否有相同的形状,以及相应的元素是否相同。
X =: 'abc'X -: XY =: 1 2 3 4X -: Y
abc11 2 3 40


不管“匹配”函数的参数是什么,它的结果一定是单个0或者1。

注意一下,空列表和空列表是匹配的。比如空的字符串和空的数字列表:
   '' -: 0 $ 0
1
因为两个列表有相同的形状,而且相应的元素也相同(两个都没有元素)。 

还有另一个函数 =(叫做“相等”)用来测试它的两个参数是否相等。= 逐个比较两个参数的元素,生成一个和参数的形状相同,只包含比较结果的阵列:
YY = YY = 2
1 2 3 41 1 1 10 1 0 0
因此,= 的两个参数形状必须相同(或者至少要兼容,比如 Y=2)。否则就会报错:
YY = 1 5 6 4Y = 1 5 6
1 2 3 41 0 0 1error



2.6 盒子(Boxes)阵列

2.6.1 拼接(Linking)

有一个内建函数 ;(分号,称为“拼接”)。它将两个参数拼接成一个列表,两个参数可以是不同的种类(这一点和“追加”函数不同)。 例如我们将字符串和数字拼接起来:
   A =: 'The answer is'  ;  42
   A
+-------------+--+
|The answer is|42|
+-------------+--+
A是一个长度为2的列表,是一个“盒子”的列表。A的第一个盒子中是字符串'The answer is',第二个盒子中是数字42。盒子在屏幕上显示为一个矩形,盒中的值显示在矩形中。
A0 { A
+-------------+--+
|The answer is|42|
+-------------+--+
+-------------+
|The answer is|
+-------------+


一个盒子是一个标量,不管里面装的是什么;因此盒子可以像数字一样装入阵列中。所以,A是一个标量列表。
A$ As =: 1 { A# $ s
+-------------+--+
|The answer is|42|
+-------------+--+
2+--+
|42|
+--+
0

盒子阵列的主要目的是为了能够将多种不同的值装入单个变量中。例如,一个保存购物单(日期,数量,描述)的变量就可以是一个盒子列表:
   P =: 18 12 1998  ;  1.99  ;  'baked beans'
   P
+----------+----+-----------+
|18 12 1998|1.99|baked beans|
+----------+----+-----------+


注意“拼接”和“追加”的不同。“拼接”可以连接不同种类的值,“追加”只能连接同一种类的值。也就是说,“追加”的两个参数要么都是数字阵列,要么都是字符阵列,要么都是盒子阵列,等等。否则就会报错:
'answer is'; 42'answer is' , 42
+---------+--+
|answer is|42|
+---------+--+
error


有时我们想要将字符串和数字结合在一起,比如要表达计算结果和一些描述。我们可以“拼接”描述和数字,就像上面看到的。但是更自然的方式是将数字转换成字符串,然后将两个字符串“连接”起来。
可以使用内置函数“格式化” ":(双引号 冒号)来将数字转换为字符串。下例中的n是一个数字,s是n的格式化结果—— 一个长度为2的字符串:
n =: 42s =: ": n# s'answer is ' , s
42422answer is 42


关于“格式化”的更多信息,请参考第19章。现在让我们回到盒子主题。因为盒子显示时有矩形轮廓,因此它们在屏幕上显示时可以达到表格的效果。
   p =: 4 1 $ 1 2 3 4
   q =: 4 1 $ 3 0 1 1
   
   2 3 $ ' p ' ; ' q ' ; ' p+q ' ;  p ; q ; p+q
+---+---+-----+
| p | q | p+q |
+---+---+-----+
|1  |3  |4    |
|2  |0  |2    |
|3  |1  |4    |
|4  |1  |5    |
+---+---+-----+

2.6.2 装盒和拆盒

内建函数 <(左尖括号,叫做“装盒”),可以用来构造盒子。将 < 应用于待装入的值将得到一个盒子。
   < 'baked beans'
+-----------+
|baked beans|
+-----------+


尽管盒子可以装数字,但是盒子本身并不是数字。想要对盒子中的值进行计算,就需要“拆开”盒子取出值。函数 >(右尖括号)就是“拆盒”。
b =: < 1 2 3> b
+-----+
|1 2 3|
+-----+
1 2 3


可以把 < 看作一个漏斗。流向宽的那头我们得到数据,流向窄的那头我们得到盒子,盒子是标量,没有维数,也可以看作一个点。同样的类比也适用于 >(方向相反)。既然盒子是标量,那么可以通过“追加”函数将盒子串起来形成列表。但是“拼接”函数更方便一些,因为它将“装盒”和串接一起完成。
(< 1 1) , (< 2 2) , (< 3 3)1 1 ; 2 2 ; 3 3
+---+---+---+
|1 1|2 2|3 3|
+---+---+---+
+---+---+---+
|1 1|2 2|3 3|
+---+---+---+

 

 

2.7 总结

总之,每一个数据对象在J中多是一个阵列,有0个,1个或者更多维度。一个阵列可以是数字阵列,字符阵列,或者盒子阵列(还有其他高级阵列)。

这里就到了第二章结尾了。


说明:
  1. join和link这两个词都有连接,连合之意,但是在J中指的是两种不同的操作,因此我将join翻译为连接,将link翻译为拼接,貌似比较合适。




没有评论:

发表评论