Skip to content

Latest commit

 

History

History
310 lines (223 loc) · 7.09 KB

readme.md

File metadata and controls

310 lines (223 loc) · 7.09 KB

TypeScript 本地刷题

目录

需求

TypeScript 的静态类型检查和自动完成都是好东西,
它们能极大地提升编程体验,
但是 LeetCode 还不支持以 TypeScript 提交。

并且,在提交之前,最好能先在本地进行单元测试。

我配置了一个自动化脚本,拥有以下功能:

  • 用 TypeScript 刷题(当然 JavaScript 也能支持)
  • 自动编译 TS 到 JS 以便能够提交(会多两行模块化代码,不会影响提交运行)
  • 自动进行单元测试(有本地测试用例时)
  • 在 git commit 的时候自动使用 eslint 和 prettier 对代码进行规范化

安装

Step 1

使用 VSCode 并安装插件:

然后调整一下插件的默认配置,比如我的:

{
  "leetcode.defaultLanguage": "javascript",
  "leetcode.workspaceFolder": "[你的文件夹路径]",
  "leetcode.filePath": {
    "javascript": {
      "folder": "./js/problems/${id}.${kebab-case-name}",
      "filename": "solution.ts"
    }
  }
}

并登陆 leetcode 账户,这样就能愉快地在本地刷题了。

Step 2

(假设你已经拥有基本的 TS、JS 和 Node 使用经验。)

下载本仓库,并在这里执行

npm install
npm run start

其中的 start 命令就启动了上文中描述的那些自动化任务。

单元测试

单元测试的方式,我一开始设计成完全自动化:
有一个唯一的运行文件,各个题目的测试文件中只有测试数据。

然而最终还是决定改成手动调用的方式,
这样虽然在每个测试文件中都会重复一些代码,
但是代码逻辑会更清晰,
对于多个解答的情况也能更灵活。

而且,刷题的重点和难点在于算法本身不是吗?
多复制粘贴几个字符花不了多少时间。

// * in `solution.ts`

const solution = (...input) => {
  // ... complete algorithm logic

  return output;
};

export { solution };
// * in `.test.ts`

import { testRunner, makeTestCases, makeTestCasesOfSingleInput } from '../../helper/test-helper';

// * ------------------------------------------------ test cases

type Input = [number[], number];
type Output = [number, number];

const cases = makeTestCases<Input, Output>([
  //
  {
    input: [[2, 7, 11, 15], 9],
    output: [0, 1],
  },
  {
    input: [[3, 2, 4], 6],
    output: [1, 2],
  },
  {
    input: [[0, 4, 3, 0], 0],
    output: [0, 3],
  },
]);

// * ------------------------------------------------ it will run test

import { solution } from './solution';
import { solution as solution2 } from './solution-2';
import { solution as solution3 } from './solution-3';
testRunner(cases, solution);
testRunner(cases, solution2, 'displayAnotherFunctionName');
testRunner(cases, solution3, 'anotherSolution');

API

我做了一些方法来辅助我的测试

  • makeTestCases:生成测试用例
  • makeTestCasesOfSingleInput:生成测试用例(单输入的情况)
  • testRunner:进行用例测试
  • compareBy:生成 自定义 testRunner

makeTestCases

这个 API 封装测试用例,
本身其实不进行任何数据处理,
主要用于添加数据类型,并保持一致性。

函数可能有需要多个参数的情况,
多个参数可以以单个数组的形式传递,
原理是:

const myCase = {
  input: [a, b],
  output: c,
};

solution(a, b) === c;
solution(...myCase.input) === c;

makeTestCasesOfSingleInput

对于单参数的情况,
可以不写最外层干扰视线的方括号,
测试用例中可以直接写参数本身。

const casesA = makeTestCasesOfSingleInput<number, number>([
  {
    input: 2,
    output: 2,
  },
]);

const casesB = makeTestCases<number, number>([
  {
    input: [2],
    output: 2,
  },
]);

const casesC = [
  {
    input: [2],
    output: 2,
  },
];

// * 以上三者都等价

数据形状确保是一致的,那么后续进行测试就方便多了。

testRunner

type TestRunner = (testCases: TestCases, solver: Function, fnName?: string) => void;

测试每个用例,并显示测试信息。

也支持原地算法(直接修改输入数据,不返回新数据)的情况,
(如 第 344 题

如果需要测试多个解答时,
可选的第三个参数能在测试中区分显示函数名。
比如:

import { solution } from './solution';
import { solution as solution2 } from './solution-2';
import { solution as solution3 } from './solution-3';
testRunner(cases, solution);
testRunner(cases, solution2, 'displayAnotherFunctionName');
testRunner(cases, solution3, 'anotherSolution');

compareBy

const myTestRunner = compareBy<Output>((a) => a.sort());

对于一些题目(如 349),返回的数组不要求顺序一致。
那么可以调整测试的判断逻辑,生成自定义的测试运行器。
而不用强行修改算法本身来通过单元测试了。

模块化注意事项

导出声明

为了支持测试,需要模块化导出,
模块化的写法必须和声明分开。

比如:

// * ts
const solution = (str: string): string => {};

export { solution };

会编译成以下 JS 代码,这样不会影响提交:

// * js
const solution = (str) => {};

exports.solution = solution;

而不能连着写:

// * ts
export const solution = () => {};

不然编译的 JS 代码会提交失败:

// * js
exports.solution = () => {};

ESM vs CJS

此外,如果 TS 源码采用 CJS 风格,

module.exports = solution;

在有多个解答文件时,会报以下错误:

Cannot redeclare block-scoped variable 'solution'

并且貌似没有什么优雅的解决方案。

同时,测试中如果使用 require('./solution') 导入解答函数,
则无法支持 VS Code 的 Update Imports On File Move 功能。

所以,推荐一律使用 ESM 风格,

// * solution.ts
const solution = (str: string): string => {};

export { solution };
// * do.test.ts
import { solution } from './solution';

testRunner(cases, solution);

虽然可能显得麻烦和啰嗦一点,
但总之,刷题的重点和难点在于算法本身不是吗?