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 9 | 2 2 $ 1 |
5 6 7 8 9 5 6 7 | 1 1 1 1 |
函数 $ 提供了一种构建表格的方法,还有很多其他的方式,参看第5章。
如同之前我们看到的列表一样,函数也可以应用于整个表格:
table | 10 * table | table + table |
5 6 7 8 9 10 | 50 60 70 80 90 100 | 10 12 14 16 18 20 |
也可以一个参数是表格,另一个是列表:
table | 0 1 * table |
5 6 7 8 9 10 | 0 0 0 8 9 10 |
2.2 阵列
表格有两个维度:行和列。类似的,列表可以看作只有一个维度。我们还可以构造类似表格但维数大于2的数据。$ 函数的左参数可以是一个包含任意维数的列表。拥有维数的数据对象统称为“阵列”。下面示范一维、二维、三维阵列:
3 $ 1 | 2 3 $ 5 6 7 | 2 2 3 $ 5 6 7 8 |
1 1 1 | 5 6 7 5 6 7 | 5 6 7 8 5 6 7 8 5 6 7 8 |
回忆一下,一元函数 # 给出一个列表的长度:
# 6 7 | # 6 7 8 |
2 | 3 |
L =: 5 6 7 | $ L | T =: 2 3 $ 1 | $ T |
5 6 7 | 3 | 1 1 1 1 1 1 | 2 3 |
L | $ L | # $ L | T | $T | # $ T |
5 6 7 | 3 | 1 | 1 1 1 1 1 1 | 2 3 | 2 |
如果x只是一个数字,那么表达式(# $ x)的值为0:
# $ 17 0这意味着:尽管表格的维数是2,列表的是1,但单个数字没有维数,因为它的维度计数是0。一个维数为0的数据对象被称为标量。我们说“阵列”是有维数的数据对象,标量也是阵列,是维数为0的特殊阵列。
表达式(# $ 17)值为0。由此我们得出:既然标量的维数为0,那么它的维度表($ 17)必然长度为0(空列表)。既然一个长度为2的列表可以通过表达式2 $ 99来构造,那么长度为0的列表就可以通过 0 $ 99(或者 0 $ 任意数字)来构造。
空列表的值显示时没有输出:
2 $ 99 | 0 $ 99 | $ 17 |
99 99 |
需要注意的是,标量(例如17)和只有一个元素的列表(例如 $ 17),或者一个1行1列的表格(比如 1 1 $ 17)是不同的。标量维度为0,列表维度为1,表格维度为2。但是这三个显示时看起来一样:
S =: 17 L =: 1 $ 17 T =: 1 1 $ 17
S | L | T | # $ S | # $ L | # $ T |
17 | 17 | 17 | 0 | 1 | 2 |
一个表格可能只有一列,但维数依然为2。例如:t有3行1列:
t =: 3 1 $ 5 6 7 | $ t | # $ t |
5 6 7 | 3 1 | 2 |
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 |
rear | ranged | rearranged |
“追加”函数连接列表或者单个元素。
x =: 1 2 3 | 0 , x | x , 0 | 0 , 0 | x , x |
1 2 3 | 0 1 2 3 | 1 2 3 0 | 0 0 | 1 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 3 | 3 |
一般来说,# 计算阵列的元素数目,也就是说,它给出的是阵列的第一维度:
T1 | $ T1 | # T1 |
cat dog | 2 3 | 2 |
# T1也是T1维度表的第一个元素。标量,维度为0,被当作单独一个元素:
# 6 1
再回到之前给出的“追加”例子:
T1 | T2 | T1 , T2 |
cat dog | rat pig | cat dog rat pig |
再来看一个展示“元素”这个概念用途的例子,回忆一下函数 +/ 将 + 插入列表的元素之间。
+/ 1 2 3 | 1 + 2 + 3 |
6 | 6 |
现在我们可以说 +/ 其实是将 + 插入阵列的元素之间。下面这个例子中的元素是阵列的行:
T =: 3 2 $ 1 2 3 4 5 6 | +/ T | 1 2 + 3 4 + 5 6 |
1 2 3 4 5 6 | 9 12 | 9 12 |
2.5.3 选择
现在我们看看如何从列表中选取元素。列表的位置从0开始:0,1,2等等 。通过位置选择元素的函数是 { (左大括号,叫做“来自”)。Y =: 'abcd' | 0 { Y | 1 { Y | 3 { Y |
abcd | a | b | d |
位置号也称为索引。{ 函数的左参数可以是单个索引,也可以是索引列表:
Y | 0 { Y | 0 1 { Y | 3 0 1 { Y |
abcd | a | ab | dab |
看看内置函数 i.(i点号)。表达式(i. n)从0开始生成n个连续数字:
i. 4 | i. 6 | 1 + i. 3 |
0 1 2 3 | 0 1 2 3 4 5 | 1 2 3 |
如果x是个列表,那么表达式(i. # x)生成列表x的所有索引:
x =: 'park' | # x | i. # x |
park | 4 | 0 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 -: X | Y =: 1 2 3 4 | X -: Y |
abc | 1 | 1 2 3 4 | 0 |
不管“匹配”函数的参数是什么,它的结果一定是单个0或者1。
注意一下,空列表和空列表是匹配的。比如空的字符串和空的数字列表:
'' -: 0 $ 0 1
因为两个列表有相同的形状,而且相应的元素也相同(两个都没有元素)。
还有另一个函数 =(叫做“相等”)用来测试它的两个参数是否相等。= 逐个比较两个参数的元素,生成一个和参数的形状相同,只包含比较结果的阵列:
Y | Y = Y | Y = 2 |
1 2 3 4 | 1 1 1 1 | 0 1 0 0 |
Y | Y = 1 5 6 4 | Y = 1 5 6 |
1 2 3 4 | 1 0 0 1 | error |
2.6 盒子(Boxes)阵列
2.6.1 拼接(Linking)
有一个内建函数 ;(分号,称为“拼接”)。它将两个参数拼接成一个列表,两个参数可以是不同的种类(这一点和“追加”函数不同)。 例如我们将字符串和数字拼接起来:A =: 'The answer is' ; 42 A +-------------+--+ |The answer is|42| +-------------+--+A是一个长度为2的列表,是一个“盒子”的列表。A的第一个盒子中是字符串'The answer is',第二个盒子中是数字42。盒子在屏幕上显示为一个矩形,盒中的值显示在矩形中。
A | 0 { A |
+-------------+--+ |The answer is|42| +-------------+--+ | +-------------+ |The answer is| +-------------+ |
一个盒子是一个标量,不管里面装的是什么;因此盒子可以像数字一样装入阵列中。所以,A是一个标量列表。
A | $ A | s =: 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 =: 42 | s =: ": n | # s | 'answer is ' , s |
42 | 42 | 2 | answer 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个或者更多维度。一个阵列可以是数字阵列,字符阵列,或者盒子阵列(还有其他高级阵列)。这里就到了第二章结尾了。
说明:
- join和link这两个词都有连接,连合之意,但是在J中指的是两种不同的操作,因此我将join翻译为连接,将link翻译为拼接,貌似比较合适。