Colf 题解
编辑Colf 是一个面向 AI prompt 的 code golf,大概就是利用最少的代码(在这里是最少的 token 数)来解题。
使用起来很简单,在左边的框中输入 prompt,生成代码,然后会自动运行是否 AC,如果 AC 则按照输入和输出 token 数计分。
小小尝试了一下,AC 完之后排名还挺前,所以写篇文章记录一下题解和心得。
题解
#1 LRU Cache
function lruops(
ops: [op: string, key: number, value?: number][],
capacity: number,
): number[]
code golf
function lruops(o,c){let m=new Map(),r=[];for(let [op,k,v]of o){if(op=="get"){if(m.has(k)){r.push(m.get(k)),m.delete(k),m.set(k,r[r.length-1])}else r.push(-1)}else{if(m.size==c)m.delete(m.keys().next().value);m.set(k,v)}}return r}
Input 33 tokens, Output 85 tokens, Total 118 tokens
上来先展示一下流氓解法:复制函数签名,加上 code golf。
#2 Topological Sort with Cycle Detection
function toposort(
n: number,
edges: [u: number, v: number][],
): number[]
Code golf choose the smallest index
function toposort(n, edges) {
const g = Array.from({ length: n }, () => []), inDeg = Array(n).fill(0);
edges.forEach(([u, v]) => (g[u].push(v), inDeg[v]++));
const q = [], res = [];
for (let i = 0; i < n; i++) if (!inDeg[i]) q.push(i);
while (q.length) {
const u = q.splice(q.indexOf(Math.min(...q)), 1)[0];
res.push(u);
for (const v of g[u]) {
if (--inDeg[v] === 0) q.push(v);
}
}
return res.length === n ? res : [];
}
Input 33 tokens, Output 159 tokens, Total 192 tokens
同样是流氓解法,但是有 case 过不了,再审题干,要选择更小的 index,所以补一句。
#3 Longest Common Subsequence
code golf
function lcs(a,b: string): string
function lcs(a,b){let dp=Array.from({length:a.length+1},()=>Array(b.length+1).fill(0));for(let i=1;i<=a.length;i++)for(let j=1;j<=b.length;j++)dp[i][j]=a[i-1]==b[j-1]?dp[i-1][j-1]+1:Math.max(dp[i-1][j],dp[i][j-1]);let res='';for(let i=a.length,j=b.length;i&&j;){if(a[i-1]==b[j-1]){res=a[i-1]+res;i--;j--;}else dp[i][j-1]>dp[i-1][j]?j--:i--;}return res;}
Input 12 tokens, Output 157 tokens, Total 169 tokens
同样的流氓解法,唯一区别是从函数签名偷了一点。
#4 Expression Evaluator
calc expr
round code golf
const calc=(e)=>Math.round(eval(e));
Input 6 tokens, Output 10 tokens, Total 16 tokens
还是一样的解法,复制函数签名,但是 AC 不过;以为是优先级的问题,又把优先级的条件复制进去,还是不行;看一下发现需要取整(round),加上关键词,完事。如果强调优先级,它会生成一个很复杂的递归去解释的逻辑。
#5 Single-Source Shortest Paths
code golf
function dijkstra(
n: number,
edges: [u: number, v: number, w: number][],
s: number,
): (number|null)[]
function dijkstra(n,e,s){let d=Array(n).fill(Infinity);d[s]=0;let q=new Set([s]);while(q.size){let u=[...q].reduce((a,b)=>d[a]<d[b]?a:b);q.delete(u);for(let[_,v,w]of e.filter(x=>x[0]==u)){if(d[u]+w<d[v]){d[v]=d[u]+w;q.add(v)}}}return d.map(x=>x===Infinity?null:x)}
Input 41 tokens, Output 108 tokens, Total 149 tokens
没啥特别,照样流氓解法。
#6 N-Queens
function nqueens(N: number):number
one-liner
const nqueens = N => (function solve(row, cols, diag1, diag2) { return row === N ? 1 : Array.from({ length: N }).reduce((count, _, col) => (cols[col] || diag1[row + col] || diag2[row - col + N - 1]) ? count : count + solve(row + 1, { ...cols, [col]: true }, { ...diag1, [row + col]: true }, { ...diag2, [row - col + N - 1]: true }), 0); })(0, {}, {}, {});
Input 13 tokens, Output 126 tokens, Total 139 tokens
启用了一个新的技巧 one-liner
,这个词只占 2 个 token,跟 code golf
一样,效果差不多(主要是 code golf
在这里不 work)。
#7 Range Sum with Updates
function segment(
nums: number[],
ops: [op: string, a: number, b: number][],
): number[]
set i v updates a[i] = v.
sum l r returns inclusive sum from l to r.
code golf
function segment(nums, ops) {
const a = [...nums];
return ops.map(([op, x, y]) => {
if (op === 'set') a[x] = y;
else if (op === 'sum') return a.slice(x, y + 1).reduce((s, v) => s + v, 0);
}).filter(v => v !== undefined);
}
Input 52 tokens, Output 84 tokens, Total 136 tokens
同样的技巧,唯一的缝补是 set 和 sum 的操作看起来不是很标准,塞进去之后就能 AC。
#8 JSON Diff
function jsondiff(
a: unknown,
b: unknown,
): string[]
Return a lexicographically sorted list of JSON paths where two JSON-serializable values differ. Paths use '.' for keys and '[i]' for array indices; the root is ''. always start with .
jsondiff({"x":1,"y":2}, {"x":1,"y":3}) // -> [".y"]
jsondiff({"k":[1,2]}, {"k":[1]}) // -> [".k[1]"]
function jsondiff(a, b) {
const diffs = [];
function compare(objA, objB, path) {
if (typeof objA !== typeof objB) {
diffs.push(path);
return;
}
if (typeof objA === 'object' && objA !== null && objB !== null) {
if (Array.isArray(objA) && Array.isArray(objB)) {
const maxLength = Math.max(objA.length, objB.length);
for (let i = 0; i < maxLength; i++) {
compare(objA[i], objB[i], `${path}[${i}]`);
}
} else {
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
const allKeys = new Set([...keysA, ...keysB]);
allKeys.forEach(key => {
compare(objA[key], objB[key], `${path}.${key}`);
});
}
} else if (objA !== objB) {
diffs.push(path);
}
}
compare(a, b, '');
return diffs.sort();
}
Input 108 tokens, Output 232 tokens, Total 340 tokens
答得最拉的一题,复制粘贴大法搞不定,而且总是卡那个数组下标不能 AC,需要手动改一下第二个例子,属于是 few-shot 的技巧。
#9 Sudoku Solver
function sudoku(grid: ('1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'|'.')[][]): ('1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9')[][]
function sudoku(grid) {
const isValid = (row, col, num) => {
for (let i = 0; i < 9; i++) {
if (grid[row][i] === num || grid[i][col] === num || grid[Math.floor(row / 3) * 3 + Math.floor(i / 3)][Math.floor(col / 3) * 3 + i % 3] === num) {
return false;
}
}
return true;
};
const solve = () => {
for (let row = 0; row < 9; row++) {
for (let col = 0; col < 9; col++) {
if (grid[row][col] === '.') {
for (let num = 1; num <= 9; num++) {
const strNum = num.toString();
if (isValid(row, col, strNum)) {
grid[row][col] = strNum;
if (solve()) {
return true;
}
grid[row][col] = '.';
}
}
return false;
}
}
}
return true;
};
solve();
return grid;
}
Input 63 tokens, Output 254 tokens, Total 317 tokens
这题也卡了很久,直接复制粘贴函数签名就能 AC,但是没有优化的思路。
#10 Regex Matcher
full matching code golf regex10(s,p)
function regex10(s,p){return new RegExp('^'+p+'$').test(s);}
Input 9 tokens, Output 18 tokens, Total 27 tokens
这题是我排名最前的,目前正好是第十。还是一样的套路,唯一难点是那个 full matching。
心得
先说解题的技巧。
基本盘就是复制粘贴函数签名,由于都是一些很常见的算法题,所以一般 LLM 都能一遍过;如果没过,通常是因为题干中有一些特殊的要求,这个时候回去读一遍题干,把这个特殊点找出来放进 prompt,例如 2、7、8、10 这几题。
然后就是怎么压榨输出的长度。因为打题用的 JavaScript,其实有挺多技巧/语法糖可以使用,例如箭头函数(arrow)、三元运算符(ternary)等等。这个在初期有比较大的进步,然后后来想到更加机智的一行流(one-liner),再按图索骥找到王炸 code golf
。
另外有个不太起眼的技巧就是,你需要探索出一种超短的实现方式,通常跟你一开始写的有天壤之比,例如 4 里使用的 eval()
。你可以把题干复制出来,让别的 AI 去提供解法,然后再尝试把某些关键词放进 prompt 中引诱它生成出来。当然如果你很熟悉 JavaScript,本身就知道对应的语法糖或者算法的关键词,那必然更快能 AC。
最后,还有一点个位数的空间可以压榨。实测标点符号并不重要,函数的签名多个参数可以合并一下写,很显然的参数类型可以省略等等。这些就是已经在榜单上冲得很前想再扣出来一点的技巧了。
再说说打这个题让我对 AI 编程产生的思考。
第一还是那个「AI 决定下限,人决定上限」的理论。在 8、9 这两题我表现并不佳,因为我真的没有思路怎么解这个题,自然没办法给 AI 指出一个优化的方向。当然 AI 的能力也很强,通过一个函数签名最多试几次就能 AC,而且我估计网站背后只是用的一个本地的开源小参数模型。这说明 AI 已经能有竞争力地完成一些任务。
第二就是跟 AI 的磨合。从最开始的 GitHub Copilot 的自动补全,到 Cursor 和 Claude Code,我一直在积极使用 AI 编程,就好像跟伙伴慢慢磨合,知道它的能力能很有信心地做出什么,做什么比较悬,怎么去写 prompt 效果更好。例如我一上来就知道,复制粘贴函数签名会很关键,事实也证明这能很快地 AC 大部分题。这些东西真的很难形成书面的经验,更多时候是一种感觉。
第三是编程方式和效率的变化。就像「copilot」这个名字所说的一样,LLM 时代的编程不再是人手敲代码的「刀耕火种」,人更多地需要考虑「方向」而不是具体的实施,就算你把算法倒背如流,键盘敲冒烟,依然无法比肩 LLM 几十 token/s 的输出。另外就是,如果这个时代你还不能使用 AI 编程,那你的效率跟使用 AI 的同行完全没法比。当然这个说法有点夸张,就算你使用 AI,你的「上下文」上限仍然取决于你的脑容量,或者时间精力等等。
最后是「AI 友好的代码」。在这个题库中,多数的题都是非常基础和常见的算法,所以 AI 的准确率非常高。在工作中,更多地应该使用开源、知名而且「不那么新」(要在知识截止日期前)的库,开源库也可以使用 context7 等提供文档,尽量不要重复造轮子,更加不要再搞一大堆公司的私有库。另外最好重视文档的建设,更全更准确的上下文能让 AI 更好发挥。
- 1
- 0
-
赞助
微信赞赏码
-
分享