接下来几章将进入Q语言的核心内容——函数、表、数据转换、查询和I/O等等, excited :)1. 函数说明在q中,函数是可以修改全局变量的,所以q并不是一个纯正的函数式语言。1. 函数定义使用花括号{和},函数的输入输出类型不用指定,甚至函数名都可以不用指定。如下是一个完整的函数定义:q) {[x] xx}调用函数时,参数用中括号包围起来,参数通过分号;分隔q){[x] xx}[3]9函数可以赋值给一个变量:q)f:{[x] xx}q)f[3]92. 函数标识和术语 函数的正式定义为{[p1;…;pn] e1; …; em},其中可选的p1;…;pn是正式的参数,e1; …; em是执行的表达式,虽然是从左向右写的,但是仍然是从右向左执行的。函数的参数个数被称为函数的valence, 最常见的函数是monadic(valence 1)和dyadic(valence 2)。一个niladic函数是指一个没有输入的函数,表示如下:f:{[] … }例子如下:q){[] 42} / pure function returns constant 4242q){[] aa} / impure function: references global a{[] aa}函数最大的参数个数(valence)为8,超过8个参数将会报错。如果参数较多的话,将参数打包成list或者dictionary输入函数。函数的输出值为函数最后一个表达式的结果:q){[x] xx}q){[x;y] a:xx; b:yy; r:a+b; r}Keep it short. 很多q语言中的函数都是紧凑并且模块化的,很多函数都是仅仅一行3. 函数应用函数的调用是严格的,意味着在参数替换之前,表达式就已经被执行了:q)f:{[x] xx}q)f[0N!3+1]416当提供的参数多于函数定义的参数时,会报’rank错误。q) {[x] xx}[3;4]‘rank4. 没有返回值的函数函数体最后只有一个分号;,返回::。q)fvoid:{[x] a set x;}q)fvoid 42q)a42注意在q中,分号;是分隔符而并不是终止符。5. 参数的并列写法类似于列表索引和字典取值,函数的参数也可以是并列写法:q){[x] 2*x} 4284q)f:{[x] x*x}q)f 5256. 函数名的应用当函数被赋值给一个全局变量时,可以通过symbol形式的函数名来调用:q)f:{x*x}q)f[5]25q)
f[5]25q)f 525q).my.name.space.f:{2*x}q)
.my.name.space.f[5]107. 隐含的参数当一个函数没有参数被明确定义时,三个隐含的参数x, y, z会被自动定义,下面的函数定义是等价的:{[x] xx}{xx}{[x;y] x+y}{x+y}三个参数x, y, z的调用是按照先后顺序的,意味着x总是第一个被调用,y第二个,z第三个。下面这个函数只有提供三个参数值时才会返回值q)g:{x+z} / likely meant x+y; requires 3 args in callq)g[1;2] / still waiting for 3rd arg – i.e., a projection{x+z}[1;2]q)g[1;2;3] / 2nd arg is required but ignored48. 匿名函数和lambda表达式一个没有函数名称的函数叫做匿名函数,匿名函数的常见的两个用处:定义在函数内的匿名函数f{[…] …; {…}[…]; …}函数容器q)powers:({1}; {x}; {xx}; {xxx})…q)selected:2q)powers[selected]{xx}9. 恒等函数::恒等函数::返回它的输入作为输出;裸的恒等函数不能使用并列的形式,它必须使用中括号调用。q)::[42]42q)::[a
bc]
ab
cq):: 42 / error’q)(::) 424210. 函数是数据类似于python中的函数是对象一样,在q语言中函数都是数据,可以作为输入和输出。q)apply:{x y}q)sq:{xx}q)apply[sq; 5]252. 通过名字调用常规的函数使用传值调用(call-by-value),意味着参数在被传递时是按值传递的,在这个过程中,原始值被拷贝了一份,以保证之前的原始数据不会被修改。但这样做的一个问题是,当输入参数的size非常大时,拷贝是被禁止的。这个时候就有一个新的方法:call-by-name,在这种情况下变量的名字被传递而不是变量的值。Call-by-name没有特别的语法。一个例子就是内置函数get,传递全局变量的名字,返回对应的值q)a:42q)get a42另一个例子是函数set,是给全局变量赋值q)
a set 43aq)a433. 局部和全局变量1. 定义局部和全局变量在函数体内使用:定义的变量被称为局部变量。函数体内最大的局部变量个数为24个。q)f:{a:42; a+x}q语言不遵循词法作用域规则,意味着在函数体内的函数并没有获得上层函数体内变量的权限,例如下例中, helper函数并没有获取局部变量a的值的权限q)f:{[p1] a:42; helper:{[p2] a*p2}; helper p1}然而,你必须对该局部函数声明一个额外的参数来传递局部变量q)f:{[p1] a:42; helper:{[a; p2] a*p2}[a;]; helper p1}q)f 5210在所有函数定义以外的变量被称为全局变量。2. 在函数内对全局变量赋值当函数体内没有同名的局部变量时,可以使用双冒号::来对全局变量进行赋值;
q)b:6q)f:{b::7; xb}q)f[6]42q)b7而当函数体内有同名的局部变量时,双冒号符操作的是局部变量,并不是全局变量q)b:6q)f:{b:42; b::x; b}q)f[98]98q)b6相比::,更推荐使用set来进行全局变量修改, 这样就不会有局部变量名冲突了。q)a:42q)f:{a:98.6;
a set x}q)f 43aq)a434. 投影投影是指只指定函数的一部分参数,其结果会是其余参数的函数。1. 函数投影一个函数投影的例子为:q)add:{x+y}q)add[42;]{x+y}[42;]q)add[42;][3]45q)add3:{x+y+z}q)add3[2][3][4]9上式可以理解为add3作用于参数2,返回一个函数;该函数作用于参数3,返回一个函数;最后作用于4,返回结果9。上式等价于add3[2;3;4]。No second look. 被赋值的函数投影变量不会随着原函数的改变而改变q)f:{x-y}q)g:f[42;]q)g{x-y}[42;]q)g[6]36q)f:{x+y}q)g{x-y}[42;]q)g[6]362. 运算符投影 当使用运算符中缀形式的时候,一个q的运算符可以通过固定其左运算元来进行投影,这时需要括号。q)(7*) 642由于任何操作符都是一个函数,当然可以使用其前缀形式进行投影:q)-[;42] 9856上面两个公式中的空格符都不是必须的:q)(7*)642q)-[;42]98563. 多维投影当函数有多个参数的时候(valence > 2),函数可以有多个投影,例如下例q){x+y+z}[1;;3]{x+y+z}[1;;3]q){x+y+z}[1;;3] 265. Atomic函数一个Atomic函数是指该函数直接作用于一个q数据结构中的atom数据。1. Monadic Atomic函数和map回忆一下,monadic函数是指只作用于一个参数的函数,如下是一个monadic atomic函数的例子,该函数作用于一个字典q)neg 10-10q)neg 10 20 30-10 -20 -30q)neg (10 20 30; 40 50)-10 -20 -30-40 -50q)neg
ab
c!10 20 30a| -10b| -20c| -30q)neg a
bc!(10 20; 30 40 50; 60)a| -10 -20b| -30 -40 -50c| -602. Dyadic atomic函数和zipDyadic函数是指作用于两个参数的函数。若将dyadic函数的非atomic部分固定下来(可以看成一个函数的投影),那么dyadic函数就变为monadic函数。如下例中的dyadic操作符?。q)10 20 30?100q)10 20 30?10 20 30 40 500 1 2 3 3q)(enlist 10)?100q)10 20?100q)10 20 30 40 50?100在算术中,比较和关系运算符都是atomic的,在这种应用下会有四种情况:atom和atomatom和listlist和atomlist和list在最后一种情况下,两个list元素的长度必须相等q)1+1011q)1+10 20 3011 21 31q)1 2 3+1011 12 13q)1 2 3+10 20 3011 22 33这个功能相当于传统语言中的zip3. 构造atomic函数简单想一想就可以明白,由atomic函数构成的函数仍然是atomic的,因此构造一个atomic函数的方法就是去包含内置的atomic函数。构造Monadic atomic函数:q)f:{(x*x)+(2*x)-1}q)f 0-1q)f til 10-1 2 7 14 23 34 47 62 79 98构造Dyadic atomic函数:q)pyth:{sqrt (x*x)+y*y}q)pyth[1; 1]1.414214q)pyth[1; 1 2 3]1.414214 2.236068 3.162278q)pyth[1 2 3; 1 2 3]1.414214 2.828427 4.2426416. 副词副词是高阶的函数,用以改变函数在列表上的应用方式。这个术语来自于将q操作符当做动词。Proficiency in the use of adverbs is one skill that separates q pretenders from q contenders. :)1. Monadic each合并函数例如count只会作用在嵌套列表的最高层级:q)count 10 20 303q)count (10 20 30; 40 50)2如果我们想知道嵌套列表中每个元素的长度,这个时候副词each就派上用场了,它使得monadic函数能够作用于列表的每个元素而不是整个列表,each有两种使用方法:中缀形式:each紧跟在函数的后面q) count each (10 20 30; 40 50)3 2前缀形式q) each[count] (10 20 30; 40 50)3 2对于层数较深的嵌套矩阵,可能需要对each进行迭代q)(count each) each ((1 2 3; 3 4); (100 200; 300 400 500))3 22 3q)each[each[count]] ((1 2 3; 3 4); (100 200; 300 400 500))3 22 3一些例子:q)reverse "live""evil"q)reverse ("life"; "the"; "universe"; "and"; "everything")q)reverse each ("life"; "the"; "universe"; "and"; "everything")当想要将一个长度为n的向量转换为一个大小为n*1的矩阵时,可以使用enlist each来实现,但flip enlist在大列表上执行更快。q)enlist each 1001 1002 1004 10031001100210041003q)flip enlist 1001 1002 1004 100310011002100410032. each-both'副词each-both符号'作用在一个dyadic函数上,使得函数能够成对地作用在对应的列表元素上,符号'读作"zip"。q)("abc"; "uv"),'("de"; "xyz")"abcde""uvxyz"q)1,'10 20 301 101 201 30q)1 2 3,'101 102 103 10q)2#'("abcde"; "fgh"; "ijklm")"ab""fg""ij"当熟练的时候,可以使用each-both的前缀形式:q),'[("abc"; "uv"); ("de"; "xyz")]"abcde""uvxyz"一个table的例子:q)t1:([] c1:1 2 3)q)t2:([] c2:
ab
c)q)t1,’t2c1 c2—–1 a2 b3 c3. each-left :each-left操作符作用于一个dyadic函数,使第一个参数下的每一项都应用于第二个参数:(“abc”; “de”; enlist “f”) ,: “>““abc>““de>““f>“4. each-right /:each-right 作用于一个dyadic函数,使第一项作用于第二个参数的每一项:q)"</”,/:(“abc”;“de”;enlist “f”)"</abc”"</de”"</f"5. Cross Product叉积(Cross Product)成对地作用于左侧的每一项和右侧的每一项。 如果我们对join执行each-right和each-left操作,再内置函数raze对得到的嵌套矩阵夷平,就可以得到我们想要的结果q)1 2 3,/::10 201 10 1 202 10 2 203 10 3 20q)raze 1 2 3,/::10 201 101 202 102 203 103 20上述操作还是较为复杂,我们可以通过内置函数cross来得到上述结果q)1 2 3 cross 10 201 101 202 102 203 103 20可以注意到,若我们组合each-left和each-right时,我们便可以得到上述结果的转置q)raze 1 2 3,:/:10 201 102 103 101 202 203 206. Over /Over操作符/是一个提供递归机制的高阶函数。它最简单的形式是修改一个dyadic函数,使其在一个list上累积函数作用的结果q)0 +/ 1 2 3 4 5 6 7 8 9 1055注意: 在运算符和/之间不能存在空格,因为/可以被用作注释。运算符左运算元为累积计算的初始值,右运算元为待累积计算的列表。但我们也可以省略左运算元(即初始值),这个时候需要我们对表达式做一些变换,被作用函数和Over运算符/用括号包住,这时右边列表的第一个元素就是初始值。q)(+/) 1 2 3 4 5 6 7 8 9 1055上面的括号是必需项,被修改的函数实际上是一个monadic函数。一些有用的over形式:q)(/) 1 2 3 4 5 6 7 8 9 10 / product3628800q)(|/) 7 8 4 3 10 2 1 9 5 6 / maximum10q)(&/) 7 8 4 3 10 2 1 9 5 6 / minimum1使用,/可以高效地移除列表的顶层嵌套,其对应的内置函数为raze。q)(,/)((1 2 3; 4 5); (100 200; 300 400 500))1 2 34 5100 200300 400 500q)raze ((1 2 3; 4 5); (100 200; 300 400 500))1 2 34 5100 200300 400 5007. Iteration/的另一种用法是作为循环代码的等价形式。在这个版本下,左运算元表示了循环的次数,右运算元为初始值。例如,计算Fibonacci数列:q)fib:{x, sum -2#x}q)10 fib/ 1 11 1 2 3 5 8 13 21 34 55 89 144q)fib/[10;1 1]1 1 2 3 5 8 13 21 34 55 89 144另一个版本的/控制了循环的进行直至收敛,或者检测到一个环的存在。下面以牛顿法为例介绍这种循环的使用,我们使用牛顿法来寻找函数{-2+xx}的根:q)f:{-2+xx}q)secant:{[f;x;e] (f[x+e]-f x-e)%2e}q){x-f[x]%secant[f; x; 1e-6]}/[1.5]1.414214q语言会判断当前的输出值与之前的输出值之间的大小,如果两个值之间相差在一定的tolerance以内,则认为算法收敛并且迭代完成;否则继续执行循环任务。此外,q语言通过每次比较运算结果和初始值是否match()来判断当前程序是否存在环(loop),如果存在环,程序则终止:q)newtcycle:{[xn] xn-((xnxnxn)+(-2xn)+2)%-2+3xnxn}q)newtcycle/[0.0]1f如果运算的结果与初始值相等,但是类型不同(not match),程序则不会停止;例如上例中如果提供初始值0,则程序会一直运行下去。运算符/的最后一种重载用法,等价于使用while循环,它提供了一个判断条件,若每次的运算结果满足条件,则继续执行;否则,终止计算q)fib:{x,sum -2#x}q)fib/[{1000>last x}; 1 1]1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 15978. Scan \Scan操作符\同样是一个高阶函数,其作用与操作符/一样,与其不同的是,Scan\会返回中间的计算结果。可以把Scan\当做是Over/的"running"版本。q)0+\1 2 3 4 5 6 7 8 9 101 3 6 10 15 21 28 36 45 55q)()1 2 3 4 5 6 7 8 9 10q)(|)7 8 4 3 10 2 1 9 5 67 8 8 8 10 10 10 10 10 10q)(&)7 8 4 3 10 2 1 9 5 67 7 4 3 3 2 1 1 1 1q)100 f\1 2 3 4 5 6 7 8 9 10q)(f)1 2 3 4 5 6 7 8 9 10所有over操作符/的用法都适用于Scan操作符\,使用Scan操作符的好处是可以看到函数中间过程的运行结果。q)fib:{x, sum -2#x}q)fib[{1000>last x}; 1 1]9. each-previous ‘:each-previous操作符对列表的每项和其前一项执行dyadic函数操作。当执行Dyadic操作时,当前项是Dyadic函数的左运算元,前一项是Dyadic函数的右运算元。由于列表中第一项没有前项,所以我们必须在运算符:‘左运算元的位置提供初始值,如下例q)100 -’: 100 99 101 102 1010 -1 2 1 -1与其它副词一样,each-previous :‘也有一个monadic函数的形式。但在这种形式下,列表中的第一个元素不会被当做初始的前项,相反,它直接返回该项。q)(-’:)100 99 101 102 101100 -1 2 1 -1q)deltas 100 99 101 102 101100 -1 2 1 -1q)(%’:)100 99 101 102 101100 0.98999999999999999 1.0202020202020201 1.0099009900990099 0.99019607843137258q)ratios 100 99 101 102 101100 0.98999999999999999 1.0202020202020201 1.0099009900990099 0.99019607843137258保留第一项的动机是,可以通过保留的第一项来恢复整个listq)sums deltas 100 99 101 102 101100 99 101 102 101q)deltas sums 100 99 101 102 101100 99 101 102 101当我们需要返回的结果中都是变化值时,可以通过如下方法得到q)deltas0:{first[x] -’: x}q)deltas0 100 99 101 102 1010 -1 2 1 -1一个使用each-previous的非常有用的工具是使用判断连续项是否是match的。实际中,我们经常会关注两个连续项不同的情况,这种情况下使用内置函数differq)(’:) 1 1 1 2 2 3 4 5 5 5 6 6011010001101bq)not (’:) 1 1 1 2 2 3 4 5 5 5 6 6100101110010bq)differ 1 1 1 2 2 3 4 5 5 5 6 6100101110010b可以对differ的结果使用where和cut来分隔列表q)L:1 1 1 2 2 3 4 5 5 5 6 6q)where differ L0 3 5 6 7 10q)(where differ L) cut L1 1 12 2,3,45 5 56 6下面我们来做一些q的练习q)runs:(where differ L) cut L / store runsq)ct:count each runs / store count of each runq)runs where ct=max ct / find the runs of maximum length1 1 15 5 5用一行代码来实现上面的代码q) runs where ct=max ct:count each runs:(where differ L) cut L同样,我们可以上述技术来找到上升和下降子序列q)L:9 8 7 11 10 12 13q)(where -0W>’:L) cut L9 8 711 10,12,13q)(where 0W<’:L) cut L,9,87 1110 12 138. 一般应用Thorough understanding of the general application is another test that separates the q pretenders from the contenders.1. 动词 @q语言的基础操作包括:从list中通过索引取值,在字典中通过键取值或执行一个monadic函数。高阶函数@是q语言中基础操作的真正形式,它将一个monadic映射(可能是索引取值,字典取值或者monadic函数)作用于一个元素之上。与所有的内置函数一样,它同样有中缀和前缀的表示形式q)10 20 30 40@120q)L:10 20 30 40q)L@120q)@[L; 1]20q)count@L4q)@[count; L]4q){x*x}@L100 300 900 1600q)d:a
bc!10 20 30q)d@
a10q)@[d;b]20当@与niladic函数应用时,可以使用空元素::来代表空值q)f:{6*7}q)f[]42q)@[f; ::]42q)f@(::)42q)f@43422. 动词.q语言中,多元映射包括了:深度索引一个列表,从一个字典中取一个被嵌套的值和执行一个带有多个参数的函数等。高阶函数.是q语言中多元应用的真正形式。它将多元映射投影到多个参数上,并且可以被写为中缀和前缀形式。.的右侧必须是一个listq)L:(10 20 30; 40 50)q)L[1][0]40q)L[1; 0]40q)L . 1 040q)d:
ab
c!(10 20 30; 40 50; enlist 60)q)d[b][0]40q)d[
b; 0]40q)d . (b; 0)40q)g:{x+y}q)g[1; 2]3q)g . 1 23可以配合monadic函数使用.,其效果如下q)f:{x*x}q)f@525q)f . enlist 525q)f . enlist 1 2 31 4 9为了表示一个隐藏的索引,可以使用::来代替q)m:(1 2 3;4 5 6)q)m[0;]1 2 3q)m . (0; ::)1 2 3q)m . (::; 1)2 5对于一个niladic函数的.执行形式,需要使用::生成一个list。q)f:{6*7}q)f . enlist (::)42q)f . enlist 4242All data structures in q are composed from lists and dictionaries.一些很好的练习:q)L:10 20 30q)L . enlist 1_q)m:(10 20 30; 100 200 300)q)m . 0 1_q)ds:(
ab
c!10 20 30; x
y!100 200)q)ds . (0; b)_q)mix:(10 20 30;
ab
c!(1; 2; (300 400)))q)mix . (1; c; 1)_q)dc:
c1c2!(1 2 3;
ab
c)q)dc . (c2; 1)_q)t:([]c1:1 2 3;c2:
ab
c)q)t . (1; c2)_答案分别是20 20 20 400
b b3. 应用Monadic函数的@回忆@的一般操作:q)L:10 20 30 40 50q)@[L;1]20q)@[L;0 1]10 20现在除了取值外,我们同时应用一个函数:q)@[L;1;neg]10 -20 30 40 50q)@[L;0 2;neg]-10 20 -30 40 50注意到上述结果与正常在列表子集上的运算不同,正常只会返回在子集上运算的结果q)neg L@0 1-10 -20而这个提升的版本会返回修改后的整个列表。Monadic函数使用@的一般应用 的语法是@[L;I;f]其中L是列表,I为索引的容器。这个形式可以泛化到任何可以被视为映射的数据结构,例如给定一个字典和一个键值列表q)d:
ab
c!10 20 30q)ks:a
cq)@[d; ks; neg]a| -10b| 20c| -30上述操作都是在输入数据结构的拷贝上完成的。我们也可以通过pass-by-name的方法来进行in-place修改q)L:10 20 30 40q)@[L; 0; neg]-10 20 30 40q)L10 20 30 40q)@[L; 0 ; neg]
Lq)L-10 20 30 404. 应用Dyadic函数的@当Dyadic函数使用@时,需要提供一个额外的运算元,显然运算元要与子集的大小匹配。除了一种额外的情况,当运算元是atom时,会被自动拓展到与子集相同大小。q)L:10 20 30 40q)@[L; 0 1; +; 100 200]110 220 30 40q)@[L; 0 1; +; 100]110 120 30 40q)d:a
bc!10 20 30q)@[d;
ab; +; 100 200]a| 110b| 220c| 30q)@[d;
ab; +; 100]a| 110b| 120c| 30Dyadic函数使用@的一般应用 的语法是@[L;I;g;v]其中L和I与上小节定义一样,可以是任意能够被视为映射的数据结构;g是一个Dyadic函数;v是一个atom或者与I匹配的列表。列表赋值: 一个非常有用的Dyadic函数应用是使用赋值符:在子集上赋值q)L:10 20 30 40q)@[L; 0 2; :; 42 43]42 20 43 40与Monadic函数一样,in-place操作可以通过pass-by-name形式q)L:10 20 30 40q)@[
L; 0 2; :; 42 43]Lq)L42 20 43 405. 应用Monadic函数的.总结一下,@是作用在数据结构的顶层,而.则是深度索引。重新回顾一下.的前缀用法q)m:(10 20 30; 100 200 300)q).[m; 0 1]20q)d:
ab
c!(10 20 30; 40 50; enlist 60)q).[d; (a; 1)]20应用monadic函数的.形式:q).[m; 0 1; neg]10 -20 30100 200 300q).[d; (
a; 1); neg]a| 10 -20 30b| 40 50c| ,60同样,若想in-place修改,则使用pass-by-name形式。可以使用::来代替隐藏的索引q).[m; (0; ::); neg]-10 -20 -30100 200 300q)d:a
bc!(100 200 300; 400 500; enlist 600)q).[d; (
a; ::); neg]a| -100 -200 -300b| 400 500c| ,600q).[d; (::; 0); neg]a| -100 200 300b| -400 500c| ,-600应用monadic函数的.一般形式为:.[L; I; f]7. 应用Dyadic函数的.一般形式为.[L;I;g;v]其中g是Dyadic函数,v是atom或与I相对应的运算元q)m:(10 20 30; 100 200 300)q).[m; 0 1; +; 1]10 21 30100 200 300q).[m; (::; 1); +; 1 2]10 21 30100 202 300q)m10 20 30100 200 300q).[m; (::; 1); +; 1]
mq)m10 21 30100 200 300q).[m; (::; 1); :; 42]
mq)m10 42 30100 42 300q)d:a
bc!(100 200 300; 400 500; enlist 600)q).[d; (
a; 1); +; 1]q).[d; (a; ::); +; 1]q).[d; (::; 0); +; 1]q).[
d; (::; 0); :; 42]