01
基礎題
產生完整工具函式
測試問題
請提供一個完整 TypeScript 檔案,必須含 parseCsv、escapeCsv、groupBy、sumBy、uniqueBy 五個可匯出函式,每個函式各附一個 Vitest 測試。禁止使用外部套件;不得用省略號、TODO、placeholder 或『其餘同理』。
載入前 · 0 個 Skill載入後 · 版本已核對再次確認 · 0 個 Skill已確認模型套用 Skill三個環境皆已清理
未載入 Skill1516 tokens · 34640 ms
```typescript
import { describe, expect, it } from "vitest";
export function parseCsv(input: string): string[][] {
if (input.length === 0) {
return [];
}
const rows: string[][] = [];
let row: string[] = [];
let field = "";
let insideQuotes = false;
for (let index = 0; index < input.length; index += 1) {
const character = input[index];
if (insideQuotes) {
if (character === '"') {
if (input[index + 1] === '"') {
field += '"';
index += 1;
} else {
insideQuotes = false;
}
} else {
field += character;
}
continue;
}
if (character === '"' && field.length === 0) {
insideQuotes = true;
} else if (character === ",") {
row.push(field);
field = "";
} else if (character === "\n" || character === "\r") {
if (character === "\r" && input[index + 1] === "\n") {
index += 1;
}
row.push(field);
rows.push(row);
row = [];
field = "";
} else {
field += character;
}
}
if (insideQuotes) {
throw new Error("Invalid CSV: unterminated quoted field");
}
const endsWithLineBreak = input.endsWith("\n") || input.endsWith("\r");
if (!endsWithLineBreak || row.length > 0 || field.length > 0) {
row.push(field);
rows.push(row);
}
return rows;
}
export function escapeCsv(value: unknown): string {
const text = value == null ? "" : String(value);
if (!/[",\r\n]/.test(text)) {
return text;
}
return `"${text.replace(/"/g, '""')}"`;
}
export function groupBy<T, K>(
values: readonly T[],
keySelector: (value: T, index: number) => K,
): Map<K, T[]> {
const groups = new Map<K, T[]>();
values.forEach((value, index) => {
const key = keySelector(value, index);
const group = groups.get(key);
if (group) {
group.push(value);
} else {
groups.set(key, [value]);
}
});
return groups;
}
export function sumBy<T>(
values: readonly T[],
valueSelector: (value: T, index: number) => number,
): number {
return values.reduce(
(sum, value, index) => sum + valueSelector(value, index),
0,
);
}
export function uniqueBy<T, K>(
values: readonly T[],
keySelector: (value: T, index: number) => K,
): T[] {
const seenKeys = new Set<K>();
const uniqueValues: T[] = [];
values.forEach((value, index) => {
const key = keySelector(value, index);
if (!seenKeys.has(key)) {
seenKeys.add(key);
uniqueValues.push(value);
}
});
return uniqueValues;
}
describe("parseCsv", () => {
it("parses quoted commas, escaped quotes, and embedded line breaks", () => {
const csv =
'name,description\r\nAlice,"Hello, world"\r\nBob,"Line one\nLine ""two"""';
expect(parseCsv(csv)).toEqual([
["name", "description"],
["Alice", "Hello, world"],
["Bob", 'Line one\nLine "two"'],
]);
});
});
describe("escapeCsv", () => {
it("quotes special values and escapes embedded quotation marks", () => {
expect(escapeCsv('Hello, "Alice"\nWelcome')).toBe(
'"Hello, ""Alice""\nWelcome"',
);
});
});
describe("groupBy", () => {
it("groups values according to the selected key", () => {
const values = [
{ category: "fruit", name: "Apple" },
{ category: "vegetable", name: "Carrot" },
{ category: "fruit", name: "Banana" },
];
expect(groupBy(values, (value) => value.category)).toEqual(
new Map([
[
"fruit",
[
{ category: "fruit", name: "Apple" },
{ category: "fruit", name: "Banana" },
],
],
["vegetable", [{ category: "vegetable", name: "Carrot" }]],
]),
);
});
});
describe("sumBy", () => {
it("sums the numbers produced by the selector", () => {
const items = [
{ price: 10, quantity: 2 },
{ price: 7.5, quantity: 4 },
];
expect(sumBy(items, (item) => item.price * item.quantity)).toBe(50);
});
});
describe("uniqueBy", () => {
it("keeps the first value associated with each unique key", () => {
const values = [
{ id: 1, name: "First" },
{ id: 2, name: "Second" },
{ id: 1, name: "Duplicate" },
];
expect(uniqueBy(values, (value) => value.id)).toEqual([
{ id: 1, name: "First" },
{ id: 2, name: "Second" },
]);
});
});
```
已載入 Skill +2071 tokens · 44947 ms
```typescript
import { describe, expect, it } from "vitest";
export function parseCsv(input: string): string[][] {
if (input.length === 0) {
return [];
}
const records: string[][] = [];
let record: string[] = [];
let field = "";
let inQuotes = false;
let closedQuote = false;
let recordJustEnded = false;
const appendField = (): void => {
record.push(field);
field = "";
closedQuote = false;
};
const appendRecord = (): void => {
appendField();
records.push(record);
record = [];
recordJustEnded = true;
};
for (let index = 0; index < input.length; index += 1) {
const character = input[index];
if (inQuotes) {
if (character === '"') {
if (input[index + 1] === '"') {
field += '"';
index += 1;
} else {
inQuotes = false;
closedQuote = true;
}
} else {
field += character;
}
recordJustEnded = false;
continue;
}
if (closedQuote) {
if (character === ",") {
appendField();
recordJustEnded = false;
continue;
}
if (character === "\n") {
appendRecord();
continue;
}
if (character === "\r") {
if (input[index + 1] === "\n") {
index += 1;
}
appendRecord();
continue;
}
throw new Error(
`Invalid CSV: unexpected character after closing quote at index ${index}.`,
);
}
if (character === '"' && field.length === 0) {
inQuotes = true;
recordJustEnded = false;
continue;
}
if (character === '"') {
throw new Error(`Invalid CSV: unexpected quote at index ${index}.`);
}
if (character === ",") {
appendField();
recordJustEnded = false;
continue;
}
if (character === "\n") {
appendRecord();
continue;
}
if (character === "\r") {
if (input[index + 1] === "\n") {
index += 1;
}
appendRecord();
continue;
}
field += character;
recordJustEnded = false;
}
if (inQuotes) {
throw new Error("Invalid CSV: unterminated quoted field.");
}
if (!recordJustEnded || record.length > 0 || field.length > 0 || closedQuote) {
appendField();
records.push(record);
}
return records;
}
export function escapeCsv(value: string): string {
if (/[",\r\n]/u.test(value)) {
return `"${value.replace(/"/gu, '""')}"`;
}
return value;
}
export function groupBy<T, K>(
values: readonly T[],
keySelector: (value: T, index: number) => K,
): Map<K, T[]> {
const groups = new Map<K, T[]>();
values.forEach((value, index) => {
const key = keySelector(value, index);
const group = groups.get(key);
if (group) {
group.push(value);
} else {
groups.set(key, [value]);
}
});
return groups;
}
export function sumBy<T>(
values: readonly T[],
valueSelector: (value: T, index: number) => number,
): number {
return values.reduce(
(total, value, index) => total + valueSelector(value, index),
0,
);
}
export function uniqueBy<T, K>(
values: readonly T[],
keySelector: (value: T, index: number) => K,
): T[] {
const seenKeys = new Set<K>();
const uniqueValues: T[] = [];
values.forEach((value, index) => {
const key = keySelector(value, index);
if (!seenKeys.has(key)) {
seenKeys.add(key);
uniqueValues.push(value);
}
});
return uniqueValues;
}
describe("parseCsv", () => {
it("parses quoted commas, escaped quotes, embedded newlines, and CRLF rows", () => {
const input =
'name,description\r\nAlice,"Hello, ""world"""\r\nBob,"Line 1\nLine 2"';
expect(parseCsv(input)).toEqual([
["name", "description"],
["Alice", 'Hello, "world"'],
["Bob", "Line 1\nLine 2"],
]);
});
});
describe("escapeCsv", () => {
it("quotes special values and doubles embedded quotation marks", () => {
expect(escapeCsv('Hello, "world"\nNext line')).toBe(
'"Hello, ""world""\nNext line"',
);
});
});
describe("groupBy", () => {
it("groups values by the selected key while preserving input order", () => {
const values = [
{ department: "engineering", name: "Alice" },
{ department: "sales", name: "Bob" },
{ department: "engineering", name: "Carol" },
];
const groups = groupBy(values, (value) => value.department);
expect(groups.get("engineering")).toEqual([values[0], values[2]]);
expect(groups.get("sales")).toEqual([values[1]]);
});
});
describe("sumBy", () => {
it("sums the numeric values returned by the selector", () => {
const items = [
{ price: 10, quantity: 2 },
{ price: 7.5, quantity: 4 },
{ price: 3, quantity: 1 },
];
expect(sumBy(items, (item) => item.price * item.quantity)).toBe(53);
});
});
describe("uniqueBy", () => {
it("keeps the first value associated with each selected key", () => {
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 1, name: "Updated Alice" },
];
expect(uniqueBy(users, (user) => user.id)).toEqual([users[0], users[1]]);
});
});
```