目录
2722. 根据 ID 合并两个数组
2723. 添加两个 Promise 对象
2724. 排序方式
2725. 间隔取消
2726. 使用方法链的计算器
2727. 判断对象是否为空
2624. 蜗牛排序
2694. 事件发射器
2722. 根据 ID 合并两个数组
现给定两个数组 arr1
和 arr2
,返回一个新的数组 joinedArray
。两个输入数组中的每个对象都包含一个 id
字段。joinedArray
是一个通过 id
将 arr1
和 arr2
连接而成的数组。joinedArray
的长度应为唯一值 id
的长度。返回的数组应按 id
升序 排序。
如果一个 id
存在于一个数组中但不存在于另一个数组中,则该对象应包含在结果数组中且不进行修改。
如果两个对象共享一个 id
,则它们的属性应进行合并:
- 如果一个键只存在于一个对象中,则该键值对应该包含在对象中。
- 如果一个键在两个对象中都包含,则
arr2
中的值应覆盖arr1
中的值。
示例
输入: 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 简单地连接起来。
题解
var join = function (arr1, arr2) {
// 合并两个数组
const mergedArray = arr1.concat(arr2);
// 建立一个用于存储结果的 Map
const resultMap = new Map();
// 遍历合并后的数组
for (const obj of mergedArray) {
// 获取当前对象的 id
const id = obj.id;
// 如果 resultMap 中已经存在当前 id,则合并对象的属性
if (resultMap.has(id)) {
const existingObj = resultMap.get(id);
resultMap.set(id, { ...existingObj, ...obj });
}
// 否则,将当前对象作为新的键值对添加到 resultMap 中
else {
resultMap.set(id, { ...obj });
}
}
// 将 resultMap 中的对象按 id 升序转换成数组
const joinedArray = Array.from(resultMap.values()).sort((a, b) => a.id - b.id);
return joinedArray;
}
这段代码定义了一个函数 join
,接受两个数组作为参数 arr1
和 arr2
。
代码首先通过 arr1.concat(arr2)
将两个数组合并成一个数组 mergedArray
。
接下来,代码创建了一个用于存储结果的 Map 对象 resultMap
。
然后,代码通过遍历合并后的数组 mergedArray
,获取每个对象的属性 id
。
如果 resultMap
中已经存在当前 id
,则将已存在的对象与当前对象进行属性合并,并将合并后的对象重新存入 resultMap
中。
否则,将当前对象作为新的键值对添加到 resultMap
中。
最后,将 resultMap
中的对象按照 id
进行升序排列,并转换为数组 joinedArray
。
最后,函数返回 joinedArray
。
2723. 添加两个 Promise 对象
给定两个 promise 对象 promise1
和 promise2
,返回一个新的 promise。promise1
和 promise2
都会被解析为一个数字。返回的 Promise 应该解析为这两个数字的和。
示例
输入: promise1 = new Promise(resolve => setTimeout(() => resolve(2), 20)), promise2 = new Promise(resolve => setTimeout(() => resolve(5), 60)) 输出:7 解释:两个输入的 Promise 分别解析为值 2 和 5。返回的 Promise 应该解析为 2 + 5 = 7。返回的 Promise 解析的时间不作为判断条件。
题解
var addTwoPromises = async function (promise1, promise2) {
return Promise.all([promise1, promise2])
.then(([num1, num2]) => {
const sum = num1 + num2;
return Promise.resolve(sum);
});
};
这段代码定义了一个名为addTwoPromises的异步函数,它接受两个参数promise1和promise2。函数内部利用Promise.all方法,将这两个参数传入一个数组中,并返回一个新的Promise。在这个Promise被解析后,通过回调函数中的解构赋值将num1和num2分别赋值为数组中的两个元素。然后,通过将num1和num2相加得到sum,并通过Promise.resolve方法返回一个解析后的Promise。最后,这个解析后的Promise的值将作为addTwoPromises函数的返回值。、
2724. 排序方式
给定一个数组 arr
和一个函数 fn
,返回一个排序后的数组 sortedArr
。你可以假设 fn
只返回数字,并且这些数字决定了 sortedArr
的排序顺序。sortedArr
必须按照 fn
的输出值 升序 排序。
你可以假设对于给定的数组,fn
不会返回重复的数字。
示例
输入:arr = [5, 4, 1, 2, 3], fn = (x) => x 输出:[1, 2, 3, 4, 5] 解释:fn 只是返回传入的数字,因此数组按升序排序。
题解
var sortBy = function (arr, fn) {
// 使用数组的 sort 方法进行排序
arr.sort((a, b) => {
// 根据 fn 函数的返回值进行比较
const valA = fn(a);
const valB = fn(b);
if (valA < valB) {
return -1;
} else if (valA > valB) {
return 1;
} else {
return 0;
}
});
// 返回排序后的数组
return arr;
};
这段代码定义了一个名为sortBy的函数,该函数接收两个参数arr和fn。arr是要排序的数组,fn是用于返回比较值的函数。
在sortBy函数内部,使用数组的sort方法对arr进行排序。sort方法接受一个比较函数作为参数,该函数用于确定元素的排序顺序。
比较函数使用fn函数的返回值来进行元素比较。首先,将fn应用于数组中的两个元素a和b,得到valA和valB。然后,根据valA和valB的大小关系进行比较。如果valA小于valB,返回-1,表示a应该在b之前。如果valA大于valB,返回1,表示b应该在a之前。如果valA等于valB,返回0,表示a和b的相对位置不变。
最后,sortBy函数返回排序后的数组arr。
2725. 间隔取消
现给定一个函数 fn
,一个参数数组 args
和一个时间间隔 t
,返回一个取消函数 cancelFn
。
函数 fn
应该立即使用 args
调用,并且在每个 t
毫秒内再次调用,直到调用 cancelFn
。
示例
输入:fn = (x) => x * 2, args = [4], t = 20, cancelT = 110 输出: [ {"time": 0, "returned": 8}, {"time": 20, "returned": 8}, {"time": 40, "returned": 8}, {"time": 60, "returned": 8}, {"time": 80, "returned": 8}, {"time": 100, "returned": 8} ] 解释: const cancel = cancellable(x => x * 2, [4], 20); setTimeout(cancel, cancelT); 每隔 20ms,调用 fn(4)。 第一次调用 fn 是在 0ms。fn(4) 返回 8。 第二次调用 fn 是在 20ms。fn(4) 返回 8。 第三次调用 fn 是在 40ms。fn(4) 返回 8。 第四次调用 fn 是在 60ms。fn(4) 返回 8。 第五次调用 fn 是在 80ms。fn(4) 返回 8。 第六次调用 fn 是在 100ms。fn(4) 返回 8。 在 t=110ms 时取消。
题解
var cancellable = function (fn, args, t) {
// 调用初始函数
fn(...args);
const intervalId = setInterval(() => {
fn(...args);
}, t);
// 返回取消函数
const cancelFn = () => {
clearInterval(intervalId);
};
return cancelFn;
};
这段代码定义了一个名为cancellable的函数,它接受三个参数:fn(函数)、args(函数的参数数组)和t(时间间隔)。这段代码的作用是创建一个可取消的定时器。
首先,在函数内部,我们通过调用初始函数fn并传入参数args来立即执行一次函数。
然后,我们使用setInterval函数创建了一个定时器,该定时器会每隔t毫秒调用一次函数fn并传入参数args。
接下来,我们定义了一个匿名函数cancelFn,它的作用是清除之前设定的定时器,通过调用clearInterval函数并传入定时器的ID(intervalId)来实现。
最后,我们将cancelFn函数作为返回值,以便用户调用并取消之前设定的定时器。
所以,这段代码的功能是创建一个可取消的定时器,可以设置定时器的间隔时间,并提供一个函数用于取消定时器。
2726. 使用方法链的计算器
设计一个类 Calculator
。该类应提供加法、减法、乘法、除法和乘方等数学运算功能。同时,它还应支持连续操作的方法链式调用。Calculator
类的构造函数应接受一个数字作为 result
的初始值。
你的 Calculator
类应包含以下方法:
add
- 将给定的数字value
与result
相加,并返回更新后的Calculator
对象。subtract
- 从result
中减去给定的数字value
,并返回更新后的Calculator
对象。multiply
- 将result
乘以给定的数字value
,并返回更新后的Calculator
对象。divide
- 将result
除以给定的数字value
,并返回更新后的Calculator
对象。如果传入的值为0
,则抛出错误"Division by zero is not allowed"
。power
- 计算result
的幂,指数为给定的数字value
,并返回更新后的Calculator
对象。(result = result ^ value
)getResult
- 返回result
的值。
结果与实际结果相差在 10-5
范围内的解被认为是正确的。
输入:actions = ["Calculator", "add", "subtract", "getResult"], values = [10, 5, 7] 输出:8 解释: new Calculator(10).add(5).subtract(7).getResult() // 10 + 5 - 7 = 8
题解
class Calculator {
constructor(result) {
this.result = result;
}
add(value) {
this.result += value;
return this;
}
subtract(value) {
this.result -= value;
return this;
}
multiply(value) {
this.result *= value;
return this;
}
divide(value) {
if (value === 0) {
throw new Error("Division by zero is not allowed");
}
this.result /= value;
return this;
}
power(value) {
this.result = Math.pow(this.result, value);
return this;
}
getResult() {
return this.result;
}
}
这段代码定义了一个名为Calculator的类,它具有以下功能:
-
constructor(result): 这是一个构造函数,用于初始化Calculator类的实例并设置result属性的初始值。
-
add(value): 这是一个方法,用于将给定的value添加到result属性的值上,然后返回当前Calculator实例。这使得可以链式调用这个方法。
-
subtract(value): 这是一个方法,用于从result属性的值中减去给定的value,然后返回当前Calculator实例。
-
multiply(value): 这是一个方法,用于将result属性的值乘以给定的value,然后返回当前Calculator实例。
-
divide(value): 这是一个方法,用于将result属性的值除以给定的value,然后返回当前Calculator实例。如果value为0,则抛出一个错误。
-
power(value): 这是一个方法,用于将result属性的值提升到给定的value次方,然后返回当前Calculator实例。
-
getResult(): 这是一个方法,用于返回result属性的当前值。
通过使用这些方法,可以使用Calculator类进行基本的计算操作,并将多个操作链接在一起来获得最终结果。
2727. 判断对象是否为空
给定一个对象或数组,判断它是否为空。
- 一个空对象不包含任何键值对。
- 一个空数组不包含任何元素。
你可以假设对象或数组是通过 JSON.parse
解析得到的。
示例
输入:obj = {"x": 5, "y": 42} 输出:false 解释:The object has 2 key-value pairs so it is not empty.
题解
function isEmpty(data) {
if (Array.isArray(data)) {
return data.length === 0;
}
if (typeof data === 'object' && data !== null) {
return Object.keys(data).length === 0;
}
return false; // 非数组和对象的其他类型不算为空
}
这段代码定义了一个名为isEmpty的函数,该函数判断给定的参数data是否为空。首先,通过使用Array.isArray()方法判断data是否为数组,如果是,则返回data的长度是否为0,即判断数组是否为空。接下来,通过使用typeof运算符判断data的类型是否为对象并且不为null,如果是,则使用Object.keys()方法获取data的所有属性名组成的数组,并判断该数组的长度是否为0,即判断对象是否为空。最后,如果data既不是数组也不是对象,则返回false,表示其他类型的参数不视为空。
2624. 蜗牛排序
请你编写一段代码为所有数组实现 snail(rowsCount,colsCount)
方法,该方法将 1D 数组转换为以蜗牛排序的模式的 2D 数组。无效的输入值应该输出一个空数组。当 rowsCount * colsCount !==
nums.length
时。这个输入被认为是无效的。
蜗牛排序从左上角的单元格开始,从当前数组的第一个值开始。然后,它从上到下遍历第一列,接着移动到右边的下一列,并从下到上遍历它。将这种模式持续下去,每列交替变换遍历方向,直到覆盖整个数组。例如,当给定输入数组 [19, 10, 3, 7, 9, 8, 5, 2, 1, 17, 16, 14, 12, 18, 6, 13, 11, 20, 4, 15]
,当 rowsCount = 5
且 colsCount = 4
时,需要输出矩阵如下图所示。注意,矩阵沿箭头方向对应于原数组中数字的顺序
示例
输入: nums = [19, 10, 3, 7, 9, 8, 5, 2, 1, 17, 16, 14, 12, 18, 6, 13, 11, 20, 4, 15] rowsCount = 5 colsCount = 4 输出: [ [19,17,16,15], [10,1,14,4], [3,2,12,20], [7,5,18,11], [9,8,6,13] ]
题解
Array.prototype.snail = function (rowsCount, colsCount) {
//数组长度是否等于行数乘以列数,若不相等,则返回一个空数组。
if (this.length !== rowsCount * colsCount) {
return [];
}
const res = [];
for (let i = 0; i < rowsCount; i++) {
res.push([]);
}
let seq = true; // seq初始值为true表示正向
let start = 0; //start初始值为0。
for (let i = 0; i < this.length; i++) {
res[start].push(this[i]);
// 正向
if (seq) {
if (start === rowsCount - 1) {
// 若等于,则将seq变量设为false,表示切换到逆向添加;
seq = false;
} else {
// 若不等于,则将start加1。
start++;
}
} else {
// 逆向
if (start === 0) {
// 判断start是否等于0,若等于,则将seq变量设为true表示切换到正向添加;
seq = true;
} else {
// 若不等于,则将start减1
start--;
}
}
}
return res;
}
这段代码定义了一个名为snail的数组方法。这个方法接受两个参数,分别是行数rowsCount和列数colsCount。
首先判断调用该方法的数组长度是否等于行数乘以列数,若不相等,则返回一个空数组。
接下来,创建一个空数组res用来保存结果。使用for循环,循环行数次,并在每次循环中向res数组中添加一个空数组,相当于创建了一个行数*列数的二维数组。
接着定义两个变量seq和start,seq初始值为true表示正向,start初始值为0。
使用第二个for循环,循环遍历调用该方法的数组。在每次循环中,将当前元素添加到res数组中的第start行。
根据seq变量的值,判断是正向添加还是逆向添加。若为正向(seq为true),则判断start是否等于行数-1,若等于,则将seq变量设为false,表示切换到逆向添加;若不等于,则将start加1。
若为逆向(seq为false),则判断start是否等于0,若等于,则将seq变量设为true,表示切换到正向添加;若不等于,则将start减1。
最后,返回res数组作为结果。
简单来说,这段代码的作用是将一个一维数组按照螺旋状排列成一个二维数组,并返回这个二维数组作为结果。
2694. 事件发射器
设计一个 EventEmitter
类。这个接口与 Node.js 或 DOM 的 Event Target 接口相似,但有一些差异。EventEmitter
应该允许订阅事件和触发事件。
你的 EventEmitter
类应该有以下两个方法:
- subscribe - 这个方法接收两个参数:一个作为字符串的事件名和一个回调函数。当事件被触发时,这个回调函数将被调用。 一个事件应该能够有多个监听器。当触发带有多个回调函数的事件时,应按照订阅的顺序依次调用每个回调函数。应返回一个结果数组。你可以假设传递给
subscribe
的回调函数都不是引用相同的。subscribe
方法还应返回一个对象,其中包含一个unsubscribe
方法,使用户可以取消订阅。当调用unsubscribe
方法时,回调函数应该从订阅列表中删除,并返回 undefined。 - emit - 这个方法接收两个参数:一个作为字符串的事件名和一个可选的参数数组,这些参数将传递给回调函数。如果没有订阅给定事件的回调函数,则返回一个空数组。否则,按照它们被订阅的顺序返回所有回调函数调用的结果数组。
示例
输入:actions = ["EventEmitter", "emit", "subscribe", "subscribe", "emit"], values = [[], ["firstEvent", "function cb1() { return 5; }"], ["firstEvent", "function cb1() { return 5; }"], ["firstEvent"]] 输出:[[],["emitted",[]],["subscribed"],["subscribed"],["emitted",[5,6]]] 解释: const emitter = new EventEmitter(); emitter.emit("firstEvent"); // [], 还没有订阅任何回调函数 emitter.subscribe("firstEvent", function cb1() { return 5; }); emitter.subscribe("firstEvent", function cb2() { return 6; }); emitter.emit("firstEvent"); // [5, 6], 返回 cb1 和 cb2 的输出
题解
class EventEmitter {
constructor() {
this.events = {}; // {event: [cb1, cb2]}, 存储事件和回调
}
subscribe(event, cb) {
if (!this.events[event]) this.events[event] = []; // 初始化
this.events[event].push(cb); // 订阅, 将回调存入事件对应数组
return {
unsubscribe: () => {
// 取消订阅
this.events[event] = this.events[event].filter((e) => e !== cb); // 过滤掉当前回调
},
};
}
emit(event, args = []) {
if (!this.events[event]) return []; // 未订阅, 返回空数组
return this.events[event].map((cb) => cb(...args)); // 执行回调
}
}
这段代码定义了一个 EventEmiter 类,用于实现事件的订阅和触发功能。
EventEmitter 类的构造函数初始化了一个空的 events 对象,用于存储事件和回调的对应关系。
subscribe 方法用于订阅事件,接受两个参数:event 表示要订阅的事件名称,cb 表示事件触发时要执行的回调函数。如果 events 对象中没有对应的事件数组,会先进行初始化。然后将回调函数添加到事件对应的数组中,并返回一个包含 unsubscribe 方法的对象,用于取消订阅。unsubscribe 方法会将当前回调从事件的数组中过滤掉。
emit 方法用于触发事件,接受两个参数:event 表示要触发的事件名称,args 表示传递给回调函数的参数,默认为空数组。如果 events 对象中没有对应的事件数组,表示该事件未被订阅,直接返回空数组。否则,将事件对应的数组中的每一个回调函数都执行,并传入 args 参数,最后将执行结果组成的数组返回。