热门问题
时间线
聊天
视角
J语言
来自维基百科,自由的百科全书
Remove ads
J语言,是一种阵列编程语言,由肯尼斯·艾佛森和許國華於1990年代初發明。J语言是APL語言的一种方言[5][6],延续了APL鲜明的簡潔性,它在數學和统计学程式設計上十分高效,特別是在需要进行矩陣运算的场合。
J语言最初起步于肯尼斯·艾佛森在1987年发表的《APL字典》[7],它实现了其中至关重要的秩的概念[8]。J语言提供隐式定义机制包括秩、钩子[9]、叉子[10]和多种函数复合[11],并介入了作为头等对象的动名词,用以建立控制结构[12],它常被作为隱式編程的典范之一[13]。
Remove ads
简介
Ken Iverson(右)和Roger Hui在1996年的照片[14]
J语言的运算符,承袭APL传统,没有优先级并且最右先行,2 * 3 + 4
按照2 * (3 + 4)
来运算。以历史上APL使用的典型符号为例,符号/
被用来指示折叠函数foldr1
,所以+/1 2 3
等价于1 + (2 + 3)
;在APL中,除法被表示为数学除号÷
,它将减号和冒号一起重复打印在EBCDIC和ASCII二者的纸质文本终端上;J语言使用%
表示除法,是对除号的一种近似或提示。
為了避免APL使用特殊的字符而遇到的問題,J语言只需基本的ASCII字符集,但使用点号.
和冒号:
作为“屈折”[15]。点号和冒号除了前导着空白字符的情况之外,都与紧前字符形成类似双字符组的短字。多数“基础”或“原始”的J单字,都充当数学符号,通过点号或冒号来扩展这些可用基本字符的含义。在其他语言中经常是成对的很多字符,比如[] {} "" `` <>
,在J语言中被当作单独的字,或者在有屈折的时候,作为多字符字的单字符字根。
J语言不再支持从1968年的APL\360就有的[;]
形式的方括号索引,转而支持叫做“来自”(from)的索引机制[16],它起源自Kenneth E. Iverson于1978年在《算子和函数》中提出的,依据基数解码定义[17],并用符号⌷
表示的索引[18]。
J语言承袭IBM APL\360采用了平坦阵列模型[19],不支持由NARS(嵌套阵列研究系统)于1981年介入[20],并被IBM APL2所采纳的嵌套阵列模型[21];J语言增加了Kenneth E. Iverson于1978年在《算子和函数》中提出的盒装数据类型[22],它由SHARP APL于1981年介入,并于1983年在I. P. Sharp协会研究报告《理性化APL》中,列入与APL2相比较的“限定子集”(RS)而著重强调[23]。
J语言支持AVX2指令集进行SIMD运算[24]。为了包装用面向对象编程语言开发的API和框架,J语言提供了层级命名空间机制[25],这里所有名字都存在于特定语境(locale)中[26],可以避免软件包之间的名字冲突,并能有效的用作基于类的面向对象编程的框架[27]。
J语言解释器默认装载标准库[28]。通过包管理器[29],还可以安装各种插件[30],如果是在管理员权限下安装的J语言解释器,则安装插件也需要同样的管理员权限。J语言拥有常规调试机制,还有叫做Dissect的可视调试器[31]。除了科学计算和统计分析,它还被用于关系数据库管理系统如Jd[32]、极限编程[33]和网络性能分析[34]。
2011年3月,J语言采用了GNU通用公共许可证版本3,从而成为自由和开源软件[35],人们还可以在Jsoftware的商业许可证下利用源代码[36]。
Remove ads
文档与词类
J语言的文档包括在官网的NuVoc中[37],在其中将主要的字罗列为“J原语”,并使用颜色标示出它们分别的词类[38]。早期的文档还有入门和字典。在J语言中的字,被识别为名词[39]、动词[40]、定语[41](副词和连词)、系词、标点、控制字。一个程序或例程,如果接受数据作为输入并产生数据作为输出,则被称为“动词”,与之相对,数据参数被称为“名词”。
动词有两种形式:只有右侧一个参数的一元(monad)形式,和有左右两侧参数的二元(dyad)形式,例如在-1
中减号是一元动词,而在3-2
中减号是二元动词。J语言预定义了很丰富的动词,它们都自动的作用于多种数据类型之上。用户定义的程序可以自行命名,并用在任何允许使用原始动词的地方。无论原始动词还是派生动词,它们的一元定义与二元定义,在很大程度上是独立的。
Remove ads
起步示例
J语言可以写出非常精简的程序,特别是存在重度的对符号的函数重载,以至于一些编程者将它称为难以阅读的只写语言。在计算机的终端上执行ijconsole
,即可进入J语言的REPL解释器界面。
J语言的“Hello, World!”程序:
'Hello, world!'
Hello, world!
这个Hello World的实现反映了J语言的传统用法,就是把程序录入到J解释器会话中,显示出表达式结果。还可以准备J脚本来作为独立程序来执行,比如在Linux系统上,可以编辑如下内容的一个文本文件,并命名为比如test01.ijs
:
#!/usr/bin/ijconsole
echo 'Hello, world!'
exit ''
注意第一行的#!
必须顶头,这里的echo
和exit
,是与Unix shell中同名命令功能类似的动词。然后在终端界面中执行这个文件:
$ ijconsole test01.ijs
Hello, world!
$ chmod +x test01.ijs # 另一种执行方式,授予这个文件可执行权限
$ ./test01.ijs
Hello, world!
Remove ads
在J语言中函数一般称为动词,例如定义一个叫做avg
的动词,计算一序列数的平均:
avg=: +/ % #
avg 1 2 3 4
2.5
一元动词#
“计数”(tally),总计阵列中项目的总个数。动词+
“加”(plus)和副词/
“插入”(insert),派生出的动词+/
,合计这个阵列的项目的总和。二元动词%
“除”(divide)将这个总和除以这个总个数。而用户定义的动词avg
,用到了由连串(strand)的三个动词(+/
、%
和 #
)构成的一个“叉子”(fork)。叉子(f g h) y
↔(f y) g (h y)
,这里的f
、g
和h
指示动词,而y
指示一个名词。
使用avg
的一些例子:
]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
一元副词/
“插入”(insert),接受位于它左侧的一个运算元,并产生将这个动词应用于其参数的每个项目之间的一个动词。就是说,+/
是一个动词,定义为应用+
于给它的参数的各个项目之间。计算移动平均用到的二元副词\
“中缀”(infix),将作为数据参数的列表划分成一系列的指定大小的连续项目的子列表,将所修饰动词应用于其上,并将这些结果形成一个列表。
一元动词]
“相同”(same),恒等于给它的单一右参数,常像这样用来在赋值之后显示变量的内容。一元动词?.
“掷骰/固定种子”(roll/fixed seed),不同于一元动词?
“掷骰”(roll),在生成数据参数项目所指定大小范围内的随机数之时,采用固定的种子。这里确定对矩阵按行还是按列进行平均,用到了连词"
“秩”(rank),它在后面的定语章节和单独条目中论述。
Remove ads
二元动词i.
“出现索引”(index of),和二元动词i:
“最后出现索引”(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传统的两步骤方式[42],使用一元动词/:
“升序索引”(grade up)或\:
“降序索引”(grade down),和用二元副词~
“被动”修饰的二元动词{
“出自”(from),二者连串(strand)形成的一个“钩子”来完成。一元钩子(f g) y
↔y f (g y)
;副词~
“反身·被动”,其一元定义为f~ y
↔y f y
,二元定义为x f~ y
↔y f x
。J语言还提供专用的二元动词/:
“上升排序”(sort up)或\:
“下降排序”(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
Remove ads
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系统中,建立如下内容的文本文件,并命名为比如filter01.ijs
:
#!/usr/bin/ijconsole
load 'tables/csv'
stdout makecsv 10 + makenum fixcsv stdin ''
exit ''
然后在终端界面中执行如下命令行:
$ cat test01.csv | ijconsole filter01.ijs
10,11,12
13,14,15
Remove ads
数据类型
J语言支持三种简单类型:
- 数值
- 文字(字符)
- 盒装
其中数值有很多变种。J语言提供的唯一搜集(collection)类型,是任意维度的阵列。多数算法可以使用这些阵列来简洁的表达。
J语言的数值类型之一是“位”。位有两个值:0
和1
。位还可以形成列表,例如1 0 1 0 1 1 0 0
,是8个位的列表。在语法上,J分析器将位当作一个字。空格字符被识别为字形成字符,它处在属于其他数值字的字符之间。
J语言支持任意长度的列表。J语言进一步的在这些位列表之上,支持所有常见二元运算,比如动词*.
“与”(and)、+.
“或”(or)、-.
“非”(not)、|.
“反转·旋转”(reverse·rotate)、|.!.f
“移位”(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
<.@o. 10x ^50 NB. π乘以扩展精度10的50次幂
314159265358979323846264338327950288419716939937510
这里采用一元动词o.
“π乘以”(pi times),和一元动词<.
“下取整”(floor)二者的复合,得到预期的结果[44]。位的列表可以使用一元动词#.
“基数2”(base 2)解码成整数[17]。整数可以使用一元动词#:
“反基数2”(antibase 2)编码为位的列表。
J语言还支持文字即字符类型。文字包围在撇号'
之间,比如'a'
或'b'
。文字的列表,通过将多个字符用撇号包围起来的常规字符串约定来表示,比如'abcdefg'
。在字符串内的''
表示'
字符本身。单个的文字,典型的是8
位宽即单字节的ASCII字符,此外J语言还支持Unicode文字。
不支持在文字上的数值和布尔运算,但支持面向搜集的运算,比如旋转等。使用动词".
“执行·数值”(do·numbers),将字节阵列转换成数值;使用动词":
“缺省格式·格式”(default format·format),将数值转换成字节阵列。
盒装类型的值是0维标量[22],而不管所包含的是怎样的数据结构。使用一元动词<
“盒装”(box),将数据放置入盒子中;使用一元动词>
“打开”(open),打开盒子中取出其中数据。还可以通过二元动词;
“链接”(link)建立盒子的列表,通过一元动词;
“拆除”(raze)移除一层盒子的列表。盒子内可以装入其他盒子,还可以通过二元动词$
“重制形状”(reshape)和二元动词#
“计件复制”(copy)等操作盒子及其列表。
J语言的阵列,具有同质(homogeneous)的项目类型,例如列表1 2 3
是整数的列表,尽管1
还可以是一个位。这种类型问题,在极大程度上对于编程者是透明的。只有特定的特殊运算,会显露出在类型上的不同。例如列表1.0 0.0 1.0 0.0
,对大多数运算,将被当作是完全同于列表1 0 1 0
。
J语言支持数值稀疏阵列,通过它们的下标存储非零数值。这在非零数值相对很少的情况下,是有效率的机制。
简要词汇表
下面的表格简要列出了常用词汇。如果含义中用了间隔号( · )分隔,通常前者是只有一个右侧参数的一元含义,后者是左右两侧都有参数的二元含义。列出的对应APL符号,是Dyalog等现代APL所采用的符号。
定语
J语言的能力,很大程度上来自它的“定语”(modifier:修饰词),这个范畴包括“副词”和“连词”:这些符号接受名词和动词作为运算元(operand),并以指定方式应用这些运算元。定语都可以应用于任何动词,包括用户写的动词,用户可以写自己的定语。
J语言的二元动词有右结合性,或称为尽量长右作用域,即它有尽可能多的右参数。定语即算子有左结合性,或称为尽量长左作用域,即它有尽可能多的左运算元。如果表达式中存在定语即算子,首先应用这些定语,然后应用其生成的动词。
一元副词/
“插入”(insert),副词\
“前缀·中缀”(prefix·infix),副词\.
“后缀·外缀”(suffix·outfix),和连词;.
“剪切”(cut)[49],指定参数的诸个规则或不规则子集,并在其上执行运算。在J语言实现中,前缀和+/\
、极小值<./\
和极大值>./\
运算,是典型的会对其进行速度优化的特殊组合[50]。
副词~
“反身·被动”(reflex·passive),其一元形式f~ y
↔y f y
,将提供给动词的右参数重复放置在左参数位置上;二元形式x f~ y
↔y f x
,将提供给动词的左右两个参数对换位置。
名词的秩(rank)是排布其原子所依据的轴的数目,即它的形状中项目的数目。动词的秩是它能够在其上直接运算的右(和左)名词参数的最高秩,典型的表示为三个原子的一个列表:一元秩 二元左秩 二元右秩
。对副词和连词标示的秩,是所形成的动词的秩。
秩在特定动词和特定名词的上下文下,将名词的诸维,划分成前缀诸维的序列,称为框架(frame);和后缀诸维的序列,称为单元(cell)。秩采用连词"
“秩”来操纵[51],对应于APL符号⍤
,它有三种形式:u"n
“指定秩”(assign),m"n
“常量动词”(constant),u"v
和m"v
“复制秩”(copy),这里的u
、v
表示动词运算元,而m
、n
表示名词运算元。正数动词秩,指示单元诸维的数目,负数动词秩,指示框架诸维的数目,_
指示整体。
二元动词的左右参数的框架经常是匹配的,就是说二者有相同的形状,从而保证了它们有相同数目的单元。如果左右参数的框架不匹配,有三种可以运算的情况[52]:
- 标量一致,如果指定了两参数中某一侧的框架为空,即秩为
_
,这一个整体单元被应用于另一侧参数的所有单元。 - 前缀一致,两参数中有一侧的短框架是另一侧长框架的前缀,短框架的每个单元,被应用于对应的长框架去掉前缀余下诸维形成的单元阵列的所有单元。
- 后缀一致,两参数中有一侧的短框架是另一侧长框架的后缀,这时需要以两侧加上相同正数增量的方式指定秩,使原短框架侧的框架为空,原短框架的诸维形成的这一个单元阵列,被应用于原长框架去掉后缀余下诸维形成阵列的每个单元阵列。
连词@:
“在于”(at)、@
“顶上”(atop)、&:
“并列”(appose)、&
“合成”(compose),是四种复合(composition)。J语言支持叫作“钩子”(hook)和“叉子”(fork)的隐形连词[9][10],二种隐形连词和四种复合连词,规定了如何将参数或将所饰动词应用于参数的结果,提供给所饰动词来进行应用的规则。下表列出它们的定义:
在上面表格中,mv=: 0{v b.0
,lv=: 1{v b.0
,rv=: 2{v b.0
,这里的副词b.0
给出动词v
的三个秩[53]。在应用四种复合连词形成新动词的表达式中,@:
和&:
,要对第一步运算的中间结果,按所在子表达式的秩或整个表达式的秩_
进行汇集(assembly),并在有需要的情况下进行框架填充[54],然后在这个汇集成的框架内进行第二步运算;而@
和&
,直接在第一步运算的框架内,对中间结果进行第二步运算[55];在整个表达式求值结束时,最终结果在有需要的情况下要进行整体填充。
在x (u @ v) y
中,一元u
直接在二元v
所划分的框架内进行自己的运算。在x (u @: v)"v y
中,@:
将二元v
的运算结果,汇集成"v
所指定的框架。在x (u & v) y
,二元u
直接在一元v
所划分的两个框架内进行自己的运算。在x (u &: v)"mv y
中,&:
将一元v
的两个运算结果,汇集成"mv
所指定的框架。
下面例子展示四种复合的中间结果的单元差异:
] a =: >:i. 2 3
1 2 3
4 5 6
] b =: 0.1*>:i. 2
0.1 0.2
a (< @: +) b
┌───────────┐
│1.1 2.1 3.1│
│4.2 5.2 6.2│
└───────────┘
a (< @ +) b
┌───┬───┬───┐
│1.1│2.1│3.1│
├───┼───┼───┤
│4.2│5.2│6.2│
└───┴───┴───┘
a (; &: |) b
┌─────┬───────┐
│1 2 3│0.1 0.2│
│4 5 6│ │
└─────┴───────┘
a (; & |) b NB. 这里的框架仍是2 3
┌─┬───┐
│1│0.1│
├─┼───┤
│2│0.1│
├─┼───┤
│3│0.1│
└─┴───┘
┌─┬───┐
│4│0.2│
├─┼───┤
│5│0.2│
├─┼───┤
│6│0.2│
└─┴───┘
当连词&
的一个运算元是名词的时候,表示“粘上”(bond),它通过向二元动词固定提供其一个参数的值而产生一个动词:
&
派生的动词经常作为一元动词使用,即m&v y
或u&n y
;如果作为二元动词使用,即x m&v y
或x u&n y
,左参数表示应用这个派生动词于右参数的次数。需要确保通过&
定义的一元动词,不出现在能够取用左右两个的参数的上下文中;如此定义的二元动词,也不应该出现在只能取用一个右参数的上下文中。需要注意m&v
、v/
和v\
等的左参数的作用域,有时遇到其左侧的复合连词会产生并非预期的效果,经常需要将表达式整体加以圆括号包围。
J语言还提供连词&.:
“底下”(under)和&.
“对偶”(dual)[56]。下面定义中的动词幂^:_1
表示逆运算:
@
、&
和&.
合称为“紧密复合”(close composition)。现代APL中另有¨
“每个”(each),f¨
相当于J语言中的f &.>
[57]。例如:
1 2 + &.:> 0.1 0.2
┌───────┐
│1.1 2.2│
└───────┘
1 2 + &.> 0.1 0.2
┌───┬───┐
│1.1│2.2│
└───┴───┘
在J语言中,孤立的动词序列叫做“列车”(train)[13], e f g h
意味着(e (f g h))
,d e f g h
意味着(d e (f g h))
;以此类推,动词列车的一般模式(a b c ...)
,依赖于动词的数目,在偶数时形式为(a (b c ...))
,最外层是个钩子;而在奇数时形式为(a b (c ...))
,最外层是个叉子;二者的内部都是可能有多层的嵌套的叉子。
叉子、@:
再加上[
和]
,可以将很多常用复合写为列车。在惯用法([: f g)
中,并不实际执行的隐式动词[:
“遮帽”(cap),屏蔽了叉子的左分支,形成了等价于f @: g
的特殊化叉子[58]。
与现代APL如Dyalog等对照,复合连词@:
对应于同秩连词共享APL符号⍤
的“顶上”(atop),而&:
对应于⍥
“上方”(over),共享&
符号的“粘上”,对应于APL中的“绑上”(bind),APL的“绑上”和“边上”(beside)共享符号∘
[59],“边上”的一元形式同于⍤
,而二元形式同于钩子。在《APL字典》中,@
对应其⍥
[60],而&
列入秩连词⍤
之内[61],钩子对应于符号⍩
“枝条”(withe)[62],&.
对应于符号¨
[63]。
下面的简单例子是计算欧几里得范数,和生成数位与维度坐标一致的整数:
]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
在coor
中采用的圆括号包围,使得@:
处在整个表达式的最外层,从而形成了两步骤运算;右侧的第一步是圆括号包围的子表达式,它的完全加圆括号(fully-parenthesized)形式为:(((< @: >:) @ i.)"0)
;左侧的第二步是{
与复合到其上诸运算构成的子表达式,它的完全加圆括号形式为:(((10 & #.) @ >) @ {)
。一元动词{
“目录汇编”(catalogue),应用在盒装列表的列表之上,是接受可变数目的变长参数的典型的动词。
下面通过对圆括号包围的子表达式加以变化,辨析秩连词和复合连词的特性。这里的动词]
,划分开了给连词或副词的名词运算元,和给所生成的动词的名词参数:
]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│
└───────┘
下面的例子展示并联电阻电路计算:,它可以如下这样表达[64]:
Rtotal=: +/ &.: %
Rtotal 10 5 15
2.72727
连词^:
动词幂”(power of verb)[65],有两种形式:
^:n
,是运算元为名词的“固定幂”。^:v
,是运算元为动词的“动态幂”。
对于固定幂u ^:n
,如果x
缺席,u ^:n y
在以y
为运算对象的迭代中,将动词u
应用n
次;如果x
存在,x u ^:n y
在以y
为运算对象的迭代中,将动词x&u
应用n
次。如果n
是阵列,则按每个原子项目都执行一次动词幂,结果的框架为这个阵列的形状;如果n
是取值为0
或1
的变量,则形成布尔值条件执行;如果n
是_1
,则进行u
的逆运算[66];如果n
是_
,则意味着“收敛”(converge),即反复应用u
直到结果不再变化。例如:
(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
对于动态幂u ^:v
,如果x
缺席,u ^:v y
在以y
为运算对象的迭代中,将动词u
应用v y
次;如果x
存在,x u ^:v y
在以y
为运算对象的迭代中,将动词x&u
应用x v y
次。动词幂可以形成动态条件执行,这里的动词v
必须总是产生布尔值结果,应用动词u
当且仅当v
返回1
。进而u ^:v ^:_ y
可以形成while循环构造,只要v
返回1
,就反复的执行u
,直到v
返回0
,或者u
将它的参数无变化的返回。例如:
-&2 ^:(>&4) "0 ] 1 3 6 12 NB. 对大于阈值4的列表项目减去2
1 3 4 10
4 (0.25&*@[+(1-0.25)&*@]) ^:< "0 ] 1 3 6 12 NB. 对大于阈值4的列表项目在其超出部份上扣除25%
1 3 5.5 10
+&3 ^:(<&100) ^:_ "0 ] 2 3 100 NB. 只要列表项目小于100就对它加上3
101 102 100
下面的例子用来辨析秩指定与框架划分及汇集的性质,其中涉及的直接定义等内容可见于后面的定义章节:
itemize=: ,: : {{,: ^:x ] y}}"(0 _) NB. 定义具有一元和二元两种形式的项目化扩秩运算
<@itemize~ i.3
┌─────┬─────┬─────┐
│0 1 2│0 1 2│0 1 2│
└─────┴─────┴─────┘
$&.> <@itemize~ i.3
┌─┬───┬─────┐
│3│1 3│1 1 3│
└─┴───┴─────┘
$ <"2@itemize~ i.3
3 1
$&.> <"2@itemize~ i.3
┌───┐
│3 │
├───┤
│1 3│
├───┤
│1 3│
└───┘
$ <"_1@itemize~ i.3
3 3
$&.> <"_1@itemize~ i.3
┌───┬─┬─┐
│ │ │ │
├───┼─┼─┤
│3 │0│0│
├───┼─┼─┤
│1 3│0│0│
└───┴─┴─┘
#&.>@<"_1@itemize~ i.3
┌─┬─┬─┐
│1│1│1│
├─┼─┼─┤
│3│ │ │
├─┼─┼─┤
│1│ │ │
└─┴─┴─┘
$ 1
$ 0$0
0
# 0$0
0
$ <"1@itemize~ i.3
3 1 1
$ <"_2@itemize~ i.3
3 1 3
$ <"0@itemize~ i.3
3 1 1 3
$ <"_3@itemize~ i.3
3 1 1 3
在J语言中,动名词(gerund)是叫做“原子表示”的特殊盒子的一个列表,这种盒子可以像任何其他盒子一样使用,并可以最终转变回到要执行的动词。关于动名词的运算有:
- 连词
`
“连结动名词”(tie or gerund),建立动名词。 - 连词
`:
“唤起动名词”(evoke gerund),在加以运算元之后成为:`:6
,将动名词转变成动词列车;`:0
,将转变回来的这些动词分别单独应用,并将它们的结果收集入一个列表。
- 连词
@.
“议程”(agenda),m @. n
从动名词m
中,选择出第n
个原子表示,将它转变回到动词并执行它。
下面是动名词简单示例和考拉兹猜想示例:
grd =: * ` (+&2) NB. 建立一个动名词
grd NB. 显示原子表示,这里的符号'0'标识名词
┌─┬─────────────┐
│*│┌─┬─────────┐│
│ ││&│┌─┬─────┐││
│ ││ ││+│┌─┬─┐│││
│ ││ ││ ││0│2││││
│ ││ ││ │└─┴─┘│││
│ ││ │└─┴─────┘││
│ │└─┴─────────┘│
└─┴─────────────┘
{. grd NB. 动名词可以像普通盒子一样操纵
┌─┐
│*│
└─┘
grd `:6 i. 4 NB. 将动名词转换成动词列车来执行
0 3 8 15
grd `:0 i. 4 NB. 将动名词转换成并行执行的动词
0 1 1 1
2 3 4 5
Collatz=: -: ` (1+*&3) @. (2&|) NB. 考拉兹猜想的算式
Collatz "0 ] 1 2 3 4 5 6 7
4 1 10 2 16 3 22
>./@(Collatz^:(>&1)^:_"0@>:@?@$&1e6) 1000 NB. 取1000个在1e6内的随机数测试考拉兹猜想
1
定义
J语言支持用户进行显式定义[67],和{{
……}}
形式的直接定义[68]。下面以五种复合作为显式定义的例子:
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=: {{x u@:v"(1 _) y}}
(a (+/ matmul *) b) -: p
1
inner=: {{x (u@:v"1)"(1 _) y}}
(a (+/ inner * 0&|:) b) -: p
1
outer=: {{x u@(v/"_1~ |:)~"(2 _) y}}
(a (+/ outer *) b) -: p
1
revmul=: {{x (i.<:#$y)&|:@(u@:v"(_ 1)~ |:)~"(2 _) y}}
(a (+/ revmul * 0&|:) b) -: p
1
APL传统上的将内积Pf.gQ
解释为f/PgQ
[47],J语言的矩阵乘法要求写为Pf/ .gQ
,不隐含的为左运算元f
附加一元/
。转置也是有较大开销的运算,不同的矩阵乘法算法有不同的参照局部性。
matmul
通过"(1 _)
将u@:v
的右参数从向量扩展为一般阵列,它用于右参数为行主序阵列的情况。在向量与向量列表二者诸项之间逐对的进行乘积累加运算,是BLAS的标准算法[69]。inner
采用了两向量之间的点积运算u@:v"1
,它适宜直接用于右参数为列主序阵列的情况。在右参数为行主序阵列之时,需要如例子代码这样,对右参数阵列进行二元转置0&|:
,这里的0
指示将第一轴安排至最后位置而其他轴保持原序前移。这种基于点积的实现,通常需要进一步加以循环镶嵌。outer
可以看作matmul
的变体,它首先对左参数阵列进行转置,然后进行多组的向量与一般阵列之间二元的张量积运算v/"_1
[48],最后在各组结果的列表上进行u
计算,每组运算之后参与其中的左右两阵列的元素不会被其他组的运算再次访问。revmul
同inner
一样适宜直接用于右参数为列主序阵列的情况,但采用了同matmul
类似的计算方法,它在乘法之前对左参数阵列和在乘法之后对结果要做转置,matmul
与revmul
的关系如同。matmul
一次性访问左参数阵列,反复多次访问右参数阵列;revmul
一次性访问右参数阵列,反复多次访问左参数阵列。
在隐式定义中,递归定义可以不通过名字引用自身,转而使用动词$:
“自引用”。例如递归的计算斐波那契数列:
fibonacci=: 1: ` ($:@-&2 + $:@<:) @. (>&2) "0 : [:
fibonacci >:i.9
1 1 2 3 5 8 13 21 34
_9:
到9:
是常量动词。动词[:
“遮帽”,用在连词u : v
“一元与二元定义”所应用的动词位置上,即充任了一元动词u
或二元动词v
,可分别在如下两种情况下报错:定义的是二元动词,却不适当的被用作一元动词;或定义的是一元动词,却不适当被用作二元动词。
在显式定义和直接定义中,提供了类似其他过程语言的控制结构[70]。这里列出的是每个范畴内的代表性控制字:
索引
J语言的索引机制采用二元{
“来自”(from)动词来完成,它的秩为0 _
,它有两种形式,分别为左参数为索引阵列的主轴索引,和左参数为二层或一层盒装结构的逐轴索引,二者分别对应APL中,方括号内为单个轴的主轴索引选取,和方括号内为;
分隔的多个轴的逐轴索引选取。
索引阵列的每个项目指定对主轴的项目单元的一个选取,将它们的结果再汇合为一个阵列。负值索引表示从末尾往前记数。在APL中,这种形式的索引被称为“来自”(from),也叫做“选取”(select)或幽默地称为“明智”(sane)索引,最早出现在SAX(SHARP APL for UNIX)对其@
“来自”索引的扩展中,部份现代APL,将它表示为符号⊇
。例如:
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
逐轴选取可以形成子阵列[16],在现代APL中,它被表示为⌷
,故而也被称为“扁方块”(squad:squish quad)索引(indexing)或就叫做“索引”(index)函数。装在二层盒装结构中的,是对应诸轴的一层盒装子结构的列表,其中每个一层盒装子结构内都是数值列表,它对应在此轴内一个或多个项目选择。默认全选使用名词a:
“么点”(ace)指示,它是盒装空列表<0$0
。在尾部的连续多个默认全选不需要写出。例如:
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
(<1 2;0 2 3) { i. 3 4
4 6 7
8 10 11
(<a:;0 2 3) { i. 3 4
0 2 3
4 6 7
8 10 11
(<a:;0 2 3)
┌──────────┐
│┌──┬─────┐│
││┌┐│0 2 3││
│││││ ││
││└┘│ ││
│└──┴─────┘│
└──────────┘
它还支持一层盒装结构,装在其中的是数值列表,它的元素指示每轴选取一个项目,如果单选了所有轴,则指定一个原子项目。一层盒装结构中的这个数值列表,等价于二层盒装结构中一层盒装的单一数值的列表。两种索引形式可以结合使用,即可以将盒装结构的阵列作为给{
的左参数,它按这个阵列的形状汇合多个逐轴选取的结果。例如:
(<1 2) { i. 3 4
6
((<0 0),(<2 2),(<1 1)) { i. 3 4
0 10 5
(<0 0),(<2 2),(<1 1)
┌───┬───┬───┐
│0 0│2 2│1 1│
└───┴───┴───┘
例如(<<1 2),(<a:;0 2 3)
这样的选取是合法的,将形状不一致的选取结果汇合在一起,会导致结果值填充[54]。
重排轴也叫做二元转置。APL的二元转置对轴次序的指定,类似于组合数学中置换的一行形式的柯西表示法[71],它被称为“可能是APL编程者最后掌握的原始运算之一”[72]。J语言的二元转置对轴次序的指定,不涉及将多个轴映射到结果中的一个轴的情况,使用了普通的索引形式的置换向量[73]。在置换向量的元素个数小于阵列轴的数目之时,J语言在置换结果中将其指定的诸轴安排在尾部,而其他轴保持原来相对次序前移。
针对向量的索引运算,是不加盒装的主轴索引。要访问一般阵列的特定原子项目,其选取向量需要一层盒装,从而对其进行逐轴索引。置换向量p
所对应的逆向置换向量是/:p
[42],对于两个置换向量p
和q
,则有/:p{q
↔ (/:q){/:p
。
二元转置与索引机制有密切关联[18],它有两个重要性质[74]:
- 两次连续的二元转置,可以变换成等价形式:
p|:q|:A
↔(p{q)|:A
,即先将后者置换向量p
对前者置换向量q
进行置换,然后用结果的置换向量做一次二元转置。 - 对二元转置后的阵列,进行原子项目的选取,可以变换成等价形式:
(<k){p|:A
↔(<(/:p){k){A
,即先用置换向量p
的逆置换向量/:p
,对选取向量k
进行置换,然后用结果的选取向量来选取未转置阵列。
下面是演示例子代码:
a=: ? @ $&1000 @: >: 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
((<k) { p|:a) -: (<(/:p){k) { a
1
示例
下面例子形成帕斯卡三角的二项式系数的直接定义[75],并顺带展示基本的矩阵与盒装运算:
pascal=: {{(0&, + ,&0) ^: y 1}}"0
pascal @ i. ] 5
1 0 0 0 0
1 1 0 0 0
1 2 1 0 0
1 3 3 1 0
1 4 6 4 1
({. (+//. @: pascal @ i.)) 9 NB. 斐波那契数列
1 1 2 3 5 8 13 21 34
((+/ . *) |:) @: pascal @ i. ] 5 NB. 帕斯卡矩阵
1 1 1 1 1
1 2 3 4 5
1 3 6 10 15
1 4 10 20 35
1 5 15 35 70
<"0 @ pascal @ i. ] 5
┌─┬─┬─┬─┬─┐
│1│ │ │ │ │
├─┼─┼─┼─┼─┤
│1│1│ │ │ │
├─┼─┼─┼─┼─┤
│1│2│1│ │ │
├─┼─┼─┼─┼─┤
│1│3│3│1│ │
├─┼─┼─┼─┼─┤
│1│4│6│4│1│
└─┴─┴─┴─┴─┘
]t=: < @ pascal @ i. ] 5
┌─┬───┬─────┬───────┬─────────┐
│1│1 1│1 2 1│1 3 3 1│1 4 6 4 1│
└─┴───┴─────┴───────┴─────────┘
(<@#&a:"0@|.@i.@# ,&> }:@,@:(,.&a:@<"0)&.>) t
┌─┬─┬─┬─┬─┬─┬─┬─┬─┐
│ │ │ │ │1│ │ │ │ │
├─┼─┼─┼─┼─┼─┼─┼─┼─┤
│ │ │ │1│ │1│ │ │ │
├─┼─┼─┼─┼─┼─┼─┼─┼─┤
│ │ │1│ │2│ │1│ │ │
├─┼─┼─┼─┼─┼─┼─┼─┼─┤
│ │1│ │3│ │3│ │1│ │
├─┼─┼─┼─┼─┼─┼─┼─┼─┤
│1│ │4│ │6│ │4│ │1│
└─┴─┴─┴─┴─┴─┴─┴─┴─┘
二项式系数也可以写成隐式定义形式:
pascal=: ((0&, + ,&0)@] ^:[ 1:)"0 : [:
下面是基于二元副词/
“形成表格”(table),制作条形图和散点图的简易图表例子[76]:
barChartH=: {&('.',u:16b2584) @ (>/ i.@(>./)) : [:
barChartH 3 1 4 1 5 9 2 6 5
▄▄▄......
▄........
▄▄▄▄.....
▄........
▄▄▄▄▄....
▄▄▄▄▄▄▄▄▄
▄▄.......
▄▄▄▄▄▄...
▄▄▄▄▄....
barChartV=: {&('.',u:16b258c) @ (</~ |.@i.@(>./)) : [:
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之中。
这里的scatterChart
可以使用二元副词}
“修改”(amend)来实现:
scatterChart=: {&('.',u:16b2588) @ ((>./ , #) {{
1 y } $&0 x}} (#~ <&9@{."1)@((-~ >./) ,. i.@#)) : [:
APL的二元转置,在多个轴映射到结果中的一个轴的情况下,将其依次安排到前导位置上并进行对角线选取[77]。下面基于一元动词I.
“真值位置索引”,定义进行对角线选取的动词diag
,它的左参数是布尔值列表,其中的真值1
指示与其位置对应的轴,要依次安排在前导位置上并进行对角线选取,其他的假值0
所对应的轴相对位置不变。
diag=: {{
s=. (I. , I.@:-.) @ ({.~ #@$)
t=. <"1 @ (i.@(<./)@({. $) */ #&1@[)
({~ (+/x)&t) @ (|:~ x&s) y}}
这里局部定义了s
,它的左右参数同于给diag
的参数,它生成二元转置需要的置换向量,这是由要安排到前导位置上的那些轴的位置索引,和余下其他轴的位置索引串接而成。这里的{.~ #@$
以右参数的诸轴数目选取左参数,用来在左参数的真值和假值的总数小于右参数的诸轴数目之时,对左参数填充上假值。
接着局部定义了t
,它的左参数是给diag
的布尔值列表中真值1
的个数,右参数是要对其指定数目的前导轴进行对角线选取的阵列,它生成对角线选取所需要的一层盒装选取列表。这里的(<./)@({. $)
选取出要进行对角线选取的前导诸轴的最小长度,用i.
形成这个长度的整数数列,再用#&1@[
形成其长度为左参数的全为1
的列表,通过*/
在二者之上形成表格矩阵。
最后的表达式先进行指定的二元转置,再对其结果进行相应的对角线选取。下面是简单用例:
>: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语言提供的排序机制基于了稳定排序算法,下面的例子代码是快速排序的直接定义[78]:
cmp=: * @ -
quicksort=: {{
if. 1 >: #y do. y return. end.
s=. y u y {~?# y
(u quicksort (s<0)#y),((s=0)#y),(u quicksort (s>0)#y)}}
这里定义一个动词cmp
,它通过逐个做两个数的差并取其符号,得到取值为_1
、0
或1
的平衡三进制值。cmp
将作为左运算元传递给副词quicksort
。
在quicksort
的定义中,向局部变量s
赋值的表达式,第一步随机选择支点(pivot)运算,首先计算?#y
,生成在数据总个数范围内的随机数,接着在其上计算{~
,选择出在随机数指定的位置上的支点值;它的第二步运算,将运算元u
,应用到其左参数的数据列表,和右参数的支点值二者之上。
随后是串接分治运算结果,首先将平衡三进制值列表,分别与0
做逐项的三分法比较,得到三个布尔值列表;然后以这种列表中的0
和1
作为件数,复制出数据列表的符合这个条件一个新的子列表,其中两个作为参数传递给递归调用进行排序。
下面的快速排序实现,展示了隐式编程,即将函数复合在一起,而不显式的引用任何变量,不提及要应用于其上的形式参数。这里将前面代码中向局部变量s
赋值时所求值的表达式改为隐式定义,进而以作为钩子的第一步运算的方式,代入引用这个变量的表达式之中,并且采用钩子的参数复制机制消隐了形式参数y
:
cmp=: * @ -
quicksort=: {{((($:@#~ <&0),(#~ =&0),($:@#~ >&0)) (u ({~ ?@#))) ^: (1<#) y}}
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
提供给连词^:
的左侧运算元,外层是个一元钩子,它将提供给它的单一右数据参数,重复放置在它的左数据参数位置上。这个外层一元钩子的第一步运算,是生成平衡三进制值列表的嵌套的二层一元钩子(u ({~ ?@#))
;而外层一元钩子的第二步运算,将生成的三个子列表串接起来。生成三个子列表的表达式,以数据列表是作为左参数,以平衡三进制值列表作为右参数;这里的三个二元钩子首先生成布尔值列表,接着进行对换了左右参数位置的二元复制运算,最后它们中有两个通过自引用$:
进行了递归调用。
这个定义中的$:
是在这个副词的私有语境内调用的动词,所以不像前面直接定义那样需要加上运算元u
以副词形式来调用。这里没有对字符串长度小于等于1
的情况进行处理,这是因为迭代运算在条件不满足时返回初始值,也就是返回这个字符串本身。将cmp
的表达式,代入定义中的左运算元u
,就能定义出动词,同时也不再需要外在的采用直接定义的形式。
将一元钩子替代为左分支为]
的叉子,形成的动词列车更具可读性:
quicksort=: {{(] (($:@#~ <&0),(#~ =&0),($:@#~ >&0)) ] u ] {~ [:?#) ^: (1<#) y}}
下面的例子定义基于一元/:
进行字符串比较的cmp
[79]:
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=: <;._1 t
┌───┬─────┬───┬───┬───────┬────┬───┬──────┬────┬───┬────┐
│the│heart│has│its│reasons│that│the│reason│does│not│know│
└───┴─────┴───┴───┴───────┴────┴───┴──────┴────┴───┴────┘
cmp&> quicksort words
┌────┬───┬─────┬───┬────┬───┬──────┬───────┬────┬───┬───┐
│does│has│heart│its│know│not│reason│reasons│that│the│the│
└────┴───┴─────┴───┴────┴───┴──────┴───────┴────┴───┴───┘
在这个cmp
定义中,/:@;
先将两个字符串参数进行盒装串接,然后一元/:
给出二者的升序索引,二者之间为升序或相同时为0 1
,而二者为降序时为1 0
;至此是升序还是相同仍需区分,-.@-:
判断两参数是否为“不相同”,不相同时为1
,而相同时为0
。这里的副词;._1
“自有区间”,使用字符串的第一个字符作为分隔符,对字符串进行划分并去除分隔符,然后应用所修饰的动词于这些子字符串之上。
下面的例子给出n
个项目的所有置换的有次序的矩阵[80]。首先定义名词p3
,它是置换长度为3
的全排列矩阵:
]p3=: (i.@! A. i.) 3
0 1 2
0 2 1
1 0 2
1 2 0
2 0 1
2 1 0
这里用到了动词:A.
“易位词”(anagram),x A. y
的左参数x
,指定了长度为#y
的所有置换中给特定一个置换的编号,其对应的置换向量在组合数学中被称为逆序向量,据此编号置换y
的项目。这里求全排列的数目,用到了一元动词!
“阶乘”。接着在p3
的基础上,实现置换长度为4
的全排列矩阵:
=/~ @ 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.
<"_1 perm0 4
┌───────┬───────┬───────┬───────┐
│0 1 2 3│1 0 2 3│2 0 1 3│3 0 1 2│
│0 1 3 2│1 0 3 2│2 0 3 1│3 0 2 1│
│0 2 1 3│1 2 0 3│2 1 0 3│3 1 0 2│
│0 2 3 1│1 2 3 0│2 1 3 0│3 1 2 0│
│0 3 1 2│1 3 0 2│2 3 0 1│3 2 0 1│
│0 3 2 1│1 3 2 0│2 3 1 0│3 2 1 0│
└───────┴───────┴───────┴───────┘
$ perm0 4
4 6 4
这里的{"(_ 1)
,其左侧是从6×3全排列矩阵加工而成6×4选取矩阵,用它分别对其右侧的4×4“奇妙”矩阵的每一行进行主轴索引运算,即将选取矩阵每行第1个元素保持为“奇妙”矩阵此行的第1个元素,而每行后面3个元素是对“奇妙”矩阵此行后面3个元素的排列[81]。这一步写出的排列动词的结果是4×6×4的三维阵列,将它重制形状为24×4全排列矩阵:
perm1=: (,~ !) $ ,@((0&,.@:+&1 p3) {"(_ 1) \:"1@(=/~)@i.)
$ p4=: perm1 4
24 4
二元动词$
的左参数的值为(!y) , y
,这里的y
是给排列动词的数据参数;而它的右参数是用一元动词,
将三维阵列散开后形成的一个向量。
然后将其中的p3
,替代为递归的自引用$:@-&1
;并通过名词秩,形成常量动词(1 0$0)"_
,设置长度为0
时的基础值为1 0$0
:
perm=: (1 0$0)"_ ` ((,~ !) $ ,@(0&,.@:+&1@$:@-&1 {"(_ 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
最后将递归形式改为迭代形式,可采用连词F..
“单结果正向折叠”(fold single forward),使用它需要事先安装插件dev/fold
[82]。这个连词所形成的动词,从左至右遍历右参数列表,将其项目逐个作为动词运算元所见到的左参数;它的左参数是迭代对象的初始值,动词运算元所见到的右参数是迭代对象:
load 'dev/fold/foldr'
perm=: (1 0$0)&(]F..((,~ !)@[ $ ,@(0&,.@:+&1@] {"(_ 1) \:"1@(=/~)@i.@[)))@:(>:@i.) : [:
如果不采用连词F..
,可以基于一元副词/
“插入”,自行实现正向折叠算子:
foldl=: {{m"_ ` (v&:>/@,&(<m)@(<"_1@|.)) @. (>&0@#)}}
perm=: (1 0$0)foldl((,~ !)@[ $ ,@(0&,.@:+&1@] {"(_ 1) \:"1@(=/~)@i.@[))@:(>:@i.) : [:
在J语言中,提供了二元副词;.±3
“子阵列”(subarrays),它是;.
“剪切”(cut)的三种形式之一,也被称为密铺(tessellate)或镶嵌(tile)。x(u;._3)y
应用动词u
于由x
指定的y
的有相同形状的每个正规镶嵌之上。;.3
与之类似,但不丢弃结果中不完整的镶嵌。
现代APL所使用的二元算子⌺
“模板”(stencil),在边缘的处理上不同于J语言的镶嵌,它要求镶嵌子阵列每个轴的中心,在长度为奇数时是y
的元素,在长度为偶数时在其元素之间,并且用填充(fill)元素填满超出的部份,它的缺省移动步长是1
。下面在;._3
的基础上,利于二元动词{.
“采取”(take)的填充特性,定义一个stencil
实现:
stencil=: {{
p=. 1&,:`|.@.(>&1@#@$) n
r=. <.-:<:{: p
t=. -r+s=. r+$y
p u;._3 t&{.s&{. y}}
这个定义只提供一个右名词运算元n
,它是镶嵌子阵列的规定矩阵,不提供同每个子阵列对应的诸轴填充数目作为左名词运算,APL⌺
算子提供它,意图在需要时籍此移除填充。这里的镶嵌规定矩阵定义,其第1行是子阵列的每轴长度,第2行是每轴的移动步长,这个行次序与;.±3
的规定相反。这里的p
是给;._3
的镶嵌规定矩阵,r
是每轴在头部和尾部的填充数量,s
是正值控制尾部填充,t
是负值控制头部填充。在下面的简单用例中,数值阵列的填充元素是0
:
]d=: 4 4 $ >: i. 9
1 2 3 4
5 6 7 8
9 1 2 3
4 5 6 7
< stencil 2 3 d
┌─────┬─────┬─────┬─────┐
│0 1 2│1 2 3│2 3 4│3 4 0│
│0 5 6│5 6 7│6 7 8│7 8 0│
├─────┼─────┼─────┼─────┤
│0 5 6│5 6 7│6 7 8│7 8 0│
│0 9 1│9 1 2│1 2 3│2 3 0│
├─────┼─────┼─────┼─────┤
│0 9 1│9 1 2│1 2 3│2 3 0│
│0 4 5│4 5 6│5 6 7│6 7 0│
└─────┴─────┴─────┴─────┘
下面实现康威生命游戏,它是基于Moore邻域的一种细胞自动机[83]:
life=: {{3=s-y*.4=s=. (+/@,) stencil 3 3 ] y}}
glider=: ".;._2 noun define
0 0 1 0 0
1 0 1 0 0
0 1 1 0 0
0 0 0 0 0
0 0 0 0 0
)
life glider
0 1 0 0 0
0 0 1 1 0
0 1 1 0 0
0 0 0 0 0
0 0 0 0 0
{&('.',u:16b2596) &.> (i.8) {{life ^:x ] y}} &.> <glider
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│..▖..│.▖...│..▖..│.....│.....│.....│.....│.....│
│▖.▖..│..▖▖.│...▖.│.▖.▖.│...▖.│..▖..│...▖.│.....│
│.▖▖..│.▖▖..│.▖▖▖.│..▖▖.│.▖.▖.│...▖▖│....▖│..▖.▖│
│.....│.....│.....│..▖..│..▖▖.│..▖▖.│..▖▖▖│...▖▖│
│.....│.....│.....│.....│.....│.....│.....│...▖.│
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
这里的实现算法,将生命游戏规则合并入一个单一表达式中,它由Arthur Whitney提出[84]。算法的第一步骤用+/@,
合计每个格点及其周围格点中1
数目,并将它局部保存在s
中。第二步骤将为前面的结果与4
做相等比较,再与当前状态y
做逻辑与运算,只有在当前格点为1
,而且周围格点合计为3
的情况下,当前格点得到真值1
。第三步骤用s
减去前面的结果真值,再与3
做相等比较。在这个最终结果中,当前格点得到真值1
有两类情况:⑴它在s
中的值为4
,并减去了真值1
,这就是第二步骤运算所选定的情况;⑵它在s
中的值为3
,并减去第二步骤运算在这种情况下必然得出的假值0
,这又可细分为两种情况:当前格点为0
,而且周围格点合计为3
;或者当前格点为1
,而且周围格点合计为2
。
这个算法可以写为隐式定义形式:
life=: (=&3@] +. (*. =&4)) (+/@,) stencil 3 3 : [:
在J语言中,实现以递推关系定义的序列,避免出现的时间复杂度,可以采用连词F:.
“多结果正向折叠”(fold multiple forward),使用它需要事先安装插件dev/fold
[82]。这个连词所形成的动词,不输出左参数的初始值,如果提供的右参数为空值,它的输出为空值,否则它输出每次迭代的结果。上例中的迭代算式可以改写为:
load 'dev/fold/foldr'
{&('.',u:16b2588) &.> (< , <F:.(life@])&(7$0)) glider
如果不采用连词F:.
,可以自行实现输出中间结果的正向折叠算子:
foldlist=: {{
if. 1 > # y do. '' return. end.
r=. '' [ a=. x
for_i. y do. r=. r , u a=. i v a end.
r }}
{&('.',u:16b2588) &.> (< , <foldlist(life@])&(7$0)) glider
在这里的foldlist
中,将r=. '' [ a=. x
改为r=. u a=. x
,则(< , <foldlist(life@])&(7$0))
可改为<foldlist(life@])&(7$0)
。
在J语言中,提供了二元副词/.
“键分组”(key),它按左参数中的唯一键,对右参数进行分组(group)或称为分区(partition),并将所修饰动词应用到这些分组之上。分组次序同于一元动词~.
“唯一值”处理结果的次序,它除去匹配前面出现过的项目的任何项目。下面在它的基础之上,定义对应现代APL使用的二元算子⌸
“键分组”(key)的一个副词,它将所修饰动词应用在唯一键和相应的分组二者之上,它还具有一元和二元两种形式[85]:
key=: {{
(<"0 ~. y) u (y </. i.#y)
:
(<"0 ~. x) u (x </. y)}}
它的二元形式的左参数含有唯一键,而右参数是要分组的数据;它的一元形式的右参数含有唯一键,所分组的是右参数诸项目的索引值。下面是这个键分组副词所适用的动词的简单示例:
x=: 'Mississippi'
[ key x
┌─┬─┬─┬─┐
│M│i│s│p│
└─┴─┴─┴─┘
] key x
┌─┬────────┬───────┬───┐
│0│1 4 7 10│2 3 5 6│8 9│
└─┴────────┴───────┴───┘
,. key x
┌─┬────────┐
│M│0 │
├─┼────────┤
│i│1 4 7 10│
├─┼────────┤
│s│2 3 5 6 │
├─┼────────┤
│p│8 9 │
└─┴────────┘
([ ,. #&.>@]) key x
┌─┬─┐
│M│1│
├─┼─┤
│i│4│
├─┼─┤
│s│4│
├─┼─┤
│p│2│
└─┴─┘
下面的例子通过键分组来找到一个单词列表中的易位词:
]a=: <;._1 ' pats spat teas sate taps etas past seat eats tase star east seta'
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│pats│spat│teas│sate│taps│etas│past│seat│eats│tase│star│east│seta│
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
/:~ &.> 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语言中,提供了二元动词I.
“区间索引”,它左参数x
必须是有次序的,从而定义了1+#x
个区间,除了最后一个之外的每个区间,都含有并结束于x
的一个项目,而最后一个结束于正无穷,第一个开始于负无穷;它应用于右参数y
,给出y
所位于的区间的索引。
下面的例子产生符合指定离散概率分布的随机数列表,这里定义了动词ran
,它依据左参数x
给出的正实数向量中的这些权重,从i.#x
中选取出由右参数y
指定个数的随机数列表[86]:
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
这里首先通过?@$&0
,生成指定数目的在区间(0,1)
中的随机浮点数,它也可以写为等价的?@($ 0:)
。然后在叉子+/\ % +/
中,使用一元副词\
“前缀”修饰动词+/
,从而计算权重向量的前缀和,再用前缀和除以总和得出累积分布函数。最后通过区间索引,在有随机浮点数落入特定区间的时候,生成这个区间对应的随机整数。
动词ran
的表达式是个二层二元钩子,外层钩子的第一步运算应用到右参数上,它的第二步运算即内层钩子,整体修饰了二元副词~
“被动”而对换了两个参数的位置。内层钩子的第一步运算+/\ % +/
所应用的右参数实际上是外层钩子的左参数,它的第二步运算I.
修饰了~
,从而将它所面对的内层钩子的左右两参数,再次对换回到外层钩子即整体表达式原先的位置上。
下面是将区间索引和键分组结合起来的例子,演示了林德伯格-莱维中心极限定理[87]:
histogram =: {{
{&('.',u:16b258c) @ ((|.i.x)&(</)) @ (>.@*&x) @ (% >./) y}}
summary=: {{ l=. 0 [ r=. 1
-&1@#/.~ @ ((i.x)&,) @ ((l+(}.i.x)*%&x(r-l))&I.) y}}
sampleMean=: {{%&m @ (+/) @ (m&, $ v@*&m) y}}
24&histogram @ (80&summary) @ (10 sampleMean (?@$&0)) 1e6
......................................▌▌▌▌......................................
.....................................▌▌▌▌▌▌.....................................
....................................▌▌▌▌▌▌▌▌....................................
....................................▌▌▌▌▌▌▌▌....................................
...................................▌▌▌▌▌▌▌▌▌▌...................................
..................................▌▌▌▌▌▌▌▌▌▌▌▌..................................
..................................▌▌▌▌▌▌▌▌▌▌▌▌..................................
.................................▌▌▌▌▌▌▌▌▌▌▌▌▌▌.................................
.................................▌▌▌▌▌▌▌▌▌▌▌▌▌▌.................................
................................▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌................................
................................▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌................................
...............................▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌...............................
...............................▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌...............................
..............................▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌..............................
..............................▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌..............................
.............................▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌.............................
............................▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌............................
............................▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌............................
...........................▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌...........................
..........................▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌..........................
.........................▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌.........................
........................▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌........................
......................▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌......................
.........▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌........
lineChart=: {{
s=. (|.i.x)&(</) @ (>.@*&x) @ (% >./)
t=. >&0 @ (2&(-/\)) @ (2&(-~/\))"1 @ ,.&1 @ (1&,.)
{&('.',u:16b2588) @ (2&(-~/\)@(0&,) +. t) @ s y}}
24&lineChart @ (+/\) @ (80&summary) @ (10 sampleMean (?@$&0)) 1e6
....................................................████████████████████████████
..................................................██............................
................................................██..............................
...............................................█................................
..............................................█.................................
............................................██..................................
............................................█...................................
...........................................█....................................
..........................................█.....................................
.........................................█......................................
........................................█.......................................
.......................................█........................................
.......................................█........................................
......................................█.........................................
.....................................█..........................................
....................................█...........................................
...................................█............................................
...................................█............................................
.................................██.............................................
................................█...............................................
...............................█................................................
.............................██.................................................
...........................██...................................................
.........██████████████████.....................................................
这里定义的summary
局部赋值了两个局部变量l
和r
,可以将这两个局部赋值去掉,并将这两个局部变量的出现替代为运算元m
和n
,如此对它的调用将变成(80&(0 summary 1))
这样的形式。接下的代码结合前面两个例子,采用了不同于上例连续型均匀分布的其他分布作为独立同分布,和不同的样本平均:
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
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 ;< 'rome';'madrid'
sqlinsert__db 's' ; cls ;< dat NB. 将数据插入表格s
0
3 sqltail__db 's' NB. 返回最后3个格式化记录
┌───┬─────┬──────┬──────┐
│sid│name │status│city │
├───┼─────┼──────┼──────┤
│s5 │adams│30 │athens│
│s6 │brown│40 │rome │
│s7 │eaton│10 │madrid│
└───┴─────┴──────┴──────┘
sqlclose__db '' NB. 关闭数据库
1
J语言采用命名语境实现类,采用编号语境实现对象,下面示例建立字典类:
cocurrent 'Dict'
create=: {{o [ DEFAULT__o=: 0$0 [ o=. conew 'Dict'}}
get=: {{". 'ITEM_',y}}
set=: {{
('ITEM_',y)=: DEFAULT
:
('ITEM_',y)=: x}}
del=: {{erase 'ITEM_',y}}
pop=: {{r [ del y [ r=. get y}}
default=: {{DEFAULT=: y}}
filt=: {~ I.@:({.@('ITEM_'&E.)@>)
len=: {{# filt namelist 0}}
list=: {{5&}.&.> filt namelist 0}}
in=: {{+/@:(-:&y@>) list ''}}
clear=: {{#@,@:(erase @>) filt namelist 0}}
copy=: {{o=. conew 'Dict'
o [ ".@(,&'__o=:',(5!:5)@<)&.> 'DEFAULT';filt namelist 0}}
destroy=: codestroy
cocurrent 'base'
在家目录中建立一个dict.ijs
文件并录入上述代码,接着以如下代码建立字典对象并对其进行检视和简单操作:
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│
└──┴──┴──┘
参见
引用
外部連結
Wikiwand - on
Seamless Wikipedia browsing. On steroids.
Remove ads