# JSON
# 判断对象是否为空
给定一个对象或数组,判断它是否为空。
- 一个空对象不包含任何键值对。
- 一个空数组不包含任何元素。
- 你可以假设对象或数组是通过 JSON.parse 解析得到的。
# 示例 1:
输入:obj = {"x": 5, "y": 42}
输出:false
解释:这个对象有两个键值对,所以它不为空。
# 示例 2:
输入:obj = {}
输出:true
解释:这个对象没有任何键值对,所以它为空。
# 示例 3:
输入:obj = [null, false, 0]
输出:false
解释:这个数组有 3 个元素,所以它不为空。
# 题解
# 方法 1:使用 JSON.stringify
/**
* 判断数组或对象是否为空
* @param {object|array} val 用于判断的值
* @return {Boolean|Number} 如果该对象或数组为空则返回false 否则返回数组或对象的长度
*/
const isEmpty = (val) => {
if (typeof val !== "object") throw new Error("必须是数组或对象");
const newVal = JSON.stringify(val);
if (["{}", "[]"].includes(newVal)) return false;
if (Object.prototype.toString.call(val) === "[object Array]") {
return val.length;
}
if (Object.prototype.toString.call(val) === "[object Object]") {
return Object.keys(val).length;
}
};
console.log("结果", isEmpty([])); //false
console.log("结果", isEmpty([5])); //1
console.log("结果", isEmpty({name: 123})); //1
# 方法二 使用Object.keys()
/**
* 判断数组或对象是否为空
* @param {object|array} val 用于判断的值
* @return {Boolean|Number} 如果该对象或数组为空则返回false 否则返回数组或对象的长度
*/
const isEmpty = (val) => {
if (typeof val !== "object") throw new Error("必须是数组或对象");
const newVal = Object.keys(val).length;
if (newVal === 0) return false;
return newVal;
};
console.log("结果", isEmpty([])); //false
console.log("结果", isEmpty([5])); //1
console.log("结果", isEmpty({name: 123})); //1
注意
- Object.keys() 方法。此方法返回对象自身可枚举属性名(键)的数组。
- 虽然
Object.keys()名义上是对象的方法,但typeOf对象或数组结果都是Object - 因此数组和对象都属于
Object Object.keys(对象)返回属性的key值Object.keys(数组)将数组的索引值当key值返回
# 方法 3:使用循环
如果数组/对象不为空,解释器将进入 for-in 循环,因此将运行第一个返回语句 false。如果为空,解释器将不进入 for-in 循环,因此将执行第二个返回语句 true。
var isEmpty = function (obj) {
for (const _ in obj) return false;
return true;
};
# 分块数组
给定一个数组 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
输出:[]
解释:没有元素需要分块,因此返回一个空数组。
# 题解
# 方法 1:使用暴力方法
我们可以使用嵌套的 while 循环来迭代输入数组并形成指定大小的块。外部循环可以控制输入数组的索引,而内部循环可以向临时数组添加元素,直到达到期望的块大小或达到输入数组的末尾。然后,将临时数组添加到块数组。这个过程会一直持续,直到所有元素都被处理。
算法:
- 将 chunkedArray 初始化为空数组。
- 我们使用 while 循环来迭代数组,条件是 i < arr.length。
- 在每次迭代中,创建一个 temp 数组来保存每个块的元素。
- 使用内部的 while 循环,条件是 len-- > 0 && i < arr.length,向 temp 数组添加元素。
- 在添加元素到 temp 时检查是否到达数组的末尾,以处理最后一个块。
- 现在将 temp 数组作为子数组添加到 chunkedArray。
- 返回 chunkedArray。简而言之:使用嵌套的 while 循环迭代数组并形成块。
/**
* @param {Array} arr
* @param {number} size
* @return {Array[]}
*/
var chunk = function (arr, size) {
const chunkedArray = [];
let index = 0;
while (index < arr.length) {
let count = size;
const temp = [];
while (count-- > 0 && index < arr.length) {
temp.push(arr[index]);
index++;
}
chunkedArray.push(temp);
}
return chunkedArray;
};
# 方法 2:使用切片
思路:
我们可以使用 slice 方法根据当前索引和指定的大小从输入数组中提取一个块。slice 方法创建一个从当前索引开始到当前索引加上块大小的数组浅拷贝。然后将块添加到块数组,并将索引增加块大小。这个过程会一直持续,直到所有元素都被处理。
算法:
- 我们使用 while 循环来迭代数组。
- 在每次迭代中,使用 arr.slice(index, index + size) 将块添加到 chunkedArray。
- 然后在每次迭代后将索引增加块大小。
- 在增加索引后继续迭代,直到达到数组的末尾。
- 最后返回 chunkedArray。简而言之:我们可以使用 slice 方法根据索引和大小提取数组的块。
/**
* @param {Array} arr
* @param {number} size
* @return {Array[]}
*/
var chunk = function (arr, size) {
const chunkedArray = [];
let index = 0;
while (index < arr.length) {
chunkedArray.push(arr.slice(index, index + size));
index += size;
}
return chunkedArray;
};
# 方法 3:使用 Splice 和 Slice
思路:
我们可以使用嵌套循环来迭代输入数组并形成块。外部循环通过增加块大小来逐步迭代数组,而内部循环使用 splice 方法向一个临时数组添加元素。如果到达输入数组的末尾,使用 splice 方法将临时数组中的剩余元素移除。然后,将临时数组添加到块数组。循环结束后,使用 slice(1) 移除块数组中的第一个空子数组。
算法:
- 将 chunkedArray 初始化为包含一个空子数组的数组 [[]]。
- 维护一个临时数组 temp 来保存每个块的元素。
- 外部循环从数组的索引 j 开始,每次增加 size。
- 内部循环遍历当前块的大小 size,并使用 arr[j + i] 向 temp 添加元素。
- 现在,在添加元素到 temp 时检查是否到达数组的末尾,并使用 temp.splice(j) 移除 temp 中的任何剩余元素。
- 然后使用展开运算符 [...b, [...a]] 将 temp 数组作为子数组添加到 chunkedArray。
- 最后返回 chunkedArray,使用 b.slice(1) 移除块数组中的第一个空子数组。简而言之:使用 splice 和 slice 以及 while 循环迭代数组并形成块。
/**
* @param {Array} arr
* @param {number} size
* @return {Array[]}
*/
var chunk = function (arr, size) {
let chunkedArray = [[]];
let temp = [];
for (let i = 0; i < arr.length; i = i + size) {
for (let j = 0; j < size; j++) {
temp[j] = arr[j + i];
if (j + i === arr.length) {
temp.splice(j);
break;
}
}
chunkedArray = [...chunkedArray, [...temp]];
}
return chunkedArray.slice(1);
};
# 方法 4:使用 Reduce
思路:
在这种方法中,我们使用 reduce 函数来迭代输入数组并构建分块数组。reduce 函数接受一个初始值为一个空数组 ([]) 和一个回调函数。回调函数检查分块数组中的最后一个块。如果最后一个块不存在或其长度等于块大小,将创建一个新块并将当前元素添加到最后一个块中。每次迭代都会返回更新后的分块数组。
算法:
- 将 chunkedArray 初始化为空数组。
- 在 reduce 函数的每次迭代中:
- 使用 chunkedArray[chunkedArray.length - 1] 检查分块数组中的最后一个块。
- 如果最后一个块不存在或其长度等于块大小,将创建一个新块 chunk 并将当前元素添加到最后一个块中。
- 否则,将当前元素追加到最后一个块中。
- 最后在每次迭代中返回更新后的分块数组。
- 简而言之:使用 reduce 函数迭代数组并构建分块数组。
/**
* @param {Array} arr
* @param {number} size
* @return {Array[]}
*/
var chunk = function (arr, size) {
return arr.reduce((chunkedArray, element) => {
const lastChunk = chunkedArray[chunkedArray.length - 1];
if (!lastChunk || lastChunk.length === size) {
chunkedArray.push([element]);
} else {
lastChunk.push(element);
}
return chunkedArray;
}, []);
};
# 方法 5:使用 Push
思路:
在这种方法中,我们使用 for...of 循环迭代输入数组。它维护一个 currentChunk 数组来保存当前块的元素。当 currentChunk 达到指定大小时,将其添加到结果数组,并创建一个新的 currentChunk。在循环结束时,如果 currentChunk 不为空,将其中剩余的元素添加到结果数组。
算法:
- 维护一个 result 数组来保存分块数组。
- 使用 currentChunk 数组来存储正在构建的当前块的元素。
- 对于数组中的每个元素:
- 如果 currentChunk 达到指定大小:
- 将 currentChunk 添加到 result 数组。
- 创建一个新的 currentChunk 并将元素添加到其中。
- 否则,将元素追加到 currentChunk。
- 如果 currentChunk 中有剩余元素,则将它们添加到 result 数组。
- 最后返回 result 数组。
- 简而言之:使用 for...of 循环迭代数组,使用 push 方法将块添加到结果数组。
/**
* @param {Array} arr
* @param {number} size
* @return {Array[]}
*/
var chunk = function (arr, size) {
const result = [];
let currentChunk = [];
for (const element of arr) {
if (currentChunk.length === size) {
result.push(currentChunk);
currentChunk = [];
}
currentChunk.push(element);
}
if (currentChunk.length) result.push(currentChunk);
return result;
};
# 方法 6:使用 Ceiling
思路:
在这种方法中,我们使用 Array.from 方法来创建一个新数组,其长度由输入数组的长度除以块大小决定,并使用 Math.ceil 向上舍入。Array.from 方法还接受一个映射函数,该函数使用 slice 方法根据索引和块大小提取输入数组的相应部分来创建每个块。
算法:
- 使用 Array.from 来创建一个长度等于所需块数的新数组。
- 对新数组的每个元素使用回调函数来创建每个块。
- 回调函数使用 arr.slice(index * size, index * size + size) 来提取每个块的数组部分。
- 最后返回结果分块数组。
- 简而言之:使用 Math.ceil(arr.length / size) 确定所需的块数。
/**
* @param {Array} arr
* @param {number} size
* @return {Array[]}
*/
var chunk = function (arr, size) {
return Array.from({length: Math.ceil(arr.length / size)}, function (_, index) {
return arr.slice(index * size, index * size + size);
});
};
# 数组原型对象的最后一个元素
请你编写一段代码实现一个数组方法,使任何数组都可以调用 array.last() 方法,这个方法将返回数组最后一个元素。如果数组中没有元素,则返回 -1 。
你可以假设数组是 JSON.parse 的输出结果。
# 示例 1 :
输入:nums = [null, {}, 3]
输出:3
解释:调用 nums.last() 后返回最后一个元素: 3。
# 示例 2 :
输入:nums = []
输出:-1
解释:因为此数组没有元素,所以应该返回 -1。
# 题解
# 方法 1:扩展数组原型以包含 .last() 方法
Array.prototype.last = function () {
if (this.length === 0) return -1;
return this[this.length - 1];
};
console.log([].last); //-1
console.log([1, 2, 3, 4, 5].last); //5
# 方法 2:使用 ES6 Getters
Object.defineProperty(Array.prototype, "last", {
get: function () {
return () => (this.length ? this[this.length - 1] : -1);
},
});
console.log([].last); //-1
console.log([1, 2, 3, 4, 5].last); //5
# 分组
请你编写一段可应用于所有数组的代码,使任何数组调用 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 来分割数组。
# 方法一 使用map
const groupBy = (arr, key) => {
const map = {};
arr.forEach((element) => {
if (!map[key(element)]) map[key(element)] = [];
map[key(element)].push(element);
});
return map;
};
const arr1 = [
[1, 2, 3],
[1, 3, 5],
[2, 5, 9],
];
const fn1 = (list) => String(list[0]);
console.log("结果", groupBy(arr1, fn1));
# 方法二 使用reduce
const groupBy = (arr, key) => {
return arr.reduce((map, element) => {
const k = key(element);
if (!map[k]) map[k] = [];
map[k].push(element);
return map;
}, {});
};
# 排序方式
给定一个数组 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 处的数字升序排序。
# 题解
# 使用sort进行排序
const sort = (arr, fn) => arr.sort((a, b) => fn(a) - fn(b));
注意
- Array.prototype.sort() 方法对数组的元素进行排序,并返回数组。它使用原地算法,对数组的元素进行直接修改,会改变原始数组
- 在比较函数中,将元素 b 的可排序值减去元素 a 的可排序值。
- 如果结果为正数,a 将被排序到比 b 更高的索引处(即 a 在 b 之后)。
- 如果为负数,a 将被排序到比 b 更低的索引处(即 a 在 b 之前)。
- 如果结果为 0,a 和 b 的相对位置保持不变。
# 根据 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 的值。
# 题解
我们需要基于它们的 "id" 键合并两个数组 arr1 和 arr2。合并后的数组 joinedArray 应包含来自两个数组的所有唯一对象,并按其 id 值的升序排序。当对象具有相同的 id 时,它们的属性应合并,arr2 中的值将覆盖 arr1 中的值。如果一个 id 只存在于一个数组中,那么该 id 对应的对象应无修改地包含在结果中。
# 方法 1:暴力法
我们首先创建一个新数组 combinedArray,通过组合 arr1 和 arr2 的内容来确保包含来自两个数组的所有对象。
接下来,我们初始化一个名为 merged 的空对象。该对象将用作容器,以存储基于其 ID 作为键的合并对象。
然后,我们使用 forEach 方法迭代 combinedArray 中的每个对象。对于每个对象,我们检查它的 ID 是否已存在于 merged 对象中作为键。
如果 ID 在 merged 对象中不存在,我们将向 merged 对象添加一个新的键值对。键是 ID,值是通过创建当前对象的副本 (...obj) 而生成的新对象。这确保了 merged 对象中的每个对象都有自己独立的副本。
但是,如果 ID 已经存在于 merged 对象中,这意味着已经存在具有相同 ID的另一个对象。在这种情况下,我们执行属性的合并。我们通过复制其属性 (...merged[id]) 并然后用当前对象的属性 (...obj) 覆盖它们来更新merged 中的现有对象。这确保了合并过程中 arr2 中的属性优先于 arr1。
在合并所有对象之后,我们使用 Object.values() 从 merged 对象中提取值。这会创建一个数组 joinedArray,其中只包含已合并对象,而不包含 ID 键。
最后,我们使用基于 ID 键的升序对 joinedArray 进行排序。我们使用 sort 方法和比较函数 (a, b) => a.id - b.id 来确定正确的排序顺序。比较函数比较两个对象的 ID 并确定它们在排序数组中的位置。
最终,我们将排序后的 joinedArray 作为 join 函数的最终结果返回。
/**
* @param {Array} arr1
* @param {Array} arr2
* @return {Array}
*/
var join = function (arr1, arr2) {
const combinedArray = arr1.concat(arr2);
const merged = {};
combinedArray.forEach((obj) => {
const id = obj.id;
if (!merged[id]) {
merged[id] = {...obj};
} else {
merged[id] = {...merged[id], ...obj};
}
});
const joinedArray = Object.values(merged);
joinedArray.sort((a, b) => a.id - b.id);
return joinedArray;
};
# 方法 2:使用 Map
我们可以使用 Map 来合并两个数组 arr1 和 arr2。我们将 arr1 中的所有对象添加到 Map 中,然后根据它们的 ID 合并 arr2 中的对象。合并后的对象存储在名为 res 的数组中。最后,我们根据 ID 属性将 res 数组按升序排序。
# 算法
我们首先创建一个新的 Map 对象,命名为 map。Map 用于高效地存储和检索键-值对。
使用 for-of 循环,我们遍历 arr1 中的每个对象。对于每个对象,我们将其 ID 设置为键,将整个对象设置为 map 中的值。
接下来,我们使用另一个 for-of 循环来遍历 arr2 中的每个对象。对于每个对象,我们检查其 ID 是否已经存在于 map 中。
如果该 ID 不存在于 map 中,我们将该 ID 设置为键,将整个对象设置为 map 中的值。这确保了在不修改的情况下将对象包含在 res 数组中。
但是,如果 ID 已经存在于 map 中,我们使用 map.get(obj.id) 获取现有对象。然后,我们使用 Object.keys(obj) 来迭代当前对象的每个属性。
对于每个属性,我们使用当前对象的值更新现有对象的相应属性。此合并过程确保当对象具有相同的 ID 时,arr2 中的值将覆盖 arr1 中的值。
在合并所有对象后,我们创建一个名为 res 的空数组,用于存储最终结果。
我们使用 map.keys() 来迭代 map 的键。对于每个键,我们使用 map.get(key) 检索相应的对象,并将其推送到 res 数组中。
最后,我们使用 sort 方法和比较函数 (a, b) => a.id - b.id 来根据 ID 属性的升序对 res 数组进行排序。
最终,我们将排序后的 res 数组作为 join 函数的最终结果返回。
var join = function (arr1, arr2) {
const map = new Map();
for (const obj of arr1) map.set(obj.id, obj);
for (const obj of arr2) {
if (!map.has(obj.id)) map.set(obj.id, obj);
else {
const prevObj = map.get(obj.id);
for (const key of Object.keys(obj)) prevObj[key] = obj[key];
}
}
const res = new Array();
for (let key of map.keys()) res.push(map.get(key));
return res.sort((a, b) => a.id - b.id);
};
# 方法 3:使用双指针
# 概述
主要思路类似于合并两个已排序数组。我们遍历已排序的 arr1 和 arr2,比较它们的 ID 并将对象添加到结果的 joinedArray 中,然后根据需要增加 i 或 j 指针。一旦一个数组被完全处理,就插入另一个数组中的剩余对象。
# 算法
我们首先根据其 ID 对 arr1 和 arr2 进行升序排序。这确保在合并过程中以一致的顺序处理对象。
初始化一个名为 joinedArray 的空数组,用于存储合并的对象。
我们使用两个指针 i 和 j,来跟踪当前在 arr1 和 arr2 中的位置。我们从 i = 0 和 j = 0 开始。
进入一个 while 循环,直到达到 arr1 或 arr2 的末尾。在循环内部,我们比较当前位置的对象的 ID(arr1[i].id 和 arr2[j].id)。
如果 arr1 中的 ID 小于 arr2 中的 ID,我们将 arr1 中的对象添加到 joinedArray,并递增 i,以继续处理 arr1 中的下一个对象。
如果 arr1 中的 ID 大于 arr2 中的 ID,我们将 arr2 中的对象添加到 joinedArray,并递增 j,以继续处理 arr2 中的下一个对象。
如果 ID 相同,我们合并这两个对象的属性。我们通过将 arr1[i] 的属性展开(...arr1[i])然后用 arr2[j] 的相应值覆盖来创建一个新对象。合并的对象添加到 joinedArray,然后递增 i 和 j,以继续处理两个数组中的下一个对象。
在 while 循环结束后,我们检查是否还有未处理的对象在 arr1 或 arr2 中。如果有,我们进入单独的 while 循环,将剩余的对象添加到 joinedArray。
最后,我们返回 joinedArray,其中包含来自两个数组的所有合并对象。
/**
* @param {Array} arr1
* @param {Array} arr2
* @return {Array}
*/
var join = function (arr1, arr2) {
arr1.sort((a, b) => a.id - b.id)
arr2.sort((a, b) => a.id - b.id)
let i = 0
let j = 0
const joinedArray = []
while (i < arr1.length && j < arr2.length) {
if (arr1[i].id === arr2[j].id) {
joinedArray.push({...arr1[i], ...arr2[j]})
i++
j++
continue
}
if (arr1[i].id < arr2[j].id) {
joinedArray.push({...arr1[i]})
i++
continue
}
joinedArray.push({...arr2[j]})
j++
}
while (i < arr1.length) {
joinedArray.push({...arr1[i]})
i++
}
while (j < arr2.length) {
joinedArray.push({...arr2[j]})
j++
}
return joinedArray
};
# 方法 4:使用 reduce
const joinedArray = (arr1, arr2) =>
Object.values(
[...arr1, ...arr2].reduce(
(a, b) => ((a[b.id] = Object.assign(a[b.id] ?? {}, b)), a),
{}
)
).sort((a, b) => a.id - b.id);
# 扁平化嵌套数组
请你编写一个函数,它接收一个 多维数组 arr 和它的深度 n ,并返回该数组的 扁平化 后的结果。
多维数组 是一种包含整数或其他 多维数组 的递归数据结构。
数组 扁平化 是对数组的一种操作,定义是将原数组部分或全部子数组删除,并替换为该子数组中的实际元素。只有当嵌套的数组深度大于 n 时,才应该执行扁平化操作。第一层数组中元素的深度被认为是 0。
请在没有使用内置方法 Array.flat 的前提下解决这个问题。
# 示例 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 。因此,它们都被扁平化了。
# 题解
# 方法一 利用reduce 递归
const flat = (arr, n) => {
if (n === 0) return arr;
return flat(
arr.reduce(
(pre, item) => [...pre, ...(Array.isArray(item) ? item : [item])],
[]
),
--n
);
};
# 精简对象
现给定一个对象或数组 obj,返回一个 精简对象 。精简对象 与原始对象相同,只是将包含 假 值的键移除。该操作适用于对象及其嵌套对象。数组被视为索引作为键的对象。当 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] 包含假值,因此被移除。
# 题解
# 方法 1:递归深度优先搜索(DFS)
# 概述
在这种方法中,我们使用递归的深度优先搜索(DFS)
的概念。主要思想是递归地遍历对象的深度并重建对象或数组,而不包含任何假值。
# 算法
- 基本情况:如果当前值是假值,则返回 false。如果当前值不是对象,则返回该值。
- 处理数组:如果当前值是数组,我们遍历数组并递归处理每个项。如果递归调用的返回值为真,我们将其添加到一个新数组中。
- 处理对象:如果当前值是对象,我们遍历对象的键,并递归处理每个值。如果递归调用的返回值为真,我们将其添加到一个新对象中。
- 返回结果:最后,我们返回清理后的对象或数组。
# 实现
function compactObject(obj) {
function dfs(obj) {
if (!obj) return false;
if (typeof obj !== 'object') return obj;
if (Array.isArray(obj)) {
const newArr = [];
for (let i = 0; i < obj.length; i++) {
const curr = obj[i];
const subRes = dfs(curr);
if (subRes) {
newArr.push(subRes);
}
}
return newArr;
}
const newObj = {};
for (const key in obj) {
const subRes = dfs(obj[key]);
if (subRes) {
newObj[key] = subRes;
}
}
return newObj;
}
return dfs(obj);
}
# 方法 2:迭代深度优先搜索
# 思路
在处理嵌套对象时,我们可能会选择使用递归,因为递归简单而优雅。然而,递归也伴随着一系列挑战,例如在处理大型输入时可能导致堆栈溢出。因此,在这种情况下,使用手动管理的堆栈的迭代方法可能更有利。
# 算法
- 初始化一个堆栈数据结构,将输入对象添加到堆栈中。同时,创建一个新对象,它将在遍历原始对象时被填充。
- 迭代深度探索:只要堆栈上仍然有对象,就从堆栈中弹出一个对象。对于对象中的每个键值对,检查值是否为对象或数组。如果值是对象或数组,用新的空对象或数组替换副本中的相应值,并将该值添加到堆栈中。
- 守卫语句:在迭代期间,我们忽略值为假值的键值对(或数组中的索引)。
- 最终输出:一旦堆栈为空,表示我们已经探索了输入中的所有对象和数组。此时,我们的副本已被修改,只包含具有真值的键(或数组中的索引),因此我们返回它。
# 实现
function compactObject(obj) {
const stack = [[obj, Array.isArray(obj) ? [] : {}]];
let newObj = stack[0][1];
while (stack.length > 0) {
const [currObj, newCurrObj] = stack.pop();
for (const key in currObj) {
const val = currObj[key];
if (!val) continue;
if (typeof val !== 'object') {
Array.isArray(newCurrObj) ? newCurrObj.push(val) : newCurrObj[key] = val;
continue;
}
const newSubObj = Array.isArray(val) ? [] : {};
Array.isArray(newCurrObj) ? newCurrObj.push(newSubObj) : newCurrObj[key] = newSubObj;
stack.push([val, newSubObj]);
}
}
return newObj;
}
# 方法 3.使用entries 参数归一
const compactObject = (obj) => {
if (!obj || typeof obj !== "object") return obj;
let newObj = [];
Object.entries(obj).forEach(([key, value]) => {
if (value) newObj.push([key, compactObject(value)]);
});
if (!Array.isArray(obj)) return Object.fromEntries(newObj);
return newObj.map((item) => item[1]);
};
← Promises 和 Time 类 →