我正在挑选雄辩的JavaScript,这个高阶函数练习的答案让我难住了:
function characterScript(code) {
for (let script of SCRIPTS) {
if (script.ranges.some(([from, to]) => {
return code >= from && code < to;
})) {
return script;
}
}
return null;
}
// takes a test function and tells you whether that function
// returns true for any of the elements in the array
function countBy(items, groupName) {
let counts = [];
for (let item of items) {
let name = groupName(item);
let known = counts.findIndex(c => c.name == name);
if (known == -1) {
counts.push({name, count: 1});
} else {
counts[known].count++;
}
}
return counts;
}
// returns an array of objects, each of which names a group
// and tells you the number of elements that were found in that group
function dominantDirection(text) {
let scripts = countBy(text, char => {
let script = characterScript(char.codePointAt(0));
return script ? script.direction : "none";
}).filter(({name}) => name != "none");
if (scripts.length == 0) return "ltr";
return scripts.reduce((a, b) => a.count > b.count ? a : b).name;
}
console.log(dominantDirection("Hello!"));
// → ltr
console.log(dominantDirection("Hey, مساء الخير"));
// → rtl
这段代码返回一个大型数据集中占主导地位的写入方向,如下所示:
[
{
name: "Coptic",
ranges: [[994, 1008], [11392, 11508], [11513, 11520]],
direction: "ltr",
year: -200,
living: false,
link: "https://en.wikipedia.org/wiki/Coptic_alphabet"
},
// …
]
我理解如何使用some
方法的循环来查找字符代码返回true
的任何数组。我无法理解countBy
函数或dominantDirection
函数是如何导致底部显示的结果的。
这两个函数的分解以及它们如何导致正确的结果将非常感谢!
检查一些中间结果会更容易理解。添加console.log
以查看scripts
返回的内容,删除.name
以查看reduce
调用的结果:
function dominantDirection(text) {
const scripts = countBy(text, (char) => {
const script = characterScript(char.codePointAt(0));
return (script
? script.direction
: "none"
);
})
.filter(({name}) => name !== "none");
if(scripts.length === 0){
return "ltr";
}
console.log(scripts); // What is the result of the `countBy` function?
return scripts.reduce((a, b) => (a.count > b.count
? a
: b)); // What is the object that the `name` property comes from?
}
现在dominantDirection("Hello!")
将scripts
记录为
[
{ name: "ltr", count: 5 }
]
结果也是
{ name: "ltr", count: 5 }
和dominantDirection("Hey, مساء الخير")
将记录scripts
为
[
{ name: "ltr", count: 3 },
{ name: "rtl", count: 9 }
]
显示结果
{ name: "rtl", count: 9 }
scripts
数组来自countBy
调用,它返回字符串中每个脚本方向有多少代码点的计数。它试图通过比较codePoint
属于哪个ranges
并获得相应的direction
属性,从每个SCRIPTS
中找到相应的脚本。
这个高阶函数countBy
接受参数items
和groupName
。dominantDirection
用两个参数调用countBy
,并将结果存储在scripts
中。
items
是一个可迭代的值,在这种情况下是一个字符串(代码点):这只是输入字符串,例如"Hey, مساء الخير"
。从这个值出发,单独的项目(代码点)将被分组到"桶"中并单独计数。groupName
是一个返回单个代码点(例如一个字符)所属的"桶"名称的函数(基于代码点本身):在这种情况下,它是箭头函数char => {
…}
,它调用characterScript
与单个char
的代码点并返回相应的脚本对象(你说你理解)。然后它得到脚本的direction
,例如"ltr"
的{ name: "Coptic",
…}
对象从你的例子(或"none"
如果没有脚本对象可以找到)。
顺便说一句,groupName
不是一个好名字,因为它期望是一个函数,但是这个名字暗示了一个字符串。也许groupNameFromItem
更好。
当countBy
在字符串(for (let item of items)
)上迭代时,这个函数(最初是char => {
…}
)被调用并赋值给name
(let name = groupName(item);
)。由于char => {
…}
返回脚本的direction
,name
变成"ltr"
,"rtl"
或"none"
-这是"桶"的名称。数组counts
由{ name: "ltr", count: 1 }
这样的对象填充。如果下一个代码点也来自ltr
脚本,则使用findIndex
找到该对象,并使用++
增加其count
。
然后返回这个填充的数组(scripts
在dominantDirection
内部引用的数组)。
reduce
很容易解释:a
和b
是来自scripts
数组之一的对象。如果a.count
大于b.count
,则返回a
,否则返回b
;然后将返回的对象用于下一次比较,或者,如果不需要比较其他对象,则作为结果返回。因此,reduce
调用查找具有最大count
的对象。在原始代码中,最后只返回name
,而不是整个对象。
总结:
text
是一个字符串,由来自不同脚本的代码点组成。countBy
接受text
,迭代代码点,调用groupName
以获得当前代码点的"桶名",用{ name, count }
条目填充counts
数组(名为scripts
,在函数之外),这些条目告诉您count
许多代码点来自name
方向的脚本。然后一个reduce
在这些表项中查找最大的count
,并返回它的name
。
还有两点:
我理解如何使用
some
方法的循环来查找字符代码返回true
的任何数组。字符代码本身不返回
true
。如果代码点code
落在from
(包含)和to
(不包含)之间的任何一个范围内,则some
调用返回true
,否则返回false
。本章是关于高阶函数的,所以理解
function countBy(items, groupName){
…}
中的groupName
参数是如何工作的很重要。我不太确定你对这个概念有多熟悉,但这里有一个更简单的例子,其中奇数和偶数被计数,并附有一些解释性注释:const countOddAndEvenNumbers = (iterable) => { const oddOrEvenBucketFromNumber = (number) => (number % 2 === 0 ? "even" : "odd"); // This is the function that distinguishes odd and even numbers. return countGroups(iterable, oddOrEvenBucketFromNumber); // The distinguishing function is passed to `countGroups` to be used. }, countGroups = (iterable, bucketNameFromItem) => { const result = {}; // Usually counting is done with hash maps, e.g. objects or Maps, instead of arrays. for(let item of iterable){ const bucketName = bucketNameFromItem(item); // Generic way of `const bucketName = (item % 2 === 0 ? "even" : "odd")`; acts as `const bucketName = oddOrEvenBucketFromNumber(item)`, but with no own knowledge of what odd or even numbers are: it’s entirely separated and knows nothing about the implementation of the function. result[bucketName] = (result[bucketName] ?? 0) + 1; // Increment entry `bucketName` by one. If it doesn’t exist, initialize it to `0` first. } return result; }; countOddAndEvenNumbers([0, 1, 1, 2, 3, 5, 8, 13]); // { "even": 3, "odd": 5 }