- 作者:老汪软件技巧
- 发表时间:2024-09-05 17:01
- 浏览量:
每日的知识积累,包括 1 个 Ts 类型体操,两个 Leetcode 算法题,三个前端八股文题,四个英语表达积累。
1. 一个类型体操
类型体操题目集合kebab case
将 camelCase 或 PascalCase 的字符串转换为 kebab-case 的风格。
例如:
type FooBarBaz = KebabCase<"FooBarBaz">;
const foobarbaz: FooBarBaz = "foo-bar-baz";
type DoNothing = KebabCase<"do-nothing">;
const doNothing: DoNothing = "do-nothing";
分析
这无非就是将元组的遍历变成对字符串的遍历而已;而对于字符串的遍历,我们使用的是 模板字符串。遍历每一个字符,如果它是大写的就将其替换成 *- 的形式。
尝试写出
type KebabCase<
T extends string,
isFirst = true
> = T extends `${infer First}${infer Rest}`
? First extends Lowercase<First>
? `${First}${KebabCasefalse >}`
: isFirst extends true
? `${First}${KebabCasefalse >}`
: `-${Lowercase} ${KebabCasefalse >}`
: T;
测试用例
type FooBarBaz = KebabCase<"FooBarBaz">; // 'Foo-bar-baz'
type FooBarBaz2 = KebabCase<"do-nothing">; // 'do-nothing'
参考答案
type KebabCase<
S extends string,
TLastWord extends string = ""
> = S extends `${infer L}${infer R}`
? Lowercase<`${Lowercase extends L
? L
: TLastWord extends ""
? L
: `-${L}`} ${KebabCase} `>
: S;
经验总结
显然参考答案简洁许多,但是思路是一样的。
2. 两个 Leetcode 题目
刷题的顺序参考这篇文章 LeeCode 刷题顺序
2.1 [498] 对角线遍历
给你一个大小为 m x n 的矩阵 mat ,请以对角线遍历的顺序,用一个数组返回这个矩阵中的所有元素。
示例 1:
输入:mat = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,4,7,5,3,6,8,9]
示例 2:
输入:mat = [[1,2],[3,4]]
输出:[1,2,3,4]
提示:
m == mat.length
n == mat[i].length
1 <= m, n <= 104
1 <= m * n <= 104
-105 <= mat[i][j] <= 105
尝试实现:
/**
* @param {number[][]} mat
* @return {number[]}
*/
var findDiagonalOrder = function (mat) {
const r = mat.length;
const c = mat[0].length;
const n = r * c;
const rst = [];
let i = 0;
let j = 0;
let dir = 1; // 1 右上 | -1 左下
while (rst.length < n) {
const ele = mat[i] ? mat[i][j] : undefined;
// 下一个有值的情况
if (typeof ele !== "undefined") {
rst.push(ele);
if (dir === 1) {
i -= 1;
j += 1;
} else {
i += 1;
j -= 1;
}
} else {
// 下一个没有值,则改变方向,尝试获取下一个值
if (dir === 1) {
dir = -dir;
i += 1;
} else {
dir = -dir;
j += 1;
}
const ele = mat[i] ? mat[i][j] : undefined;
if (typeof ele === "undefined") {
if (dir === 1) {
i -= 1;
j += 1;
} else {
i += 1;
j -= 1;
}
}
}
}
return rst;
};
我的思路:
遍历的顺序为:右上(右);左下(下);右上(右);左下(下);右上(右)...首先确定遍历的结束条件,以已经收集到一维数组中元素的个数等于矩阵个数作为退出条件每次遍历做下面的事情:获取下一个位置的元素;如果 typeof 不为 'undefined' 就添加到数组然后 continue否则,改变方向,然后尝试获取新的位置的下一个元素,如果 typeof 不为 'undefined' 就添加到数组然后 continue,否则直接 continue
得分结果: 5.30% 7.06%
总结提升:比之前好多了,但是还是不能避免 const ele = mat[i] ? mat[i][j] : undefined; 导致的错误。
2.2 [566] 重塑矩阵
在 MATLAB 中,有一个非常有用的函数 reshape ,它可以将一个 m x n 矩阵重塑为另一个大小不同(r x c)的新矩阵,但保留其原始数据。
给你一个由二维数组 mat 表示的 m x n 矩阵,以及两个正整数 r 和 c ,分别表示想要的重构的矩阵的行数和列数。
重构后的矩阵需要将原始矩阵的所有元素以相同的 行遍历顺序 填充。
如果具有给定参数的 reshape 操作是可行且合理的,则输出新的重塑矩阵;否则,输出原始矩阵。
示例 1:
输入:mat = [[1,2],[3,4]], r = 1, c = 4
输出:[[1,2,3,4]]
示例 2:
输入:mat = [[1,2],[3,4]], r = 2, c = 4
输出:[[1,2],[3,4]]
提示:
m == mat.length
n == mat[i].length
1 <= m, n <= 100
-1000 <= mat[i][j] <= 1000
1 <= r, c <= 300
尝试完成:
/**
* @param {number[][]} mat
* @param {number} r
* @param {number} c
* @return {number[][]}
*/
var matrixReshape = function (mat, r, c) {
const _r = mat.length;
const _c = mat[0].length;
if (r * c !== _r * _c) {
return mat;
}
// map 会跳过 empty 元素,切记
const newMat = Array(r)
.fill(0)
.map(() => Array(c).fill(0));
for (let i = 0; i < _r; i++) {
for (let j = 0; j < _c; j++) {
const cur = mat[i][j];
const num = i * _c + j + 1;
let _j = num % c;
let _i;
if (_j === 0) {
_i = (num - _j) / c - 1;
} else {
_i = Math.floor((num - _j) / c);
}
if (_j - 1 < 0) {
_j = _j - 1 + c;
} else {
_j = _j - 1;
}
newMat[_i][_j] = cur;
}
}
return newMat;
};
我的思路:
先获取原来矩阵的 _r _c 检查乘积是否相等遍历原来的数组,然后将每个元素填到新的位置中去
得分结果: 27.71% 39.76%
总结提升:
map 会跳过 empty 元素,切记,因此初始化可以写成:const newMat = Array(r).fill(0).map(() => Array(c).fill(0));获取矩阵的 row 和 col 的个数,如下所示:
const _r = mat.length;
const _c = mat[0].length;
3. 三个前端题目说说 const 的原理及其可修改性
补充:Object 三傻:freeze seal preventExtensions
如果尝试 new 一个箭头函数会怎么样先手写一个函数实现 new 函数的功能:
function myNew(constructor, ...rest) {
// 1. 判断类型
if (typeof constructor !== "function")
throw new Error("constructor must be a function");
// 2. 创建一个新对象其原型为构造函数的原型
const obj = Object.create(constructor.prototype);
// 3. 执行构造函数
const rst = constructor.apply(obj, rest);
// 4. 判断并返回
return rst && Object(rst) === rst ? rst : obj;
}
手写一个函数,实现柯里化功能分析:柯里化的本质是**收集到一定数量的参数(称为生产资料更为合适)**之后才去执行既定的函数,如果参数的数目小于既定函数执行所需要的个数则不执行,而是继续收集参数。
如果用代码来表示,可以设既定函数为f,柯里化之后映射成为函数g,g 在执行的时候会对积累到的参数的数目进行判断,如果数目是足够的,则返回 f 的执行结果(相当于执行了 f),如果参数的数目不够,则将此次调用传入的参数累积起来,然后返回函数g',等待下一次调用。**返回的g'函数和g具有相同的德行。
根据上面的分析,首先需要一个私有域存储已经收集到了的参数,这就需要用到闭包了;而执行既定函数的参数数目可以直接从既定函数上获取:f.length表示函数需要的形参数目。
现在尝试性的写一下:
function _G(f) {
if (typeof f !== "function") throw new Error("f must be a function!");
return function (...rest) {
const currentParams = [...rest];
const currentLength = currentParams.length;
if (currentLength >= f.length) return f.apply(this, currentParams);
// return _G(f); ???
};
}
这里无法处理参数不够的时候如何返回一个g'的问题。上面分析到g'和g性质相同,所以g'和g都是由_G产生的,所以问题出在_G没有设计好,一个参数根本无法区分g'和g。
于是,需要增加一个形参,表示目前g类已经收集到的参数:
function _G(f, alreadyCollectedParams = []) {
if (typeof f !== "function") throw new Error("f must be a function!");
return function (...rest) {
const currentParams = [...alreadyCollectedParams, ...rest];
const currentLength = currentParams.length;
if (currentLength >= f.length) return f.apply(this, currentParams);
return _G(f, currentParams);
};
}
function add(a, b, c) {
return a + b + c;
}
const c_add = _G(add);
console.log(c_add(1)(2)(3)); // 6
console.log(c_add(1, 2)(3)); // 6
console.log(c_add(1)(2, 3)); // 6
console.log(c_add(1, 2, 3)); // 6
4.四句英语积累get through -- completeWe have a lot to [get through] today, so let's begin. [Why We have a lot to finish today, ...]Thanks for a great meeting everybody! We [got through] a lot of important business today.knuckle down -- focus and work hardWe'll have to really [knuckle down] if we want to finish the project on schedule. [Why not work hard]The start of the month was hard, but we [knuckled down] and [hit our sales targets] [in the end].