如何使kdb+/q代码更加简短?

2022-05-25 00:00:00 拆分 代码 记录 方案 简短
本文编译自https://qriouskdb.wordpress.com/2020/10/10/kdb-golf/,作者为Cillian Reilly。

Code Golf(代码高尔夫)是一种计算机编程竞赛,在该竞赛中,参与者努力用尽可能短的源代码实现特定算法。作为一种紧凑简洁的语言,kdb+/q非常适合于此。通常,kdb+有可能在一行代码中实现一个算法,而其他语言根本无法实现。下面给出几个code golf的例子。

1、避免使用括号
在下面的例子中,可以看到避免使用方括号可以使代码更简短:
q)x:til 10
q)(count x)>
1b
q)count[x]>
1b
q)<count x
1b
q)k)<#x
1b
q)count each("(count x)>0";"count[x]>0";"0<count x")
11 10 9 4

2、恰当使用amend 
修改(amend),也可以使代码更加简短:
q)x:2;type x:x,()
7h
q)x:2;typex,:()
7h
q)count each("x:x,()";"x,:()")
65

3、用乘法代替除法
由于除法不是可交换的,因此%的参数顺序很重要,除以2将被强制写入x%2。但是,如果需要计算x,则必须使用方括号,例如count [x]%2。并不是那么快。除以2等于乘以0.5,并且乘法是可交换的。
q)x:til 10
q)(count x)%2
5f
q).5*count x //丢掉0.5开头的0很重要
5f
q)k).5*#x
5f

4、避免使用关键字
即使不在做code golfing,也要尽可能地避免使用关键字。
q)reciprocal 10
.1
q)1%10
.1

5、使用投影(projection)
在kdb+,投影是一种通过预先固定一定数量的输入参数,来减少函数输入参数数量的技巧。这里要注意的是,我们可以即时创建投影。
q)dbl:2*        //dbl是一个投影,2是固定参数
q)dbl 4 6 18
8 12 36
q)f:{x+3*y}
q)f[1;2 3 4]
7 10 13
q)f[1]2 3 4 //省略; 更简短
7 10 13

6、用take拆分数组
拆分list,可以使用cut,下面的例子使用了上面的一些技巧:
q)x:til 10
q)(0,floor .5*count x)_x
0 1 2 3 4
5 6 7 8 9
q)k)(0,_.5*#x)_x
0 1 2 3 4
5 6 7 8 9
还可以使用take,代码将更简洁:
q)2 0N#x
0 1 2 3 4
5 6 7 8 9
q)count each("(0,floor .5*count x)_x";"(0,_.5*#x)_x";"2 0N#x")
22 12 6

7、用except拆分表格
用except拆分表格,也可以使代码简短。假设你有一个包含许多记录的trade表,每条记录都是买入或卖出,你想将这个表拆成两个子表,一个表只包含买入的记录,另一个只包含卖出的记录。
q)show trade:([]time:.z.p;sym:10?`AAPL`GOOG`IPM`JPM`GE;price:10?100.0;side:10?`B`S)
q)2#trade
time sym price side
----------------------------------------------------------
2021.01.03D08:15:36.712108000 GOOG 49.31835 B
2021.01.03D08:15:36.712108000 GOOG 19.59907 S

你可能会说这个很简单啊,可以通过下面两个sql语句实现:

q)buy:select from trade where side=`B
q)sell:select from trade where side=`S
也许你还会说,我还可以通过一个sql语句实现:
q)buy:delete from trade where trade in sell:select from trade where side=`S

上述方案都是可行的,但是还有更好的方式吗?我个人更喜欢使用except:
q)buy:trade except sell:select from trade where side=`S

这种方式更加简洁明了。上述几种方式,那种更加高效呢?
q)\ts:10000 buy:select from trade where side=`B;sell:select from trade where side=`S
35 1776
q)\ts:10000 buy:delete from trade where trade in sell:select from trade where side=`S
89 2192
q)\ts:10000buy:trade except sell:select from trade where side=`S
84 1328
因此种方案的运行速度快,但使用的内存多;第二种方案是糟糕的,速度慢,且使用的内存与种方案相当。excetp方案所用的时间在三种方案中处于中间水平,但是使用的内存少(选用哪种方案,取决于个人的风格,但我会坚持选择方案三,因为它更短!)。
如果我们要筛选或删除仅某些列匹配的记录,则必须使用q-sql,无法使用except。例如,假如想删除卖出记录中所有与买入记录的时间和sym相同的记录:
q)delete from sell where([]time;sym)in select time,sym from buy
q)delete from sell where([]time;sym)in`time`sym#buy
 
8、跳出常规思维,灵活运用所学知识
假如我们要将+号替换成1,-号替换成-1,有如下方式:
q){?["+"=x;1;-1]}"++--+"    // Vector conditional
1 1 -1 -1 1
q){("+-"!1 -1)x}"++--+" // Dictionary map
1 1 -1 -1 1
q){-1 1"+"=x}"++--+" // Conditional indexing
1 1 -1 -1 1
可以看到采用conditional indexing(条件索引)方式为简洁。我们解析一下这段代码,{-1 1"+"=x}"++--+"等同于-1 1 "+"="++--+" ,"+"="++--+"的结果为11001b,-1 1 (11001b)表示分别取数组(-1;1)的第1、1、0、0、1个元素,即(1;1;-1;-1;1)。
 
本文只是举了简单的几个例子,简短的代码更具可读性,也更容易发现潜在的错误。
来源 https://www.modb.pro/db/212273

相关文章