JavaScript:使用对象遍历数组并跟踪项目频率

JavaScript: Using an object to iterate over array and keep track of item frequency

我需要一个函数 mostFrequentWord 来 return 在数组 words 中找到的最常见的字符串。我想使用一个对象来跟踪这些词频。使用 getter 和 setter 方法似乎是最可行的选择。其中 setter 函数用于更改代表单词的每个键的值。然后在我按频率值对对象进行排序后,我可以 return 频率最高的单词。我是不是想多了?

下面是如何使用 Array.prototype.reduce()

解决这个问题

var words = ["one", "three", "three", "three", "two", "two"];

var frequencies = words.reduce(function(memo, word) {
    //either start the count now, if this is the first encounter or increas it by 1
    memo[word] = (memo[word] + 1) || 1;
    return memo;
}, {}); // note the empty object being passed in here - that's the initial value for the variable "memo"

console.log(frequencies);

var mostFrequentWord = Object.keys(frequencies)
  .reduce(function(highest, current) {
    return frequencies[highest] > frequencies[current] ? highest : current;
  }, "");

console.log("most frequent word: " + mostFrequentWord + 
"\ncount: " + frequencies[mostFrequentWord])

那么要得到最高值,就又是运行reduce一样简单,只不过这次用的是Object.keys()

编辑:处理评论:

Is there any advantage to using .reduce() over .forEach() in your first loop? You're just returning the same object every time so it seems that .forEach() would work just as well and perhaps be a little clearer

好吧,这在某种程度上取决于风格 - 这两个 可以 达到相同的结果。尽管他们做事的方式不同,但我认为 reduce 至少具有微不足道的优势。原因如下:

  1. reduceforEach 传达不同的意图。虽然它们都可以用来实现类似的结果,但它们操作方式的差异确实使它们对某些操作有点偏见。

    • reduce 的目的是 "I want to take this collection of things, go through it and return one thing"。例如,它非常适合查找最小值或最大值或总和。因此,如果您在开头有一个数组并希望以其他内容结尾(尽管有时,您也可以 return 一个数组),则可以使用它。
    • forEach 的意图略有不同 - 它是 "I want to go through this collection and do something with each item"。本质上,它适用于当您想要对每个对象执行相同操作时,例如,您可能 console.log 对它们进行处理、验证或上传它们。通常,您将拥有一些代码,它接受一个项目并用它做一些事情,您将通过 forEach 将它应用于所有项目。
  2. reduce 是独立的。它可能看起来并不多,而且根据上下文可能也不多,但您必须认识到 reduce 中包含了全部功能。这使得在更大的上下文中更容易掌握,因为你在一个地方拥有你需要的一切。让我们使用 forEach 重写它,我将尝试显示差异

var words = ["one", "three", "three", "three", "two", "two"];

var frequencies = {}; //<- instantiation needs to be separate

words.forEach(function(word) { //<- population needs to be separate
    frequencies[word] = (frequencies[word] + 1) || 1;
});

console.log(frequencies); //<- usage is separate

因此,您将函数缩短了一行(不是 return)但由于变量的实例化而增加了一行。现在看起来完全没问题,因为它是独立的,但在更大的代码库中,您可能在每个部分之间都有代码。这使得将所有逻辑记在脑海中变得更加困难 - 如果您只阅读 forEach 循环,您就没有完整的上下文,因为您需要知道关于 frequencies 当您滚动到它时,您可能看不到 forEach。更重要的是,您甚至不知道 frequencies 会处于什么状态,然后您会进入 forEach - 它会预先填充一些值吗?它会被设置为null吗?它会是一个数组而不是一个对象吗?您不仅必须找到 frequencies 的初始 声明 ,而且还必须追踪 如果 它随时被更改在函数被调用之前。

现在,话虽如此,让我们重新审视一下 reduce 的作用 - 一切 您需要了解其运作方式的一切都集中在一个地方。 frequencies 的声明、所有更改和最终赋值始终发生在三行代码的范围内,因此无论您有多少代码,您都无需为上下文寻找任何其他内容。是的,您可能需要知道 words 包含什么,但是 forEach 也是如此。

关于这两点,我觉得reduce比较好理解。 forEach 看起来更简单的解决方案的唯一原因是,如果您只使用常规 for 循环做事并且需要功能替换。然而,声明式方法与命令式方法有其不同之处 - forEachfor 是不同的。两者都不是天生的 更好 但它们确实有优点和缺点,具体取决于情况。在这种情况下,reduce 操作是更好的功能方法。

您可以使用一个对象来保持一个词的索引计数,然后遍历计数以获得最高的一个。这是一个说明的工作片段:

function findMostFrequent(array) {
    var map = {};
    
    array.forEach(function(item) {
        map[item] = (map[item] || 0) + 1;
    });

    // find highest word count
    var highWord = Object.keys(map).reduce(function(highestWord, currentWord) {
        return map[currentWord] > map[highestWord] ? currentWord : highestWord;
    });
    return {word: highWord, count: map[highWord]};
}

var words = ["hello", "goodbye", "hello", "hello", "whatever", "something", "goodbye"];

var result = findMostFrequent(words);
console.log("highest count word is " + result.word + ", count = " + result.count);

在 ES6 中,您可以使用 Map 对象而不是普通的 JS 对象来保留计数,尽管这两种方式在实现上几乎没有区别。

是这样的:

function inArrayToIndex(value, array){
  for(var i=0,l=array.length; i<l; i++){
    if(array[i] === value){
      return i;
    }
  }
  return false;
}
function mostFrequentWord(wordsArray){
  var h = [], w, a, c = [], m;
  for(var i=0,l=wordsArray.length; i<l; i++){
    w = wordsArray[i]; a = inArrayToIndex(w, h)
    if(a !== false){
      c[a]++;
    }
    else{
      h.push(w); c.push(1);
    }
  }
  return h[inArrayToIndex(Math.max.apply(null, c), c)];
}
var mostest = mostFrequentWord(yourWordsArray);

这是另一个解决方案,它使用 lodash

var words = ["bob", "bill", "jimmy", "jack", "bob", "bob", "jimmy"];
    freq = {};

_.forEach(words, function (word) {
  freq[word] = freq[word]++ || 1;
});

var max = 0,
    mostFreq = undefined;

_.forEach(freq, function (count, word) {
  if (count > max) {
    max = count;
    mostFreq = word;
  }
});

console.log(mostFreq);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.15.0/lodash.min.js"></script>

for each 函数在 javascript 中是原生的,但仅适用于数组。使用 lodash,您可以遍历数组的每个元素,或对象的每个键值对。当对对象使用 _.forEach 时,传递给回调的第一个参数是值,第二个参数是对象中每对的键。您应该查看 lodash 文档。他们提供了一些非常有用的工具。

感谢所有输入的家伙和女孩。这是我解决它的方法。

首先我从辅助函数开始:

function getTokens(rawString) {
  // returns an alphabetically sorted list of words, removing punctuation
  // characters
  return rawString.toLowerCase().split(/[ ,!.";:-]+/).filter(Boolean).sort();
}

然后我的主要功能如下:

function mostFrequentWord(words) { 
  var wordsArray = getTokens(words);           // setup array for strings to live
  var wordsObject = {};                        // Setup object literal for words + count

  for (var i=0; i<wordsArray.length; i++) {
    var wordToCheck = wordsArray[i];
    if (wordsObject[wordToCheck] == undefined) {
      // word doesn't exist, let's add it as a key and set value pair to 1
      console.log(wordToCheck + " not found. Adding to object.");
      wordsObject[wordToCheck] = 1;
    } else {
      // word does exist, let's increment the value pair by 1
      console.log(wordToCheck + " has been found. Incrementing.");
      wordsObject[wordToCheck] += 1;
    }
  }

console.log(wordsObject);
var mostFrequent;

  for (var key in wordsObject) {
    if (mostFrequent == undefined) {
      mostFrequent = key;
    } else if (wordsObject[key] > wordsObject[mostFrequent]) {
       mostFrequent = key;
    }
  }

console.log("Most frequent word is: " + mostFrequent);
return mostFrequent;

}