2727. 判断对象是否为空
给定一个对象或数组,判断它是否为空。
- 一个空对象不包含任何键值对。
- 一个空数组不包含任何元素。
你可以假设对象或数组是通过 JSON.parse 解析得到的。
示例 1:
输入:obj = {"x": 5, "y": 42}
输出:false
解释:这个对象有两个键值对,所以它不为空。
示例 2:
输入:obj = {}
输出:true
解释:这个对象没有任何键值对,所以它为空。
示例 3:
输入:obj = [null, false, 0]
输出:false
解释:这个数组有 3 个元素,所以它不为空。
提示:
obj是一个有效的 JSON 对象或数组2 <= JSON.stringify(obj).length <= 10^5
你可以在 O(1) 时间复杂度内解决这个问题吗?
思路一
使用 Object.keys方法检查对象或者数组的键的长度,看这长度是否为 0,就可以判断对象或者数组是否为空了。
Object.keys的时间复杂度和空间复杂度都为 O(n),因为需要遍历对象/数组里每一个键值对,且返回的数组长度和对象的大小正相关。
代码一
type JSONValue = null | boolean | number | string | JSONValue[] | { [key: string]: JSONValue };
type Obj = Record<string, JSONValue> | JSONValue[]
function isEmpty(obj: Obj): boolean {
return Object.keys(obj).length === 0;
};
思路二(O(1) 解法:判断是否有可迭代内容)
题目倡导我们用O(1)的时间复杂度来解决问题,下面是一种方法:
可以使用 for 循环迭代器来检查是否有内容可迭代,如果有,表示对象不为空,如果没有可迭代的内容,表示对象为空。这样除了时间复杂度为O(1)之外,空间复杂度也为O(1)。
代码二
type JSONValue = null | boolean | number | string | JSONValue[] | { [key: string]: JSONValue };
type Obj = Record<string, JSONValue> | JSONValue[]
function isEmpty(obj: Obj): boolean {
for (let _ in obj) {
// 如果走进了迭代器,说明有内容,直接判定为不为空并返回
return false;
}
// 没有走进迭代器,直接走到了下边,说明无内容
return true;
};
2677. 分块数组
给定一个数组 arr 和一个块大小 size ,返回一个 分块 的数组。分块 的数组包含了 arr 中的原始元素,但是每个子数组的长度都是 size 。如果 arr.length 不能被 size 整除,那么最后一个子数组的长度可能小于 size 。
你可以假设该数组是 JSON.parse 的输出结果。换句话说,它是有效的JSON。
请你在不使用 lodash 的函数 _.chunk 的情况下解决这个问题。
示例 1:
输入:arr = [1,2,3,4,5], size = 1
输出:[[1],[2],[3],[4],[5]]
解释:数组 arr 被分割成了每个只有一个元素的子数组。
示例 2:
输入:arr = [1,9,6,3,2], size = 3
输出:[[1,9,6],[3,2]]
解释:数组 arr 被分割成了每个有三个元素的子数组。然而,第二个子数组只有两个元素。
示例 3:
输入:arr = [8,5,3,2,6], size = 6
输出:[[8,5,3,2,6]]
解释:size 大于 arr.length ,因此所有元素都在第一个子数组中。
示例 4:
输入:arr = [], size = 1
输出:[]
解释:没有元素需要分块,因此返回一个空数组。
提示:
arr是一个有效的 JSON 数组2 <= JSON.stringify(arr).length <= 10``51 <= size <= arr.length + 1
思路一(常规解法)
简单题,怎么搞都能很简单返回结果,重点还是在性能上。
少次循环 + 少做修改数组的操作,可以让性能达到最好。
代码一
type JSONValue = null | boolean | number | string | JSONValue[] | { [key: string]: JSONValue };
type Obj = Record<string, JSONValue> | Array<JSONValue>;
function chunk(arr: Obj[], size: number): Obj[][] {
const result = [];
// 需要注意,每次循环,下标移动 size 长度
for (let i = 0; i < arr.length; i += size) {
// 一次性把 chunk 推到 result 中
result.push(arr.slice(i, i + size));
}
return result;
};
因为还需要 result 存储结果,所以空间复杂度为O(n)
思路二(高阶解法)
如果能一口气返回结果,那肯定是最好的。Array.from函数除了可以把想要转换成数组的类数组或可迭代对象转化成数组,还可以很方便的产出批量处理的数组。
developer.mozilla.org/zh-CN/docs/…
示例:
// 从字符串构建数组
console.log(Array.from('foo'));
// Expected output: Array ["f", "o", "o"]
// 从 Set 构建数组
const set = new Set(["foo", "bar", "baz", "foo"]);
Array.from(set);
// [ "foo", "bar", "baz" ]
// 从 Map 构建数组
const map = new Map([
[1, 2],
[2, 4],
[4, 8],
]);
Array.from(map);
// [[1, 2], [2, 4], [4, 8]]
// 根据 DOM 元素的属性创建一个数组
const images = document.querySelectorAll("img");
const sources = Array.from(images, (image) => image.src);
const insecureSources = sources.filter((link) => link.startsWith("http://"));
// ======================================================
// 使用箭头函数作为映射函数去操作多个元素
Array.from([1, 2, 3], (x) => x + x);
// [2, 4, 6]
// 生成一个数字序列。因为数组在每个位置都使用 `undefined` 初始化,下面的 `v` 值将是 `undefined`
Array.from({ length: 5 }, (v, i) => i);
// [0, 1, 2, 3, 4]
代码二
type JSONValue = null | boolean | number | string | JSONValue[] | { [key: string]: JSONValue };
type Obj = Record<string, JSONValue> | Array<JSONValue>;
function chunk(arr: Obj[], size: number): Obj[][] {
// 定义一个数组,长度计算如下,需要进位保证所有元素有地方放
return Array.from({ length: Math.ceil(arr.length / size) }, (_, index) =>
// 数组里每个元素的内容
arr.slice(size * index, size * index + size)
);
}
2619. 数组原型对象的最后一个元素
请你编写一段代码实现一个数组方法,使任何数组都可以调用 array.last() 方法,这个方法将返回数组最后一个元素。如果数组中没有元素,则返回 -1 。
你可以假设数组是 JSON.parse 的输出结果。
示例 1 :
输入:nums = [null, {}, 3]
输出:3
解释:调用 nums.last() 后返回最后一个元素: 3。
示例 2 :
输入:nums = []
输出:-1
解释:因为此数组没有元素,所以应该返回 -1。
提示:
arr是一个有效的 JSON 数组0 <= arr.length <= 1000
思路
这题没什么难点,主要是注意,数组原型上的 this 指的是数组本身。
代码
declare global {
interface Array<T> {
last(): T | -1;
}
}
Array.prototype.last = function () {
// 判断数组长度,有元素就返回最后一个元素,否则返回 -1
return this.length > 0 ? this[this.length - 1] : -1;
};
/**
* const arr = [1, 2, 3];
* arr.last(); // 3
*/
export { };
2631. 分组
请你编写一段可应用于所有数组的代码,使任何数组调用 array. groupBy(fn) 方法时,它返回对该数组 分组后 的结果。
数组 分组 是一个对象,其中的每个键都是 fn(arr[i]) 的输出的一个数组,该数组中含有原数组中具有该键的所有项。
提供的回调函数 fn 将接受数组中的项并返回一个字符串类型的键。
每个值列表的顺序应该与元素在数组中出现的顺序相同。任何顺序的键都是可以接受的。
请在不使用 lodash 的 _.groupBy 函数的前提下解决这个问题。
示例 1:
输入:
array = [
{"id":"1"},
{"id":"1"},
{"id":"2"}
],
fn = function (item) {
return item.id;
}
输出:
{
"1": [{"id": "1"}, {"id": "1"}],
"2": [{"id": "2"}]
}
解释:
输出来自函数 array.groupBy(fn)。
分组选择方法是从数组中的每个项中获取 "id" 。
有两个 "id" 为 1 的对象。所以将这两个对象都放在第一个数组中。
有一个 "id" 为 2 的对象。所以该对象被放到第二个数组中。
示例 2:
输入:
array = [
[1, 2, 3],
[1, 3, 5],
[1, 5, 9]
]
fn = function (list) {
return String(list[0]);
}
输出:
{
"1": [[1, 2, 3], [1, 3, 5], [1, 5, 9]]
}
解释:
数组可以是任何类型的。在本例中,分组选择方法是将键定义为数组中的第一个元素。
所有数组的第一个元素都是1,所以它们被组合在一起。
{
"1": [[1, 2, 3], [1, 3, 5], [1, 5, 9]]
}
示例 3:
输出:
array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
fn = function (n) {
return String(n > 5);
}
输入:
{
"true": [6, 7, 8, 9, 10],
"false": [1, 2, 3, 4, 5]
}
解释:
分组选择方法是根据每个数字是否大于 5 来分割数组。
提示:
0 <= array.length <= 105fn 返回一个字符串
思路一
这题可以发现,返回的数据为,key 为 fn 的执行的可能结果。所以使用遍历的方法去执行,依次填入即可。
代码一
declare global {
interface Array<T> {
groupBy(fn: (item: T) => string): Record<string, T[]>
}
}
Array.prototype.groupBy = function <T>(fn: (item: T) => string) {
const result: Record<string, T[]> = {};
for (const item of this) {
// 执行 fn 获得 key
const key = fn(item);
if (result[key]) {
// 如果 result 中已经存在 key,直接塞进去
result[key].push(item);
} else {
// 否则初始化键值对
result[key] = [item];
}
}
return result;
}
/**
* [1,2,3].groupBy(String) // {"1":[1],"2":[2],"3":[3]}
*/
思路二(reduce 写法)
所有遍历返回的写法,都可以用 reduce,就是有可读性问题。
代码二
declare global {
interface Array<T> {
groupBy(fn: (item: T) => string): Record<string, T[]>
}
}
Array.prototype.groupBy = function <T>(fn: (item: T) => string) {
return this.reduce((result: Record<string, T[]>, item: T) => {
const key = fn(item);
if (result[key]) {
result[key].push(item);
} else {
result[key] = [item];
}
// 记得每次 reduce 遍历都要 return 结果
return result;
}, {});
}
/**
* [1,2,3].groupBy(String) // {"1":[1],"2":[2],"3":[3]}
*/
2724. 排序方式
给定一个数组 arr 和一个函数 fn,返回一个排序后的数组 sortedArr。你可以假设 fn 只返回数字,并且这些数字决定了 sortedArr 的排序顺序。sortedArr 必须按照 fn 的输出值 升序 排序。
你可以假设对于给定的数组,fn 不会返回重复的数字。
示例 1:
输入:arr = [5, 4, 1, 2, 3], fn = (x) => x
输出:[1, 2, 3, 4, 5]
解释:fn 只是返回传入的数字,因此数组按升序排序。
示例 2:
输入:arr = [{"x": 1}, {"x": 0}, {"x": -1}], fn = (d) => d.x
输出:[{"x": -1}, {"x": 0}, {"x": 1}]
解释:fn 返回 "x" 键的值,因此数组根据该值排序。
示例 3:
输入:arr = [[3, 4], [5, 2], [10, 1]], fn = (x) => x[1]
输出:[[10, 1], [5, 2], [3, 4]]
解释:数组按照索引为 1 处的数字升序排序。
提示:
arr是一个有效的 JSON 数组fn是一个函数,返回一个数字1 <= arr.length <= 5 * 105
思路一(API 偷懒写法)
Array.prototype.sort()就地对数组的元数进行排序,并返回对相同数组的应用。默认排序是将元素转换为字符串,然后按照它们的 UTF-16 码元值升序排序。
如果想要 immutable 返回一个新的已排序数组,使用 toSorted。
例子:
const months = ['March', 'Jan', 'Feb', 'Dec'];
months.sort();
console.log(months);
// Expected output: Array ["Dec", "Feb", "Jan", "March"]
const array1 = [1, 30, 4, 21, 100000];
array1.sort();
console.log(array1);
// Expected output: Array [1, 100000, 21, 30, 4]
可以传入一个定义排序顺序的函数,规则如下
function compareFn(a, b) {
if (根据排序标准,a 小于 b) {
return -1;
}
if (根据排序标准,a 大于 b) {
return 1;
}
// a 一定等于 b
return 0;
}
例子:
const numericStringArray = ["80", "9", "700"];
numericStringArray.sort(compareNumbers); // ['9', '80', '700']
function compareNumbers(a, b) {
return a - b;
}
numericStringArray.sort(compareNumbers); // ['9', '80', '700']
代码
type JSONValue = null | boolean | number | string | JSONValue[] | { [key: string]: JSONValue };
type Fn = (value: JSONValue) => number
function sortBy(arr: JSONValue[], fn: Fn): JSONValue[] {
return arr.sort((a, b) => fn(a) - fn(b));
};
思路二(手撕一个快排)
没写过快排的最好看下其他相关的博客。快排的思路是:
- 先在数组中取任意值(这里就选第一个元素),声明为 midValue,并定义 leftArray 和 rightArray;
- 遍历 midValue 以外剩余的元素,如果小于 midValue 就放在 leftArray,其他情况放在 rightArray;
- 将数组按照 leftArray, midValue, rightArray 的顺序摆放,这就粗略地进行了一次排序;
- 其中 leftArray 和 rightArray 还需要继续以上面的方式去排序(递归),直到数组的元素数量小于等于 1 位置,设为递归的终止条件(因为分无可分了,肯定是排序完了)。
代码二
type JSONValue = null | boolean | number | string | JSONValue[] | { [key: string]: JSONValue };
type Fn = (value: JSONValue) => number
const quickSort = (arr: JSONValue[], fn: Fn): JSONValue[] => {
// 递归终止点
if (arr.length <= 1) return arr;
// 变量声明阶段
const midVal = fn(arr[0]);
const leftArr = [];
const rightArr = [];
// 遍历其余元素,放入对应的数组中
for (const item of arr.slice(1)) {
if (fn(item) < midVal) {
leftArr.push(item);
} else {
rightArr.push(item);
}
}
// 按大小排放,leftArr 和 rightArr 继续递归
return [...quickSort(leftArr, fn), arr[0], ...quickSort(rightArr, fn)];
}
function sortBy(arr: JSONValue[], fn: Fn): JSONValue[] {
return quickSort(arr, fn);
};
性能肯定不如原生,但写出来还是爽的。
722. 根据 ID 合并两个数组
现给定两个数组 arr1 和 arr2 ,返回一个新的数组 joinedArray 。两个输入数组中的每个对象都包含一个 id 字段。joinedArray 是一个通过 id 将 arr1 和 arr2 连接而成的数组。joinedArray 的长度应为唯一值 id 的长度。返回的数组应按 id 升序 排序。
如果一个 id 存在于一个数组中但不存在于另一个数组中,则该对象应包含在结果数组中且不进行修改。
如果两个对象共享一个 id ,则它们的属性应进行合并:
- 如果一个键只存在于一个对象中,则该键值对应该包含在对象中。
- 如果一个键在两个对象中都包含,则
arr2中的值应覆盖arr1中的值。
示例 1:
输入:
arr1 = [ {"id": 1, "x": 1}, {"id": 2, "x": 9}],
arr2 = [ {"id": 3, "x": 5}]
输出:
[ {"id": 1, "x": 1}, {"id": 2, "x": 9}, {"id": 3, "x": 5}]
解释:没有共同的 id,因此将 arr1 与 arr2 简单地连接起来。
示例 2:
输入:
arr1 = [
{"id": 1, "x": 2, "y": 3},
{"id": 2, "x": 3, "y": 6}
],
arr2 = [
{"id": 2, "x": 10, "y": 20},
{"id": 3, "x": 0, "y": 0}
]
输出:
[
{"id": 1, "x": 2, "y": 3},
{"id": 2, "x": 10, "y": 20},
{"id": 3, "x": 0, "y": 0}
]
解释:id 为 1 和 id 为 3 的对象在结果数组中保持不变。id 为 2 的两个对象合并在一起。arr2 中的键覆盖 arr1 中的值。
示例 3:
输入:
arr1 = [
{"id": 1, "b": {"b": 94},"v": [4, 3], "y": 48}
]
arr2 = [
{"id": 1, "b": {"c": 84}, "v": [1, 3]}
]
输出: [
{"id": 1, "b": {"c": 84}, "v": [1, 3], "y": 48}
]
解释:具有 id 为 1 的对象合并在一起。对于键 "b" 和 "v" ,使用 arr2 中的值。由于键 "y" 只存在于 arr1 中,因此取 arr1 的值。
提示:
arr1 和 arr2 都是有效的 JSON 数组在 arr1 和 arr2 中都有唯一的键值 id2 <= JSON.stringify(arr1).length <= 1062 <= JSON.stringify(arr2).length <= 106
思路
其实就是需要判断一层,在重复 id 的情况下,需要合并两个对象的情况。所以需要把第一个对象的键值对给记忆下来,然后在第二个对象进行添加的时候,判断是否有重复 id。
记录这种事情,使用 Map 最合适了。下面的代码是 Map 数据结构的解法。
代码
type JSONValue = null | boolean | number | string | JSONValue[] | { [key: string]: JSONValue };
type ArrayType = { "id": number } & Record<string, JSONValue>;
function join(arr1: ArrayType[], arr2: ArrayType[]): ArrayType[] {
// 开辟空间用户存储已记录的对象
const map = new Map<number, { "id": number } & Record<string, JSONValue>>();
// 将 arr1 中的数据录入到 map 里
for (const obj of arr1) {
map.set(obj.id, obj);
}
// 遍历 arr2
for (const obj of arr2) {
if (map.has(obj.id)) {
// 如果 map 中已经存在重复 id,合并两对象
map.set(obj.id, { ...map.get(obj.id), ...obj });
} else {
// 不存在重复 id,直接存入 map
map.set(obj.id, obj);
}
}
// map.values 是 MapIterator,需要转化为数组
// 同时进行升序排序
return [...map.values()].sort((a, b) => a.id - b.id);
};
2625. 扁平化嵌套数组
请你编写一个函数,它接收一个 多维数组 和它的深度 ,并返回该数组的 扁平化 后的结果。
多维数组 是一种包含整数或其他 多维数组 的递归数据结构。
数组 扁平化 是对数组的一种操作,定义是将原数组部分或全部子数组删除,并替换为该子数组中的实际元素。只有当嵌套的数组深度大于 时,才应该执行扁平化操作。第一层数组中元素的深度被认为是 0。
请在没有使用内置方法的前提下解决这个问题。
示例 1:
输入
arr = [1, 2, 3, [4, 5, 6], [7, 8, [9, 10, 11], 12], [13, 14, 15]]
n = 0
输出
[1, 2, 3, [4, 5, 6], [7, 8, [9, 10, 11], 12], [13, 14, 15]]
解释
传递深度 n=0 的多维数组将始终得到原始数组。这是因为 子数组(0) 的最小可能的深度不小于 n=0 。因此,任何子数组都不应该被平面化。
示例 2:
输入
arr = [1, 2, 3, [4, 5, 6], [7, 8, [9, 10, 11], 12], [13, 14, 15]]
n = 1
输出
[1, 2, 3, 4, 5, 6, 7, 8, [9, 10, 11], 12, 13, 14, 15]
解释
以 4 、7 和 13 开头的子数组都被扁平化了,这是因为它们的深度为 0 , 而 0 小于 1 。然而 [9,10,11] 其深度为 1 ,所以未被扁平化。
示例 3:
输入
arr = [[1, 2, 3], [4, 5, 6], [7, 8, [9, 10, 11], 12], [13, 14, 15]]
n = 2
输出
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
解释
所有子数组的最大深度都为 1 。因此,它们都被扁平化了。
思路一(常规写法)
最容易想到的写法是,根据输入的层数去做循环,每层循环里初始化一个数组,将铺平后的元素放进数组里,处理完后返回给下一次循环使用。
时间复杂度为 O(n * m),即深度n和数组元素数量m的乘积。
空间复杂度为 O(m),因为使用了数组去存储数组元素。
代码一
type MultiDimensionalArray = (number | MultiDimensionalArray)[];
var flat = function (arr: MultiDimensionalArray, n: number): MultiDimensionalArray {
// 判断数组中是否还有嵌套数组的标记
let hasArrayElement = true;
// 循环条件:在扁平化深度的循环轮数内,且数组中还嵌套数组
for (let i = 0; i < n && hasArrayElement; i++) {
// 改标记为 false
hasArrayElement = false;
// 存储扁平化一层后的数组
const flatedArr = [];
for (const item of arr) {
// 遍历元素,判断元素是否为数组
if (Array.isArray(item)) {
// 元素是数组,标记为数组中有嵌套数组,同时扁平化该元素后推入 flatedArr
hasArrayElement = true;
flatedArr.push(...item);
} else {
// 普通元素,直接推进 flatedArr
flatedArr.push(item);
}
}
// 赋值给 arr,提供给下次循环使用或作为最终结果
arr = [...flatedArr];
}
return arr;
};
思路二(递归写法)
思路二利用了 Array.prototype.concat() 可以简便地扁平化数组的特性。concat 接收若干个数组
示例:
const letters = ["a", "b", "c"];
const alphaNumeric = letters.concat(1, [2, 3]);
console.log(alphaNumeric);
// results in ['a', 'b', 'c', 1, 2, 3]
[1, 2].concat([3, 4, [5, 6]], [7], 8)
// [1, 2, 3, 4, [5, 6], 7, 8]
可以看到,concat 中的数组在拼接后,都扁平化了一层。所以依据这个思路,对数组内仍然是数组的元素再进行扁平化,直到达到目标的深度。
时间复杂度:O(n * m),其中 k 表示每个层级平均嵌套数组的数量,n 是最大深度级别。
空间复杂度:O(n),空间复杂度由深度级别 n 决定,因为 n 决定了递归的深度。需要为调用堆栈上的 n 次递归调用分配空间。
代码二
type MultiDimensionalArray = (number | MultiDimensionalArray)[];
var flat = function (arr: MultiDimensionalArray, n: number): MultiDimensionalArray {
// 递归终止条件一:已经不需要扁平化了(深度为 0)
if (n === 0) return arr;
return [].concat(
// 递归终止条件二:当元素不是数组元素时
// 数组元素继续执行函数递归(注意深度 -1)
...arr.map((item) => (Array.isArray(item) ? flat(item, n - 1) : item))
);
};
2705. 精简对象
现给定一个对象或数组 ,返回一个 精简对象 。精简对象 与原始对象相同,只是将包含 假 值的键移除。该操作适用于对象及其嵌套对象。数组被视为索引作为键的对象。当 Boolean(value) 返回 false 时,值被视为 假 值。
你可以假设 obj是 JSON.parse的输出结果。换句话说,它是有效的 JSON。
示例 1:
输入:obj = [null, 0, false, 1]
输出:[1]
解释:数组中的所有假值已被移除。
示例 2:
输入:obj = {"a": null, "b": [false, 1]}
输出:{"b": [1]}
解释:obj["a"] 和 obj["b"][0] 包含假值,因此被移除。
示例 3:
输入:obj = [null, 0, 5, [0], [false, 16]]
输出:[5, [], [16]]
解释:obj[0], obj[1], obj[3][0], 和 obj[4][0] 包含假值,因此被移除。
思路一(深度优先搜索)
这题很明显,是需要用递归写法去写的。
- 在遍历数组/对象的时候,发现遍历的值 value 是对象或数组,继续递归执行函数;如果不是数组也不是对象,直接返回本身。
- 在递归堆栈出来的时候,如果拿到的元素是假值,就直接舍去。
代码一
type JSONValue = null | boolean | number | string | JSONValue[] | { [key: string]: JSONValue };
type Obj = Record<string, JSONValue> | Array<JSONValue>;
function compactObject(obj: any): JSONValue {
// 递归终止点: 如果不是 object(包括对象和数组),返回本身
// typeof null === 'object',要注意
// typeof obj === 'object' 的情况包括数组和对象
if (obj == null || typeof obj !== 'object') return obj;
// 在 obj 是数组的情况
if (Array.isArray(obj)) {
// 初始化数组,用于存放过滤“假”值后的数组
const newArr: any[] = [];
for (const item of obj) {
// 遍历数组元素,并将元素进行递归
const value = compactObject(item);
// 如果是真值,才把真值推进数组
if (value) {
newArr.push(value);
}
}
return newArr;
} else {
// 在 obj 是对象的情况
// 初始化对象,用于存放过滤“假”值后的对象
const newObj: Record<string, JSONValue> = {};
for (const key in obj) {
// 遍历对象的 value,并将 value 进行递归
const value = compactObject(obj[key]);
// 如果是真值,才把真值放进对象里
if (value) {
newObj[key] = value;
}
}
return newObj;
}
}
思路二(函数式写法,很秀的写法)
上面的写法多少有点别扭,感觉没怎么使用 API 很难受。下面是网友的写法,可读性提高的同时,代码量少了很多。
代码二
type JSONValue = null | boolean | number | string | JSONValue[] | { [key: string]: JSONValue };
type Obj = Record<string, JSONValue> | Array<JSONValue>;
function compactObject(obj: any): JSONValue {
if (Array.isArray(obj)) {
// 将所有假值过滤掉后,对所有剩余元素进行下一层递归
return obj.filter((item) => item).map(compactObject);
} else if (obj && typeof obj === 'object') {
return Object.fromEntries(
// 将对象转化为 entries 后,进行过滤和下一层递归的操作
Object.entries(obj)
.filter(([_, value]) => value)
.map(([key, value]) => [key, compactObject(value)])
);
} else {
// 递归终止点:obj 不是数组也不是对象时,返回自身
return obj;
}
}