Skip to content

Commit da9a998

Browse files
author
lucifer
committed
feat: testcases
1 parent e63c057 commit da9a998

File tree

4 files changed

+346
-2
lines changed

4 files changed

+346
-2
lines changed

public/manifest.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@
1010
"icons": {
1111
"128": "logo.png"
1212
},
13+
"content_scripts": [
14+
{
15+
"matches": ["*://leetcode-cn.com/*", "*://leetcode.com/*"],
16+
"js": ["main.js"]
17+
}
18+
],
1319
"permissions": ["tabs"],
1420
"content_security_policy": "script-src 'self' 'sha256-9HcBuUP35aPkU0991A4mASdsuifTkUlifJ7elThz6Ow=' 'sha256-0Jo/EYaXS11i7poc/P9fGcq/o6P0djny2JW6WivTVVw='; object-src 'self'"
1521
}

src/App.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
LEETCODE_URL,
1313
CONTRIBUTE_PROGRAMMING_LANGUAGE_URL,
1414
} from "./constant/index";
15+
import TestCase from "./testCase";
1516
import ProblemDetail from "./Detail";
1617
import TagOrLink from "./TagOrLink";
1718
import tempaltes from "./codeTemplates/index";
@@ -172,6 +173,8 @@ function App() {
172173
</Button>
173174
))}
174175

176+
<TestCase />
177+
175178
<div style={page === "" ? {} : { display: "none" }}>
176179
<h2 style={{ display: "flex", justifyContent: "center" }}>
177180
代码模板

src/testCase.js

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
import React, { useEffect, useState } from "react";
2+
import { Input, Button, Select, message, Checkbox, InputNumber } from "antd";
3+
import {
4+
buildRandomTree,
5+
serialise_bfs,
6+
copyToClipboard,
7+
getRandom,
8+
getRandomUnique,
9+
} from "./utils";
10+
11+
const { Option } = Select;
12+
13+
// TODO referrence 支持 , 比如 k 是在数组大小范围内动态的
14+
15+
function isNull(c) {
16+
return c === "null";
17+
}
18+
19+
function isTree(A) {
20+
return A.length > 0 && A.some(isNull);
21+
}
22+
23+
function isBinary(c) {
24+
return c === "1" || c === "0" || c === 1 || c === 0;
25+
}
26+
27+
function isBinaryArray(A) {
28+
return A.every(isBinary);
29+
}
30+
31+
const zip = (rows) =>
32+
rows[0].map((_, c) => rows.map((row) => row[c]).join("\r\n"));
33+
34+
// 支持 数组,链表,树。
35+
// 值只支持数字,不支持字符串
36+
// 支持自动识别二值和普通
37+
function handleGenerate({
38+
input,
39+
amount = 10,
40+
lower = 0,
41+
upper = 10001,
42+
maxLength = 10,
43+
isUnique,
44+
type,
45+
}) {
46+
// 多行测试用例,parts 就是每一行
47+
const parts = input.split(" ");
48+
if (parts.length > 1) {
49+
return zip(
50+
parts.map((part) =>
51+
handleGenerate({
52+
input: part,
53+
amount,
54+
upper,
55+
maxLength,
56+
type,
57+
isUnique,
58+
})
59+
)
60+
);
61+
}
62+
const finalType = !["multi", ""].includes(type) ? type : gussType(input);
63+
64+
if (finalType === "tree") {
65+
const ans = [];
66+
for (let i = 0; i < maxLength; i++) {
67+
const nodes = serialise_bfs(
68+
buildRandomTree({ lower, amount, upper, isUnique })
69+
);
70+
ans.push(nodes);
71+
}
72+
return ans;
73+
} else if (finalType === "array" || finalType === "linked-list") {
74+
const ans = [];
75+
const items = input.slice(1, -1).split(",");
76+
77+
if (isBinaryArray(items)) {
78+
upper = 1;
79+
lower = 0;
80+
}
81+
82+
for (let i = 0; i < maxLength; i++) {
83+
let r = [];
84+
if (isUnique) {
85+
r = getRandomUnique(lower, upper, amount);
86+
} else {
87+
for (let i = 0; i < amount; i++) {
88+
r.push(getRandom(lower, upper));
89+
}
90+
}
91+
92+
ans.push("[" + r.toString() + "]");
93+
}
94+
return ans;
95+
} else if (finalType === "single") {
96+
// 单一值
97+
const ans = [];
98+
for (let i = 0; i < maxLength; i++) {
99+
ans.push(getRandom(lower, upper));
100+
}
101+
return ans;
102+
} else {
103+
return [];
104+
}
105+
}
106+
107+
function gussType(input) {
108+
if (!input) return "";
109+
const parts = input.split(" ");
110+
if (parts.length > 1) return "multi";
111+
if (input.length > 1 && input[0] == "[" && input[input.length - 1] == "]") {
112+
if (input[1] == "[") return "";
113+
const v = input.slice(1, -1);
114+
if (isNaN(v[0])) return "";
115+
const items = v.split(",");
116+
// 数组,链表或者树
117+
if (isTree(items)) return "tree";
118+
if (Array.isArray(items)) {
119+
if (Array.isArray(items[0])) return "";
120+
return "array";
121+
}
122+
}
123+
124+
if (isNaN(input)) return "";
125+
126+
return "single";
127+
}
128+
129+
function getProviedTestCases() {
130+
const pres = document.getElementsByTagName("pre");
131+
const ans = [];
132+
for (var i = 0; i < pres.length; ++i) {
133+
if (pres[i].innerText.includes("输入:")) {
134+
const testcase = pres[i].innerText.match(/(.*)/)[1];
135+
ans.push(testcase);
136+
}
137+
}
138+
return ans;
139+
}
140+
141+
// desc 是力扣的官方描述,需要转换为标准测试用例
142+
// eg: "nums = [1,2,3,3], quantity = [2]" => "[1,2,3,3]\n[2]"
143+
144+
function normalize(testCase) {
145+
const parts = testCase.split(", ");
146+
return parts.map((q) => q.match(/=(.*)$/)[1]).join("\n");
147+
}
148+
149+
export default function TestCase() {
150+
// [1,2,null]
151+
const [input, setInput] = useState("");
152+
const [testCases, setTestCases] = useState([]);
153+
const [type, setType] = useState("");
154+
const [isUnique, setIsUnique] = useState(false);
155+
156+
const [lower, setLower] = useState(0);
157+
const [upper, setUpper] = useState(10001);
158+
const [amount, setAmount] = useState(10);
159+
160+
useEffect(() => {
161+
const t = Array.isArray(testCases) && testCases.join("\r\n");
162+
if (t) {
163+
if (copyToClipboard(t)) message.success("复制成功");
164+
}
165+
}, [testCases]);
166+
167+
return (
168+
<>
169+
<div style={{ display: "none" }}>
170+
<div>
171+
测试用例类型:
172+
<Select value={type} style={{ width: 120 }} onChange={setType}>
173+
<Option value="array">数组(或链表)</Option>
174+
<Option value="tree"></Option>
175+
<Option value="single">单一值</Option>
176+
<Option value="multi" disabled>
177+
多个参数
178+
</Option>
179+
</Select>
180+
<div>选项</div>
181+
<Checkbox
182+
style={type === "single" ? { display: "none" } : {}}
183+
checked={upper - lower + 1 < amount ? false : isUnique}
184+
disabled={upper - lower + 1 < amount}
185+
onChange={() => setIsUnique(!isUnique)}
186+
>
187+
是否唯一
188+
</Checkbox>
189+
数据值范围:
190+
<InputNumber value={lower} onChange={setLower} /> -
191+
<InputNumber value={upper} onChange={setUpper} />
192+
<span style={type === "single" ? { display: "none" } : {}}>
193+
长度限制:
194+
<InputNumber value={amount} onChange={setAmount} />
195+
</span>
196+
</div>
197+
198+
<pre>
199+
{Array.isArray(testCases) && testCases.length == 0
200+
? "不支持的数据格式。目前只支持:单个值,数组,链表,树。"
201+
: testCases.join("\r\n")}
202+
</pre>
203+
204+
<Input
205+
style={{ width: 320, marginRight: 20 }}
206+
placeholder="粘贴题目默认的测试用例"
207+
value={input}
208+
onChange={(e) => {
209+
const v = e.target.value;
210+
setInput(v);
211+
setType(gussType(v));
212+
}}
213+
/>
214+
215+
<Button
216+
type="primary"
217+
onClick={() => {
218+
setTestCases(
219+
handleGenerate({
220+
input,
221+
type,
222+
lower,
223+
upper,
224+
amount,
225+
isUnique: upper - lower + 1 < amount ? false : isUnique,
226+
})
227+
);
228+
}}
229+
>
230+
生成十个随机测试用例
231+
</Button>
232+
</div>
233+
234+
<Button
235+
type="primary"
236+
onClick={() => {
237+
const cases = getProviedTestCases();
238+
const ans = cases.map(normalize).join("\n");
239+
console.log(cases, ans);
240+
if (ans) {
241+
if (copyToClipboard(ans)) message.success("复制成功");
242+
}
243+
}}
244+
>
245+
获取所有测试用例
246+
</Button>
247+
</>
248+
);
249+
}

src/utils.js

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,94 @@ function TreeNode(value) {
55
value,
66
};
77
}
8+
const seen = {};
9+
export function getRandomUnique(lower, upper, amount) {
10+
console.log(seen);
11+
// [10, 20]10
12+
const start = getRandom(0, upper - lower - amount);
13+
if (`${lower}-${upper}` in seen)
14+
return seen[`${lower}-${upper}`].slice(start, start + amount + 1);
815

9-
export function bfs(nodes) {
16+
const condidates = Array.from(Array(upper - lower + 1), (_, i) => i + lower);
17+
18+
const n = condidates.length;
19+
for (let i = n - 1; i >= 0; i--) {
20+
const temp = condidates[i];
21+
const number = (Math.random() * n) >>> 0;
22+
condidates[i] = condidates[number];
23+
condidates[number] = temp;
24+
}
25+
seen[`${lower}-${upper}`] = condidates;
26+
return condidates.slice(start, start + amount + 1);
27+
}
28+
29+
export function getRandom(n, m) {
30+
return Math.round(Math.random() * (m - n) + n);
31+
}
32+
export function copyToClipboard(str) {
33+
try {
34+
const el = document.createElement("textarea");
35+
el.value = str;
36+
document.body.appendChild(el);
37+
el.select();
38+
document.execCommand("copy");
39+
document.body.removeChild(el);
40+
return true;
41+
} catch (err) {
42+
return false;
43+
}
44+
}
45+
export function serialise_bfs(root) {
46+
let ans = "[";
47+
const q = [root];
48+
let cur = null;
49+
50+
while (q.length > 0) {
51+
cur = q.shift();
52+
if (cur) {
53+
q.push(cur.left);
54+
q.push(cur.right);
55+
ans += cur.value + ",";
56+
} else {
57+
ans += "null,";
58+
}
59+
}
60+
61+
return ans.slice(0, -1) + "]";
62+
}
63+
64+
export function buildRandomTree({
65+
amount = 10,
66+
upper = 10,
67+
lower = 0,
68+
isUnique,
69+
}) {
70+
let remain = amount;
71+
let condidates = [];
72+
if (isUnique) {
73+
condidates = getRandomUnique(lower, upper, amount);
74+
}
75+
76+
function dfs({ upper, lower }) {
77+
if (remain <= 0) return null;
78+
remain -= 1;
79+
80+
const root = TreeNode(
81+
!isUnique ? getRandom(lower, upper) : condidates[remain]
82+
);
83+
84+
if (Math.random() > 0.5) {
85+
root.left = dfs({ upper, lower });
86+
}
87+
if (Math.random() > 0.5) {
88+
root.right = dfs({ upper, lower });
89+
}
90+
return root;
91+
}
92+
return dfs({ upper, lower });
93+
}
94+
95+
export function deserialise_bfs(nodes) {
1096
if (nodes.length === 0 || nodes[0] === "null") return null;
1197
const root = TreeNode(nodes[0]);
1298
const q = [root];
@@ -32,7 +118,7 @@ export function bfs(nodes) {
32118

33119
i += 2;
34120
}
35-
console.log(root);
121+
36122
return {
37123
root,
38124
};

0 commit comments

Comments
 (0)