大家好。另一天,小切比赛Yumemi 提出了一个 JavaScript 测验作为挑战,学生们非常兴奋。在链接的文章中,我的答案也被介绍为一个有趣的答案。模型答案使用flatMap,但在我的答案中,我使用reduce 方法作为标准数组操作之一。
解决办法是代码有点难,所以准备了一篇评论文章。
问题和解答
设置的测验内容如下(引自上述文章)。
const array1 = [1, 2, 3, 4, 5, 6]
const array2 = array1./* ここに解答を書いてください */
console.log(array2)
// -> [1, 1, 1, 2, 2, 3, 3, 3, 4, 4, 5, 5, 5, 6, 6]
这是编写代码以按照指示处理数组的问题。
我准备的答案如下。
const array1 = [1, 2, 3, 4, 5, 6]
const array2 = array1.concat(array1, array1).reduce((a,b,c,d)=>
(d.push(...d.splice(Number(a%16n),1)),a >> 4n||d),4721443915042874085729n).slice(0, 15);
console.log(array2)
它有点难以阅读,部分原因是它被缩短以适应推文。因此,在本文中,我将解释这段代码是如何工作的。
评论
以下是上述代码的作用:
const array1 = [1, 2, 3, 4, 5, 6];
const work = array1.concat(array1, array1);
const ops = [
1, 6, 1, 5, 10,
1, 4, 1, 3, 7,
1, 6, 2, 3, 3,
15, 15, 15,
];
for (const x of ops) {
const tmp = work.splice(x, 1)[0];
work.push(tmp);
}
const array2 = work.slice(0, 15);
console.log(array2);
换句话说,我们准备数组work,并用splice 和push 重复破坏性操作以获得所需的结果。此处使用splice 从数组中间提取一个元素(上面代码中的x)。提取的元素以push 附加到数组的末尾。
这两个操作是一个集合,变成了“将数组中间的一个元素移到末尾”的操作。这是一种重新安排。
在上面的代码中,工作数组work最初包含[1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6],经过18次排列,work变成[1, 1, 1, 2, 2, 3, 3, 3, 4, 4, 5, 5, 5, 6, 6, 6, 2, 4]。最后三个是剩下的,所以slice 丢弃了多余的部分,array2 完成了。
排序操作的具体内容是作者手动准备的ops。这个[1, 6, 1, ……] 数组表示一系列操作,首先将第一个元素移到后面,然后将第六个元素移到后面,以此类推。
将排序过程编码为数字
在上面的代码中,排序过程被定义为一个数组ops,它很丑,而且很占空间,所以我们对其进行压缩。
像这样的一串整数可以编码成一个整数。由于ops 的每个元素都可以用 4 位表示,因此它可以用十六进制的每个元素恰好一个数字来表示。具体来说,ops 表示为0xfff3326173141a5161。原始数组的元素从低位开始按顺序出现。
使用这种编码重写上面的代码,我们得到:为了从压缩数字中提取原始指令序列,我们使用这样的按位运算。请注意,此数字有很多位,因此使用 BigInt 表示。
const array1 = [1, 2, 3, 4, 5, 6];
const work = array1.concat(array1, array1);
for (let ops = 0xfff3326173141a5161n; ops; ops >>= 4n) {
const tmp = work.splice(Number(ops % 16n), 1)[0];
work.push(tmp);
}
const array2 = work.slice(0, 15);
console.log(array2);
splice 的参数被转换为 Number(ops % 16n) 的数字,因为需要传递数字而不是 BigInt。可惜这个Number在代码中脱颖而出,失去了影响。BigInt有一个规范,除非用Number明确转换,否则会导致错误,所以没办法。
将循环转换为减少
由于这次的主题是array1./* ここに解答を書いてください */,所以我想避免用for语句循环。如果你使用;,你可以写一个for语句,但是为了美观,我会使用reduce。
您是否注意到原来的 ops 是一个 18 元素的数组? work 也是 18 个元素。所以,如果你用reduce正常循环的话,循环的次数会刚刚好。1.现在,使用reduce 就像forEach。
const array1 = [1, 2, 3, 4, 5, 6];
const work = array1.concat(array1, array1);
let ops = 0xfff3326173141a5161n;
work.reduce(() => {
const tmp = work.splice(Number(ops % 16n), 1)[0];
work.push(tmp);
ops >>= 4n;
});
const array2 = work.slice(0, 15);
console.log(array2);
此外,您会注意到上面代码中的let 中的ops 现在可以保留在reduce 中。 reduce 可用于准备初始值并在遍历数组时转换值的操作。这一次,ops 只是在遍历数组时被修改,所以我们可以使用这种机制。
const array1 = [1, 2, 3, 4, 5, 6];
const work = array1.concat(array1, array1);
work.reduce((ops) => {
const tmp = work.splice(Number(ops % 16n), 1)[0];
work.push(tmp);
return ops >> 4n;
}, 0xfff3326173141a5161n);
const array2 = work.slice(0, 15);
console.log(array2);
您非常接近最终代码。
形成方法链
从这里开始,我们将稍微修改代码以使其成为方法链接的形式。
首先,连接reduce和slice。目前,work.reduce 在操作 BigInt 时会循环,所以最终的返回值也是 BigInt。因此,无法按原样连接到slice。
事实上,如果你重复ops >> 4n并删除ops的内容,它将在循环结束时变为0n。因此work.reduce的返回值为0n。
利用这个事实,我们可以通过在循环结束时将reduce 的返回值设置为数组而不是 BigInt 来连接slice。如以下代码,当ops >> 4n仍为正数时,它返回BigInt,为下一次循环做准备。
const array1 = [1, 2, 3, 4, 5, 6];
const work = array1.concat(array1, array1);
const array2 = work.reduce((ops) => {
const tmp = work.splice(Number(ops % 16n), 1)[0];
work.push(tmp);
return ops >> 4n || work;
}, 0xfff3326173141a5161n).slice(0, 15);
console.log(array2);
以这种方式将reduce 存储区用于两个目的,对我来说是艺术的高点。
接下来,连接concat 和reduce。这里棘手的部分是work 需要首先存储在一个变量中,因为reduce 的回调将对work 进行破坏性操作。使用方法链接时,似乎无法访问work。
没想到reduce的回调函数可以接收到一个等价于this的数组作为第四个参数。通过使用它,您无需事先将work 放入变量中。
const array1 = [1, 2, 3, 4, 5, 6];
const array2 = array1.concat(array1, array1).reduce((ops, _cur, _i, work) => {
const tmp = work.splice(Number(ops % 16n), 1)[0];
work.push(tmp);
return ops >> 4n || work;
}, 0xfff3326173141a5161n).slice(0, 15);
console.log(array2);
现在您有一个与测验匹配的代码。之后把tmp删掉,把变量名改成一个字符,难懂。此外,通常的做法是将用十六进制数编写的幻数转换为十进制数以提高神秘性。
这将为您提供上面的代码。
const array1 = [1, 2, 3, 4, 5, 6]
const array2 = array1.concat(array1, array1).reduce((a,b,c,d)=>
(d.push(...d.splice(Number(a%16n),1)),a >> 4n||d),4721443915042874085729n).slice(0, 15);
console.log(array2)
可以改进的地方
查看上面的代码,可以在不改变概念的情况下改进一些事情。首先,在这段代码中,reduce 的回调函数的形式是(a,b,c,d)=>( ... ) 用( ) 包围表达式。这是由于内部使用了逗号运算符。但是,利用 d.push 这次总是返回 1 的事实,我可以通过执行以下操作来删除括号:这是我希望在发布之前注意到的反映。
const array1 = [1, 2, 3, 4, 5, 6]
const array2 = array1.concat(array1, array1).reduce((a,b,c,d)=>
d.push(...d.splice(Number(a%16n),1))&&a>>4n||d,4721443915042874085729n).slice(0, 15);
console.log(array2)
此外,重新排列它们可能会更简洁一些,以便使用.slice(3) 而不是.slice(0, 15)。
首先需要slice 的原因是,虽然基数是一个18 元素数组array1.concat(array1, array1),但目标是15 个元素。材质之所以是18个元素,是因为我觉得用array1作为材质,而不是把必要的数值硬生生写出来会更漂亮。
概括
所以,我解释了我准备的答案。我在准备好配料后看到了sort() 的其他答案,但它的独特之处在于配料是从array1 采购的,并且是手动排序的,而不依赖于sort()。我想我能把它弄出来。
死故事
我还考虑了以下解决方案,但由于无法创建答案而被拒绝。
- 运行确定性随机播放算法时,指定为幻数的次数恰好是所需的形状。
- 令人惊讶的是,我无法创建一个简单到可以巧妙地混合到推文中的算法,而且即使我搜索幻数也找不到它,所以我拒绝了它。
-
如果映射到
array1.concat(array1,array1)并使用适当的系数和Math.cos或Math.sin进行计算,您将得到所需的数组。- 已丢弃,因为搜索后未找到合适的系数。
由于这些都是无用的,所以这次可以在不依赖搜索的情况下创建解决方案。
-
但是,
ops的最后三个15表示什么都没做,所以我只是用15填充它,直到有18 个元素。↩
原创声明:本文系作者授权爱码网发表,未经许可,不得转载;
原文地址:https://www.likecs.com/show-308624167.html