如何在 kdb+/q 中高效筛选并展开 c4 嵌套表?
可以先筛选子表,再根据匹配数量生成父行索引。这种写法仍需遍历各个子表,但可以集中复制父列并完成最终合并,减少为每个父行构造临时结果的操作。能否明显缩短现有的 12 秒耗时,需要用实际数据测量。
先处理 enlist 带来的嵌套层级
给出的数据中,c4 的每个单元格都是 enlist table,因此筛选前应通过 first 取出完整子表。如果实际数据的单元格已经直接保存 table,就应省略这一步;对 table 使用 first 会取出第一行。
只展开 c5 属于 `x`a`b`m 的记录,没有匹配记录的父行不进入结果。
按匹配数量批量展开父行
下面的函数适用于示例中的非空父表,要求各个子表具有一致的列结构和类型,且子表列名与保留的父列名不重叠。
flattenC4:{[t]
/ Each c4 cell contains one enlisted table
children:{[v]
s:first v;
select from s where c5 in `x`a`b`m
} each t`c4;
/ Repeat each parent index by its matching child count
n:count each children;
idx:raze n#'til count t;
/ Expand parent columns, then combine column dictionaries
parents:(delete c4 from t)[idx];
flip (flip parents),flip raze children
};
result:flattenC4 tstT1;
这里的 #' 对两侧对应的元素分别执行 Take:左侧是匹配数量,右侧是父行编号。对于给出的数据,逻辑上的中间结果为:
/ Matching child counts
1 2 0 1
/ Expanded parent indices
0 1 1 3
因此,AAPL 保留一行,GOOG 复制为两行,MSFT 不保留,AMZN 保留一行。展开后的子记录依次为 x、a、b、m,顺序与父行索引一致。
raze children 把筛选后的子表纵向合并。raze 每次折叠一层可连接的结构,其语义见 KX 的 raze 文档。最后通过 flip 将两侧转为列字典,合并后再转回表,得到 c1、c2、c3、c5、c6。
为什么值得与原写法比较
原写法对每个父行构造重复的父记录,将其与筛选后的子表合并,再汇总所有结果。上面的写法将这部分工作改为一次生成索引和一次父表取行。
子表仍需筛选。如果主要耗时来自大量小表的筛选,收益可能有限。仅凭问题中的耗时,无法确定真正的瓶颈,也不能保证某个固定加速倍数。
如果先展开全部子记录再筛选,可能生成大量最终不会保留的中间行。筛选命中率较低时,通常更值得尝试先筛选,再复制父列。
核对结果并测量耗时
可以用父表已有的时间戳构造预期结果,避免 .z.p 随运行时间变化造成误判:
expected:([]
c1:`AAPL`GOOG`GOOG`AMZN;
c2:101.25 2845.6 2845.6 178.9;
c3:tstT1[`c3][0 1 1 3];
c5:`x`a`b`m;
c6:10 1 2 7
);
result~expected
然后在同一份实际数据上比较两种实现,并将筛选、索引生成和合并全部计入耗时:
\ts result:flattenC4 tstT1
这些代码尚未在此处的 q 运行环境中实测。生产使用前,还应核对全部不匹配、空子表和空父表的处理;上面的函数以非空父表为前提。数据量很大时,children、索引和输出表也会占用内存,可以按父行分批处理,并保持批次顺序。
备注:内容仅供参考。
