J语言
J语言
J语言,是一种阵列编程语言,由肯尼斯·艾佛森和于1990年代初发明。J语言是APL语言的一种方言,延续了APL鲜明的简洁性,它在数学和统计学程式设计上十分高效,特别是在需要进行矩阵运算的场合。
J语言最初起步于肯尼斯·艾佛森在1987年发表的《APL字典》,它实现了其中至关重要的秩的概念。J语言提供隐式定义机制包括秩、钩子、叉子和多种,并介入了作为头等对象的动名词,用以建立控制结构,它常被作为隐式编程的典范之一。
简介.
Ken Iverson(右)和Roger Hui在1996年的照片
J语言的运算符,承袭APL传统,没有优先级并且最右先行,codice_1按照codice_2来运算。以历史上APL使用的典型符号为例,符号codice_3被用来指示折叠函数codice_4,所以codice_5等价于codice_6;在APL中,除法被表示为数学除号,它将减号和冒号一起在EBCDIC和ASCII二者的纸质文本终端上;J语言使用codice_7表示除法,是对除号的一种近似或提示。
为了避免APL使用特殊的字符而遇到的问题,J语言只需基本的ASCII字符集,但使用点号codice_8和冒号codice_9作为“屈折”。点号和冒号除了前导着空白字符的情况之外,都与紧前字符形成类似双字符组的短字。多数“基础”或“原始”的J单字,都充当数学符号,通过点号或冒号来扩展这些可用基本字符的含义。在其他语言中经常是成对的很多字符,比如codice_10,在J语言中被当作单独的字,或者在有屈折的时候,作为多字符字的单字符字根。
J语言不再支持从1968年的APL\360就有的codice_11形式的方括号索引,转而支持叫做“来自”(from)的索引机制,它起源自Kenneth E. Iverson于1978年在《算子和函数》中提出的,依据基数解码定义,并用符号codice_12表示的索引。
J语言承袭IBM APL\360采用了平坦阵列模型,不支持由NARS(嵌套阵列研究系统)于1981年介入,并被IBM APL2所采纳的嵌套阵列模型;J语言增加了Kenneth E. Iverson于1978年在《算子和函数》中提出的盒装数据类型,它由SHARP APL于1981年介入,并于1983年在研究报告《理性化APL》中,列入与APL2相比较的“限定子集”(RS)而著重强调。
J语言支持AVX2指令集进行SIMD运算。为了包装用面向对象编程语言开发的API和框架,J语言提供了层级命名空间机制,这里所有名字都存在于特定语境(locale)中,可以避免软件包之间的名字冲突,并能有效的用作基于类的面向对象编程的框架。
J语言解释器默认装载标准库。通过包管理器,还可以安装各种插件,如果是在管理员权限下安装的J语言解释器,则安装插件也需要同样的管理员权限。J语言拥有常规调试机制,还有叫做Dissect的可视调试器。除了科学计算和统计分析,它还被用于关系数据库管理系统如Jd、极限编程和网络性能分析。
2011年3月,J语言采用了GNU通用公共许可证版本3,从而成为自由和开源软件,人们还可以在Jsoftware的商业许可证下利用源代码。
文档与词类.
J语言的文档包括在官网的NuVoc中,在其中将主要的字罗列为“J”,并使用颜色标示出它们分别的词类。早期的文档还有入门和字典。在J语言中的字,被识别为名词、动词、定语(副词和连词)、系词、标点、控制字。一个程序或例程,如果接受数据作为输入并产生数据作为输出,则被称为“动词”,与之相对,数据参数被称为“名词”。
动词有两种形式:只有右侧一个参数的一元(monad)形式,和有左右两侧参数的二元(dyad)形式,例如在codice_13中减号是一元动词,而在codice_14中减号是二元动词。J语言预定义了很丰富的动词,它们都自动的作用于多种数据类型之上。用户定义的程序可以自行命名,并用在任何允许使用原始动词的地方。无论原始动词还是派生动词,它们的一元定义与二元定义,在很大程度上是独立的。
起步示例.
J语言可以写出非常精简的程序,特别是存在重度的对符号的函数重载,以至于一些编程者将它称为难以阅读的只写语言。在计算机的终端上执行codice_15,即可进入J语言的REPL解释器界面。
Hello, World!
J语言的“Hello, World!”程序:
'Hello, world!'
Hello, world!
这个Hello World的实现反映了J语言的传统用法,就是把程序录入到J解释器会话中,显示出表达式结果。还可以准备J脚本来作为独立程序来执行,比如在Linux系统上,可以编辑如下内容的一个文本文件,并命名为比如codice_16:
echo 'Hello, world!'
exit "
注意第一行的codice_17必须顶头,这里的codice_18和codice_19,是与Unix shell中同名命令功能类似的动词。然后在终端界面中执行这个文件:
$ ijconsole test01.ijs
Hello, world!
$ chmod +x test01.ijs # 另一种执行方式,授予这个文件可执行权限
$ ./test01.ijs
Hello, world!
平均.
在J语言中函数一般称为动词,例如定义一个叫做codice_20的动词,计算一序列数的平均:
avg=: +/ % #
avg 1 2 3 4
2.5
一元动词codice_21“计数”(tally),总计阵列中项目的总个数。动词codice_22“加”(plus)和副词codice_3“插入”(insert),派生出的动词codice_24,合计这个阵列的项目的总和。二元动词codice_7“除”(divide)将这个总和除以这个总个数。而用户定义的动词codice_20,用到了由连串(strand)的三个动词(codice_24、codice_7和 codice_21)构成的一个“叉子”(fork)。叉子codice_30↔codice_31,这里的codice_32、codice_33和codice_34指示动词,而codice_35指示一个名词。
使用codice_20的一些例子:
]a=: ?. 20 $100 NB. 产生100以内20个随机整数的一个向量
94 56 8 6 85 48 66 96 76 59 33 72 63 1 89 52 17 20 9 65
avg a
50.75
4 avg\ a NB. 周期大小为4的移动平均
41 38.75 36.75 51.25 73.75 71.5 74.25 66 60 56.75 42.25 56.25 51.25 39.75 44.5 24.5 27.75
]b=: ?. 4 5 $50 NB. 产生50以内20个随机整数的一个矩阵
44 6 8 6 35
48 16 46 26 9
33 22 13 1 39
2 17 20 9 15
avg b
31.75 15.25 21.75 10.5 24.5
avg"1 b NB. 应用avg于m的每个秩为1的子阵列
19.8 29 21.6 12.6
一元副词codice_3“插入”(insert),接受位于它左侧的一个运算元,并产生将这个动词应用于其参数的每个项目之间的一个动词。就是说,codice_24是一个动词,定义为应用codice_22于给它的参数的各个项目之间。计算移动平均用到的二元副词codice_40“中缀”(infix),将作为数据参数的列表划分成一系列的指定大小的连续项目的子列表,将所修饰动词应用于其上,并将这些结果形成一个列表。
一元动词codice_41“相同”(same),恒等于给它的单一右参数,常像这样用来在赋值之后显示变量的内容。一元动词codice_42“掷骰/固定种子”(roll/fixed seed),不同于一元动词codice_43“掷骰”(roll),在生成数据参数项目所指定大小范围内的随机数之时,采用固定的种子。这里确定对矩阵按行还是按列进行平均,用到了连词codice_44“秩”(rank),它在后面的定语章节和单独条目中论述。
查找与排序.
二元动词codice_45“出现索引”(index of),和二元动词codice_46“最后出现索引”(index of last),在任何大小的阵列内查找匹配者,并返回它的位置索引,如果未找到匹配者,则返回这个阵列的大小。例如:
a=: 3 1 4 1 5 9
a i. 1 2 NB. 找到1和2的第一次出现的索引
1 6
a i: 1 2 NB. 找到1和2的最后一次出现的索引
3 6
在J语言中,排序可以按APL传统的两步骤方式,使用一元动词codice_47“升序索引”(grade up)或codice_48“降序索引”(grade down),和用二元副词codice_49“被动”修饰的二元动词codice_50“出自”(from),二者连串(strand)形成的一个“钩子”来完成。一元钩子codice_51↔codice_52;副词codice_49“反身·被动”,其一元定义为codice_54↔codice_55,二元定义为codice_56↔codice_57。J语言还提供专用的二元动词codice_47“上升排序”(sort up)或codice_48“下降排序”(sort down)。下面是用例:
a=: 2 0 4 7 15 9 8 0 4 9 18 8 1 18
/: a NB. 产生参数阵列的升序索引
1 7 12 0 2 8 3 6 11 5 9 4 10 13
({~ /:) a NB. 从参数阵列中按升序索引选取出各个项目
0 0 1 2 4 4 7 8 8 9 9 15 18 18
/:~ a
0 0 1 2 4 4 7 8 8 9 9 15 18 18
(a - 10) /: a
_10 _10 _9 _8 _6 _6 _3 _2 _2 _1 _1 5 8 8
CSV插件.
安装用来支持CSV文件的插件:
load 'pacman' NB. 加载包管理器
'install' jpkg 'tables/csv' NB. 安装CSV文件插件
'showinstalled' jpkg " NB. 查看已经安装插件
一个CSV文件简单用例:
load 'tables/csv' NB. 加载CSV插件
a=: i. 2 3
a writecsv jpath '~/test01.csv' NB. 将一个阵列写入一个CSV文件
12
]b=: readcsv jpath '~/test01.csv' NB. 从一个CSV文件读入一个盒子阵列
│0│1│2│
│3│4│5│
]c=: makenum b NB. 尽可能的将盒子阵列转换成数值阵列
0 1 2
3 4 5
下面演示使用J语言编写在管道中的过滤器,例如,在具有隐式编程机制Unix管道的Linux系统中,建立如下内容的文本文件,并命名为比如codice_60:
load 'tables/csv'
stdout makecsv 10 + makenum fixcsv stdin "
exit "
然后在终端界面中执行如下命令行:
$ cat test01.csv | ijconsole filter01.ijs
10,11,12
13,14,15
数据类型.
J语言支持三种简单类型:
其中数值有很多变种。J语言提供的唯一搜集(collection)类型,是任意维度的阵列。多数算法可以使用这些阵列来简洁的表达。
数值.
J语言的数值类型之一是“位”。位有两个值:codice_61和codice_62。位还可以形成列表,例如codice_63,是8个位的列表。在语法上,J分析器将位当作一个字。空格字符被识别为字形成字符,它处在属于其他数值字的字符之间。
J语言支持任意长度的列表。J语言进一步的在这些位列表之上,支持所有常见二元运算,比如动词codice_64“与”(and)、codice_65“或”(or)、codice_66“非”(not)、codice_67“反转·旋转”(reverse·rotate)、codice_68“移位”(shift)等。J语言还支持位的二维、三维等阵列。上面的运算同样运行在这些阵列之上。
其他数值类型包括整数(比如3、42)、浮点数(3.14、8.8e22)、复数(0j1、2.5j3e88)、扩展精度整数(12345678901234567890x)和(扩展精度)有理分数(1r2、3r4)。同位一样,它们可以形成列表或任意维度的阵列。同位的情况一样,运算可以在一个阵列的所有数值之上。下面例子展示π的前50位,超出了IEEE 754双精度浮点数的53位二进制尾数能精确表示的最大范围,这就要用到J语言的扩展精度整数:
0j15 ": o. 1 NB. π在双精度浮点数下精确值的位数
3.141592653589793
。
简要词汇表.
下面的表格简要列出了常用词汇。如果含义中用了间隔号( · )分隔,通常前者是只有一个右侧参数的一元含义,后者是左右两侧都有参数的二元含义。列出的对应APL符号,是Dyalog等现代APL所采用的符号。
定语.
J语言的能力,很大程度上来自它的“定语”(modifier:修饰词),这个范畴包括“副词”和“连词”:这些符号接受名词和动词作为运算元(operand),并以指定方式应用这些运算元。定语都可以应用于任何动词,包括用户写的动词,用户可以写自己的定语。
J语言的二元动词有右结合性,或称为尽量长右作用域,即它有尽可能多的右参数。定语即算子有左结合性,或称为尽量长左作用域,即它有尽可能多的左运算元。如果表达式中存在定语即算子,首先应用这些定语,然后应用其生成的动词。
副词.
一元副词codice_3“插入”(insert),副词codice_40“前缀·中缀”(prefix·infix),副词codice_94“后缀·外缀”(suffix·outfix),和连词codice_95“剪切”(cut),指定参数的诸个规则或不规则子集,并在其上执行运算。在J语言实现中,codice_96、极小值codice_97和极大值codice_98运算,是典型的会对其进行速度优化的特殊组合。
副词codice_49“反身·被动”(reflex·passive),其一元形式codice_54↔codice_55,将提供给动词的右参数重复放置在左参数位置上;二元形式codice_56↔codice_57,将提供给动词的左右两个参数对换位置。
秩.
名词的秩(rank)是排布其原子所依据的轴的数目,即它的形状中项目的数目。动词的秩是它能够在其上直接运算的右(和左)名词参数的最高秩,典型的表示为三个原子的一个列表:codice_104。对副词和连词标示的秩,是所形成的动词的秩。
秩在特定动词和特定名词的上下文下,将名词的诸维,划分成前缀诸维的序列,称为框架(frame);和后缀诸维的序列,称为单元(cell)。秩采用连词codice_44“秩”来操纵,对应于APL符号codice_106,它有三种形式:codice_107“指定秩”(assign),codice_108“常量动词”(constant),codice_109和codice_110“复制秩”(copy),这里的codice_111、codice_112表示动词运算元,而codice_113、codice_114表示名词运算元。正数动词秩,指示单元诸维的数目,负数动词秩,指示框架诸维的数目,codice_115指示整体。
二元动词的左右参数的框架经常是匹配的,就是说二者有相同的形状,从而保证了它们有相同数目的单元。如果左右参数的框架不匹配,有三种可以运算的情况:
复合连词.
连词codice_117“在于”(at)、codice_118“顶上”(atop)、codice_119“并列”(appose)、codice_120“合成”(compose),是四种复合(composition)。J语言支持叫作“钩子”(hook)和“叉子”(fork)的隐形连词,二种隐形连词和四种复合连词,规定了如何将参数或将所饰动词应用于参数的结果,提供给所饰动词来进行应用的规则。下表列出它们的定义:
在上面表格中,codice_121,codice_122,codice_123,这里的副词codice_124给出动词codice_112的三个秩。在应用四种复合连词形成新动词的表达式中,codice_117和codice_119,要对第一步运算的中间结果,按所在子表达式的秩或整个表达式的秩codice_115进行汇集(assembly),并在有需要的情况下进行框架填充,然后在这个汇集成的框架内进行第二步运算;而codice_118和codice_120,直接在第一步运算的框架内,对中间结果进行第二步运算;在整个表达式求值结束时,最终结果在有需要的情况下要进行整体填充。
在中,一元codice_111直接在二元codice_112所划分的框架内进行自己的运算。在中,codice_117将二元codice_112的运算结果,汇集成codice_135所指定的框架。在,二元codice_111直接在一元codice_112所划分的两个框架内进行自己的运算。在中,codice_119将一元codice_112的两个运算结果,汇集成codice_140所指定的框架。
下面例子展示四种复合的中间结果的单元差异:
] a =: >:i. 2 3
1 2 3
4 5 6
] b =: 0.1*>:i. 2
0.1 0.2
a ( 0.1 0.2
│1.1 2.2│
1 2 + &.> 0.1 0.2
│1.1│2.2│
在J语言中,孤立的动词序列叫做“列车”(train), codice_156意味着codice_157,codice_158意味着codice_159;以此类推,动词列车的一般模式codice_160,依赖于动词的数目,在偶数时形式为codice_161,最外层是个钩子;而在奇数时形式为codice_162,最外层是个叉子;二者的内部都是可能有多层的嵌套的叉子。
叉子、codice_117再加上codice_164和codice_41,可以将很多常用复合写为列车。在惯用法codice_166中,并不实际执行的隐式动词codice_167“遮帽”(cap),屏蔽了叉子的左分支,形成了等价于codice_168的特殊化叉子。
与现代APL如Dyalog等对照,复合连词codice_117对应于同秩连词共享APL符号codice_106的“顶上”(atop),而codice_119对应于codice_172“上方”(over),共享codice_120符号的“粘上”,对应于APL中的“绑上”(bind),APL的“绑上”和“边上”(beside)共享符号codice_174,“边上”的一元形式同于codice_106,而二元形式同于钩子。在《APL字典》中,codice_118对应其codice_172,而codice_120列入秩连词codice_106之内,钩子对应于符号codice_180“枝条”(withe),codice_148对应于符号codice_153。
用例.
下面的简单例子是计算欧几里得范数,和生成数位与维度坐标一致的整数:
]d=: (1 1),(1 1 1),:(3 4)
1 1 0
1 1 1
3 4 0
norm=: %: @ (+/) @: *:"1 NB. 它可加圆括号为((%: @ (+/)) @: *:)"1
norm d
1.41421 1.73205 5
coor=: 10&#. @ > @ { @: (: @ i."0)
coor 2 3 4
111 112 113 114
121 122 123 124
131 132 133 134
211 212 213 214
221 222 223 224
231 232 233 234
在codice_183中采用的圆括号包围,使得codice_117处在整个表达式的最外层,从而形成了两步骤运算;右侧的第一步是圆括号包围的子表达式,它的完全加圆括号(fully-parenthesized)形式为:codice_185;左侧的第二步是codice_50与复合到其上诸运算构成的子表达式,它的完全加圆括号形式为:codice_187。一元动词codice_50“目录汇编”(catalogue),应用在盒装列表的列表之上,是接受可变数目的变长参数的典型的动词。
下面通过对圆括号包围的子表达式加以变化,辨析秩连词和复合连词的特性。这里的动词codice_41,划分开了给连词或副词的名词运算元,和给所生成的动词的名词参数:
]a=: : @ i."0 ] 2 3 4 NB. 运算式可加圆括号为((:) @ i.)"0
│1 2│1 2 3│1 2 3 4│
a -: : @ i.)"0 ] 2 3 4 NB. 运算式可加圆括号为(: @ i.))"0
1
a -: : @ i."0) 2 3 4 NB. 运算式可加圆括号为: @ i.)"0)
1
: @ i."0 ] 2 3 4 NB. 运算式可加圆括号为((:) @ i.)"0
│1│2│ │ │
│1│2│3│ │
│1│2│3│4│
: @ i."0) 2 3 4 NB. 运算式可加圆括号为: @ i.)"0)
│1 2 0 0│
│1 2 3 0│
│1 2 3 4│
下面的例子展示并联电阻电路计算:formula_1,它可以如下这样表达:
Rtotal=: +/ &.: %
Rtotal 10 5 15
2.72727
动词幂.
连词codice_190动词幂”(power of verb),有两种形式:
对于固定幂codice_193,如果codice_194缺席,codice_195在以codice_35为运算对象的迭代中,将动词codice_111应用codice_114次;如果codice_194存在,codice_200在以codice_35为运算对象的迭代中,将动词codice_202应用codice_114次。如果codice_114是阵列,则按每个原子项目都执行一次动词幂,结果的框架为这个阵列的形状;如果codice_114是取值为codice_61或codice_62的变量,则形成布尔值条件执行;如果codice_114是codice_209,则进行codice_111的逆运算;如果codice_114是codice_115,则意味着“收敛”(converge),即反复应用codice_111直到结果不再变化。例如:
(1+*&3) ^:0 1 ] 1 2 3 4
1 2 3 4
4 7 10 13
' ' , ^:4 'abc'
abc
(1+*&3) ^:_1 ] 4 7 10 13
1 2 3 4
(-:@(]+%)) ^:_ &1 ] 0.25 3 25 NB. 以巴比伦方法即一种牛顿法特例来计算平方根
0.5 1.73205 5
对于动态幂codice_214,如果codice_194缺席,codice_216在以codice_35为运算对象的迭代中,将动词codice_111应用codice_219次;如果codice_194存在,codice_221在以codice_35为运算对象的迭代中,将动词codice_202应用codice_224次。动词幂可以形成动态条件执行,这里的动词codice_112必须总是产生布尔值结果,应用动词codice_111当且仅当codice_112返回codice_62。进而codice_229可以形成while循环构造,只要codice_112返回codice_62,就反复的执行codice_111,直到codice_112返回codice_61,或者codice_111将它的参数无变化的返回。例如:
-&2 ^:(>&4) "0 ] 1 3 6 12 NB. 对大于阈值4的列表项目减去2
1 3 4 10
4 (0.25&*@[+(1-0.25)&*@]) ^: @./@(Collatz^:(>&1)^:_"0@>:@?@$&1e6) 1000 NB. 取1000个在1e6内的随机数测试考拉兹猜想
1
定义.
J语言支持用户进行显式定义,和codice_244形式的。下面以五种复合作为显式定义的例子:
at=: conjunction define
u (v y)
u (x v y)
atop=: conjunction def '(u at v)"v'
beside=: conjunction define
u (v y)
x u (v y)
appose=: conjunction define
u (v y)
(v x) u (v y)
compose=: conjunction def '(u appose v)"(0{v b.0)'
下面以矩阵乘法作为直接定义的例子,并顺带演示秩连词的用法:
a=: ? @ $&1000 @: >: 4?10
b=: |: a
p=: a (+/ . *) b
matmul=:
(a (+/ matmul *) b) -: p
1
inner=:
(a (+/ inner * 0&|:) b) -: p
1
outer=:
(a (+/ outer *) b) -: p
1
revmul=:
(a (+/ revmul * 0&|:) b) -: p
1
APL传统上的将内积解释为,J语言的矩阵乘法要求写为,不隐含的为左运算元codice_32附加一元codice_3。转置也是有较大的运算,不同的有不同的参照局部性。
在隐式定义中,递归定义可以不通过名字引用自身,转而使用动词codice_265“自引用”。例如递归的计算斐波那契数列:
fibonacci=: 1: ` ($:@-&2 + $:@&2) "0 : [:
fibonacci >:i.9
1 1 2 3 5 8 13 21 34
codice_266到codice_267是常量动词。动词codice_167“遮帽”,用在连词codice_269“一元与二元定义”所应用的动词位置上,即充任了一元动词codice_111或二元动词codice_112,可分别在如下两种情况下报错:定义的是二元动词,却不适当的被用作一元动词;或定义的是一元动词,却不适当被用作二元动词。
在显式定义和直接定义中,提供了类似其他过程语言的控制结构。这里列出的是每个范畴内的代表性控制字:
索引.
J语言的索引机制采用二元codice_50“来自”(from)动词来完成,它的秩为codice_273,它有两种形式,分别为左参数为索引阵列的主轴索引,和左参数为二层或一层盒装结构的逐轴索引,二者分别对应APL中,方括号内为单个轴的主轴索引选取,和方括号内为codice_84分隔的多个轴的逐轴索引选取。
主轴索引.
索引阵列的每个项目指定对主轴的项目单元的一个选取,将它们的结果再汇合为一个阵列。负值索引表示从末尾往前记数。在APL中,这种形式的索引被称为“来自”(from),也叫做“选取”(select)或幽默地称为“明智”(sane)索引,最早出现在SAX(SHARP APL for UNIX)对其codice_118“来自”索引的扩展中,部份现代APL,将它表示为符号codice_276。例如:
i. 2 3
0 1 2
3 4 5
1 0 { i. 2 3
3 4 5
0 1 2
1 0 {"1 i. 2 3
1 0
4 3
(i. 2 3) { 'abcdefg'
abc
def
_1 { 'abcdefg'
g
逐轴索引.
逐轴选取可以形成子阵列,在现代APL中,它被表示为codice_12,故而也被称为“扁方块”(squad:squish quad)索引(indexing)或就叫做“索引”(index)函数。装在二层盒装结构中的,是对应诸轴的一层盒装子结构的列表,其中每个一层盒装子结构内都是数值列表,它对应在此轴内一个或多个项目选择。默认全选使用名词codice_278“么点”(ace)指示,它是盒装空列表codice_279。在尾部的连续多个默认全选不需要写出。例如:
i. 3 4
0 1 2 3
4 5 6 7
8 9 10 11
(«1 2) { i. 3 4
4 5 6 7
8 9 10 11
(: 4?10
n=: # @ $ a
p=: ?~ n
q=: ?~ n
(/:/:p) -: p
1
((/:p) { p) -: i. n
1
(/: p{q) -: (/:q) { /:p
1
(p |: q|:a) -: (p{q) |: a
1
k=: ? $ p |: a
(( }:@,@:(.&a:@) t
│ │ │ │ │1│ │ │ │ │
│ │ │ │1│ │1│ │ │ │
│ │ │1│ │2│ │1│ │ │
│ │1│ │3│ │3│ │1│ │
│1│ │4│ │6│ │4│ │1│
二项式系数也可以写成隐式定义形式:
pascal=: ((0&, + ,&0)@] ^:[ 1:)"0 : [:
简易图表.
下面是基于二元副词codice_3“形成表格”(table),制作条形图和散点图的简易图表例子:
barChartH=: {&('.',u:16b2584) @ (>/ i.@(>./)) : [:
barChartH 3 1 4 1 5 9 2 6 5
▄...
▄...
▄▄▄▄▄▄▄▄▄
▄▄▄▄▄▄...
barChartV=: {&('.',u:16b258c) @ (./)) : [:
barChartV 3 1 4 1 5 9 2 6 5
...▌...
...▌.▌.
..▌.▌▌.▌▌
▌.▌.▌▌▌▌▌
scatterChart=: {&('.',u:16b2588) @ (=/~ >:@|.@i.@(>./)) : [:
scatterChart 3 1 4 1 5 9 2 6 5
...█.
..█...
...█..
这里用到的Unicode方块元素字符也出现在IBM PC代码页437之中。
这里的codice_290可以使用二元副词codice_291“修改”(amend)来实现:
scatterChart=: {&('.',u:16b2588) @ ((>./ , #) (#~ ./) . i.@#)) : [:
对角线选取.
APL的二元转置,在多个轴映射到结果中的一个轴的情况下,将其依次安排到前导位置上并进行对角线选取。下面基于一元动词codice_292“真值位置索引”,定义进行对角线选取的动词codice_293,它的左参数是布尔值列表,其中的真值codice_62指示与其位置对应的轴,要依次安排在前导位置上并进行对角线选取,其他的假值codice_61所对应的轴相对位置不变。
diag=:
这里局部定义了codice_296,它的左右参数同于给codice_293的参数,它生成二元转置需要的置换向量,这是由要安排到前导位置上的那些轴的位置索引,和余下其他轴的位置索引串接而成。这里的codice_298以右参数的诸轴数目选取左参数,用来在左参数的真值和假值的总数小于右参数的诸轴数目之时,对左参数填充上假值。
接着局部定义了codice_299,它的左参数是给codice_293的布尔值列表中真值codice_62的个数,右参数是要对其指定数目的前导轴进行对角线选取的阵列,它生成对角线选取所需要的一层盒装选取列表。这里的codice_302选取出要进行对角线选取的前导诸轴的最小长度,用codice_45形成这个长度的整数数列,再用codice_304形成其长度为左参数的全为codice_62的列表,通过codice_306在二者之上形成表格矩阵。
最后的表达式先进行指定的二元转置,再对其结果进行相应的对角线选取。下面是简单用例:
>:i. 3 5
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
1 1 diag >:i. 3 5
1 7 13
i. 2 3 2
0 1
2 3
4 5
6 7
8 9
10 11
1 1 diag i. 2 3 2
0 1
8 9
0 2 1 |: i. 2 3 2
0 2 4
1 3 5
6 8 10
7 9 11
1 0 1 diag i. 2 3 2
0 2 4
7 9 11
快速排序.
J语言提供的排序机制基于了稳定排序算法,下面的例子代码是快速排序的直接定义:
cmp=: * @ -
quicksort=:
这里定义一个动词codice_307,它通过逐个做两个数的差并取其符号,得到取值为codice_209、codice_61或codice_62的平衡三进制值。codice_307将作为左运算元传递给副词codice_312。
在codice_312的定义中,向局部变量codice_296赋值的表达式,第一步随机选择支点(pivot)运算,首先计算codice_315,生成在数据总个数范围内的随机数,接着在其上计算codice_316,选择出在随机数指定的位置上的支点值;它的第二步运算,将运算元codice_111,应用到其左参数的数据列表,和右参数的支点值二者之上。
随后是串接分治运算结果,首先将平衡三进制值列表,分别与codice_61做逐项的三分法比较,得到三个布尔值列表;然后以这种列表中的codice_61和codice_62作为件数,复制出数据列表的符合这个条件一个新的子列表,其中两个作为参数传递给递归调用进行排序。
下面的快速排序实现,展示了隐式编程,即将函数复合在一起,而不显式的引用任何变量,不提及要应用于其上的形式参数。这里将前面代码中向局部变量codice_296赋值时所求值的表达式改为隐式定义,进而以作为钩子的第一步运算的方式,代入引用这个变量的表达式之中,并且采用钩子的参数复制机制消隐了形式参数codice_35:
cmp=: * @ -
quicksort=:
cmp quicksort 2 0 4 7 15 9 8 0 4 9 18 8 1 18
0 0 1 2 4 4 7 8 8 9 9 15 18 18
提供给连词codice_190的左侧运算元,外层是个一元钩子,它将提供给它的单一右数据参数,重复放置在它的左数据参数位置上。这个外层一元钩子的第一步运算,是生成平衡三进制值列表的嵌套的二层一元钩子codice_324;而外层一元钩子的第二步运算,将生成的三个子列表串接起来。生成三个子列表的表达式,以数据列表是作为左参数,以平衡三进制值列表作为右参数;这里的三个二元钩子首先生成布尔值列表,接着进行对换了左右参数位置的二元复制运算,最后它们中有两个通过自引用codice_265进行了递归调用。
这个定义中的codice_265是在这个副词的私有语境内调用的动词,所以不像前面直接定义那样需要加上运算元codice_111以副词形式来调用。这里没有对字符串长度小于等于codice_62的情况进行处理,这是因为迭代运算在条件不满足时返回初始值,也就是返回这个字符串本身。将codice_307的表达式,代入定义中的左运算元codice_111,就能定义出动词,同时也不再需要外在的采用直接定义的形式。
将一元钩子替代为左分支为codice_41的叉子,形成的动词列车更具可读性:
quicksort=:
下面的例子定义基于一元codice_47进行字符串比较的codice_307:
cmp=: -/ @ (-.@-: * /:@;)
'alpha' cmp 'beta'
_1
'beta' cmp 'alpha'
1
'beta' cmp 'beta'
0
t=: ' the heart has its reasons that the reason does not know'
]words=: quicksort words
│does│has│heart│its│know│not│reason│reasons│that│the│the│
在这个codice_307定义中,codice_335先将两个字符串参数进行盒装串接,然后一元codice_47给出二者的升序索引,二者之间为升序或相同时为codice_337,而二者为降序时为codice_338;至此是升序还是相同仍需区分,codice_339判断两参数是否为“不相同”,不相同时为codice_62,而相同时为codice_61。这里的副词codice_342“自有区间”,使用字符串的第一个字符作为分隔符,对字符串进行划分并去除分隔符,然后应用所修饰的动词于这些子字符串之上。
全排列.
下面的例子给出codice_114个项目的所有置换的有次序的矩阵。首先定义名词codice_344,它是置换长度为codice_345的全排列矩阵:
]p3=: (i.@! A. i.) 3
0 1 2
0 2 1
1 0 2
1 2 0
2 0 1
2 1 0
这里用到了动词:codice_346“易位词”(anagram),codice_347的左参数codice_194,指定了长度为codice_349的所有置换中给特定一个置换的编号,其对应的置换向量在组合数学中被称为逆序向量,据此编号置换codice_35的项目。这里求全排列的数目,用到了一元动词codice_351“阶乘”。接着在codice_344的基础上,实现置换长度为codice_353的全排列矩阵:
=/~ @ i. ] 4 NB. 生成4×4单位矩阵
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
\:"1 @ (=/~) @ i. ] 4 NB. 生成第1列为0 1 2 3,每行后3个元素升序的4×4“奇妙”矩阵
0 1 2 3
1 0 2 3
2 0 1 3
3 0 1 2
0&. @: +&1 p3 NB. 对3元素全排列的每个元素增1,并在每个排列头部添0
0 1 2 3
0 1 3 2
0 2 1 3
0 2 3 1
0 3 1 2
0 3 2 1
perm0=: (0&.@:+&1 p3) {"(_ 1) \:"1@(=/~)@i.
&0) : [:
(perm 4) -: p4
1
$ perm 0
1 0
$ perm 1
1 1
invertVec=: I.@(, -:"1 perm@#) : ({ perm)
1 _1 invertVec 4
0 1 3 2
3 2 1 0
invertVec 3 2 1 0
23
最后将递归形式改为迭代形式,可采用连词codice_364“单结果正向折叠”(fold single forward),使用它需要事先安装插件codice_365。这个连词所形成的动词,从左至右遍历右参数列表,将其项目逐个作为动词运算元所见到的左参数;它的左参数是迭代对象的初始值,动词运算元所见到的右参数是迭代对象:
load 'dev/fold/foldr'
perm=: (1 0$0)&(]F..((,~ !)@[ $ ,@(0&.@:+&1@] {"(_ 1) \:"1@(=/~)@i.@[)))@:(>:@i.) : [:
如果不采用连词codice_364,可以基于一元副词codice_3“插入”,自行实现正向折叠算子:
foldl=:
perm=: (1 0$0)foldl((,~ !)@[ $ ,@(0&.@:+&1@] {"(_ 1) \:"1@(=/~)@i.@[))@:(>:@i.) : [:
生命游戏.
在J语言中,提供了二元副词codice_368“子阵列”(subarrays),它是codice_95“剪切”(cut)的三种形式之一,也被称为密铺(tessellate)或镶嵌(tile)。codice_370应用动词codice_111于由codice_194指定的codice_35的有相同形状的每个正规镶嵌之上。codice_374与之类似,但不丢弃结果中不完整的镶嵌。
现代APL所使用的二元算子codice_375“”(stencil),在边缘的处理上不同于J语言的镶嵌,它要求镶嵌子阵列每个轴的中心,在长度为奇数时是codice_35的元素,在长度为偶数时在其元素之间,并且用填充(fill)元素填满超出的部份,它的缺省移动步长是codice_62。下面在codice_378的基础上,利于二元动词codice_379“采取”(take)的填充特性,定义一个codice_380实现:
stencil=:
这个定义只提供一个右名词运算元codice_114,它是镶嵌子阵列的规定矩阵,不提供同每个子阵列对应的诸轴填充数目作为左名词运算,APLcodice_375算子提供它,意图在需要时籍此移除填充。这里的镶嵌规定矩阵定义,其第1行是子阵列的每轴长度,第2行是每轴的移动步长,这个行次序与codice_368的规定相反。这里的codice_282是给codice_378的镶嵌规定矩阵,codice_386是每轴在头部和尾部的填充数量,codice_296是正值控制尾部填充,codice_299是负值控制头部填充。在下面的简单用例中,数值阵列的填充元素是codice_61:
]d=: 4 4 $ >: i. 9
1 2 3 4
5 6 7 8
9 1 2 3
4 5 6 7
(i.8) &.> ( (@]) key x
│M│1│
│i│4│
│s│4│
│p│2│
下面的例子通过键分组来找到一个单词列表中的易位词:
]a=: a
│apst│apst│aest│aest│apst│aest│apst│aest│aest│aest│arst│aest│aest│
(]key~ /:~&.>) a
│┌────┬────┬────┬────┐│┌────┬────┬────┬────┬────┬────┬────┬────┐│┌────┐│
││pats│spat│taps│past│││teas│sate│etas│seat│eats│tase│east│seta│││star││
└─────────────────────┴─────────────────────────────────────────┴──────┘
随机数.
在J语言中,提供了二元动词codice_292“区间索引”,它左参数codice_194必须是有次序的,从而定义了codice_424个区间,除了最后一个之外的每个区间,都含有并结束于codice_194的一个项目,而最后一个结束于正无穷,第一个开始于负无穷;它应用于右参数codice_35,给出codice_35所位于的区间的索引。
下面的例子产生符合指定离散概率分布的随机数列表,这里定义了动词codice_428,它依据左参数codice_194给出的正实数向量中的这些权重,从codice_430中选取出由右参数codice_35指定个数的随机数列表:
ran=: [: : ((I.~ (+/\ % +/))~ ?@$&0)
wt=: 7 5 6 4 7 2 0.4
# t=: wt ran 1e6
1000000
10 {. t
0 1 1 5 0 1 3 4 4 0
] r=: wt (+/@(=/ i.@#)~ % #@]) t NB. 实测的出现比率
0.222618 0.159083 0.19152 0.127394 0.222795 0.06378 0.01281
] p=: (% +/) wt NB. 期望的出现概率
0.22293 0.159236 0.191083 0.127389 0.22293 0.0636943 0.0127389
0j6 ": r - p
_0.000312 _0.000153 0.000437 0.000005 _0.000135 0.000086 0.000071
这里首先通过codice_432,生成指定数目的在区间codice_433中的随机浮点数,它也可以写为等价的codice_434。然后在叉子codice_435中,使用一元副词codice_40“前缀”修饰动词codice_24,从而计算权重向量的,再用前缀和除以总和得出累积分布函数。最后通过区间索引,在有随机浮点数落入特定区间的时候,生成这个区间对应的随机整数。
动词codice_428的表达式是个二层二元钩子,外层钩子的第一步运算应用到右参数上,它的第二步运算即内层钩子,整体修饰了二元副词codice_49“被动”而对换了两个参数的位置。内层钩子的第一步运算codice_435所应用的右参数实际上是外层钩子的左参数,它的第二步运算codice_292修饰了codice_49,从而将它所面对的内层钩子的左右两参数,再次对换回到外层钩子即整体表达式原先的位置上。
下面是将区间索引和键分组结合起来的例子,演示了-莱维中心极限定理:
histogram =:
summary=:
sampleMean=:
24&histogram @ (80&summary) @ (10 sampleMean (?@$&0)) 1e6
...▌▌▌▌▌▌...
...▌▌▌▌▌▌▌▌...
...▌▌▌▌▌▌▌▌▌▌▌▌...
...▌▌▌▌▌▌▌▌▌▌▌▌▌▌...
...▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌...
...▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌...
...▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌...
...▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌...
...▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌...
...▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌...
...▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌...
...▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌...
lineChart=:
24&lineChart @ (+/\) @ (80&summary) @ (10 sampleMean (?@$&0)) 1e6
...██...
...█...
...██...
...█...
...█...
...█...
...█...
...█...
...█...
...█...
...██...
...██████████████████...
这里定义的codice_443局部赋值了两个局部变量codice_444和codice_386,可以将这两个局部赋值去掉,并将这两个局部变量的出现替代为运算元codice_113和codice_114,如此对它的调用将变成codice_448这样的形式。接下的代码结合前面两个例子,采用了不同于上例连续型均匀分布的其他分布作为独立同分布,和不同的:
ratio=: [: : (-&1@#/.~@(,~ i.)~ % #@])
10&histogram @ ((#wt)&ratio) @ (wt&ran) 1e6
random=: [: : (#@[ %~ (?@$&0@] + ((I.~ (+/\ % +/))~ ?@$&0)))
20&lineChart @ (80&summary) @ (wt&random) 1e6
20&lineChart @ (80&summary) @ (2 sampleMean (wt&random)) 1e6
20&lineChart @ (80&summary) @ (4 sampleMean (wt&random)) 1e6
SQLite插件.
操纵SQLite数据库的需要安装相应插件:
load 'pacman' NB. 加载包管理器
'install' jpkg 'data/sqlite' NB. 安装SQLite数据库插件
load 'data/sqlite' NB. 加载SQLite数据库插件
getbin_psqlite_ " NB. 安装SQLite数据库的共享库
SQLite数据库的简单用例:
load 'data/sqlite' NB. 加载SQLite数据库插件
db=: sqlopen_psqlite_ '~addons/data/sqlite/db/sandp.db' NB. 打开样例数据库文件
sqltables__db " NB. 查看所有表格名字
│p│s│sp│
sqlmeta__db 's' NB. 查看表格s的结构
│cid│name │type│notnull│dflt_value│pk│
│0 │sid │text│0 │NULL │1 │
│1 │name │text│0 │NULL │0 │
│2 │status│int │0 │NULL │0 │
│3 │city │text│0 │NULL │0 │
ds=: sqlread__db 'select * from s' NB. 读取表格s,结果表格形状扁长不适合直接展示
dict=: |: @:> NB. 以字典方式显示表格数据
] rs=: dict ds
│sid │┌──┬──┬──┬──┬──┐ │
│ ││s1│s2│s3│s4│s5│ │
├──────┼──────────────────────────────────┤
│name │┌─────┬─────┬─────┬─────┬─────┐ │
│ ││smith│jones│blake│clark│adams│ │
├──────┼──────────────────────────────────┤
│status│20 10 30 20 30 │
│city │┌──────┬─────┬─────┬──────┬──────┐│
│ ││london│paris│paris│london│athens││
└──────┴──────────────────────────────────┘
rs -: sqldict__db 's'
1
cols=: {: @:> NB. 表格数据的诸列列表
cs=: cols ds
cs -: sqlexec__db 's'
1
('s_'&, &.> @ {. @:> ds) =: cs NB. 将表格的诸列并行赋值给添加了表名前缀的诸列名
s_status
20 10 30 20 30
s_sid
│s1│s2│s3│s4│s5│
({. @:> ds) -: sqlcols__db 's'
1
reads=: ({. , (,@> &.>)@}.) @:> NB. 格式化显示表格数据
] rs=: reads ds
│sid│name │status│city │
│s1 │smith│20 │london│
│s2 │jones│10 │paris │
│s3 │blake│30 │paris │
│s4 │clark│20 │london│
│s5 │adams│30 │athens│
$ @ (2&{:: @ {:) rs NB. 第3列数据的形状
5 1
rs -: sqlreads__db 's'
1
readm=: ({. ; @ >)@{:) @:> NB. 以矩阵显示表格数据
] rs=: readm ds
│┌───┬────┬──────┬────┐│┌──┬─────┬──┬──────┐│
││sid│name│status│city│││s1│smith│20│london││
│ ││s2│jones│10│paris ││
│ ││s3│blake│30│paris ││
│ ││s4│clark│20│london││
│ ││s5│adams│30│athens││
└──────────────────────┴────────────────────┘
rs -: sqlreadm__db 's'
1
cp=: '~addons/data/sqlite/db/sandp.db' ; '~/test_sandp.db'
db=: sqlcopy_psqlite_ cp NB. 复制数据库并打开复本
cls=: sqlcols__db 's' NB. 得到表格s的列名列表
dat=: ('s6';'s7') ; ('brown';'eaton') ; 40 10 ;)
len=:
list=:
in=:
clear=:
copy=:
destroy=: codestroy
cocurrent 'base'
在家目录中建立一个codice_449文件并录入上述代码,接着以如下代码建立字典对象并对其进行检视和简单操作:
load '~/dict.ijs'
conl 0 NB. 检视命名语境
│Dict│base│j│jcompare│jregex│jtask│z│
namelist_Dict_ 3 NB. 检视Dict类的动词
│clear│copy│create│default│del│destroy│filt│get│in│len│list│pop│set│
d=: create_Dict_ "
d NB. 变量保存的是盒装字符串
│0│
namelist__d 0 NB. 检视d对象的名词
│COCREATOR│DEFAULT│
conl 1 NB. 检视编号语境
│0│
copath <'0' NB. 检视编号语境的查找路径
│Dict│z│
set__d 'i1'
(2 3) set__d 'i2'
2 3
'abc' set__d 'i3'
abc
len__d "
3
list__d "
│i1│i2│i3│
e=: copy__d "
get__d 'i2'
2 3
del__d 'i2'
1
in__d 'i3'
1
pop__d 'i3'
abc
clear__d "
1
list__e "
│i1│i2│i3│
外部连结.
-{H|zh-hans:汇编语言;zh-hant:组合语言}-
-{H|zh-hans:标记语言;zh-hant:置标语言}-