这段来自《JavaScript语言精粹》的代码是如何确定主导书写方向的?

4

我正在学习 Eloquent 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 函数如何导致底部显示的结果。

非常感谢您提供这两个函数的详细说明,以及它们如何导致正确的结果!

1个回答

5

如果您检查一些中间结果,就更容易理解了。 添加一个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所属范围并获取相应的direction属性,从每个SCRIPTS中找到对应的脚本。
此高阶函数countBy接受参数itemsgroupNamedominantDirection使用两个参数调用countBy并将其结果存储在scripts中。
  • items是一个可迭代值,在这种情况下是一个字符串(代码点):这只是输入字符串,例如“Hey,مساء الخير”。从该值中,将个别项(代码点)分组到“桶”中并单独计数。
  • groupName是一个函数,它返回单个代码点(例如字符)所属的“桶”的名称(基于代码点本身):在这种情况下,它是箭头函数char => {},它使用单个char的代码点调用characterScript并返回相应的脚本对象(您说您理解)。 然后获取脚本的 direction ,例如"ltr"用于您的示例中的{ name:"Coptic", ... }对象(如果找不到脚本对象,则为"none")。

顺便说一句,groupName不是一个好名字,因为它期望一个函数,但名称暗示了一个字符串。也许groupNameFromItem更好。

countBy迭代字符串(for(let item of items))时,将调用此函数(最初为char => {}),并将其分配给name let name = groupName(item); )。由于char => {...}返回脚本的directionname变为 "ltr" "rtl" "none" - 这就是“桶”的名称。数组counts填充了诸如 { name: "ltr", count: 1 } 的对象。如果下一个代码点也来自ltr脚本,则使用findIndex找到该对象,并使用++增加其count

这个填充数组被返回,也就是在 dominantDirection 函数内部所指的 scripts 数组。

reduce 很容易解释: ab 都是来自于 scripts 数组中的对象。 如果 a.countb.count 大,那么就返回 a;否则返回 b。返回的对象会被用于下一次比较,或者,如果不需要再比较了,就直接作为结果返回。 所以 reduce 调用查找拥有最大 count 值的对象。 在原始代码中,只返回了最终的 name ,而没有返回整个对象。


总之:

text 是一个由不同脚本的码点组成的字符串。 countBy 接收一个 text 参数,遍历其中的码点,并调用 groupName 来获取当前码点的“桶名”,用 { name, count } 的形式填充被称为 scripts(该函数外部变量)的计数数组,表示在 name 方向上有 count 个码点。 然后,reduce 查找这些条目中的最大 count,并返回其 name


还有两件事:

  • I understand how a loop with the some method is used to find any arrays in which the character code returns true.

    The character code itself doesn’t return true. The some call returns true if the code point code falls into any of the ranges between from (inclusive) and to (exclusive), or false, otherwise.

  • The chapter is about higher-order functions, so it’s important to understand how the groupName parameter in function countBy(items, groupName){} works. I’m not quite sure how familiar you are with this concept, but here’s a simpler example where odd and even numbers are counted with some explanatory comments:

    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 }
    

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接