JavaScript:使用一个对象在数组上迭代并跟踪项目频率

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

本文关键字:迭代 跟踪 项目 频率 数组 一个对象 JavaScript      更新时间:2023-09-26

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

以下是如何使用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()

发表评论:

在第一个循环中使用.reduce((比使用.forEach((有什么好处吗?你只是每次都返回相同的对象,所以看起来.forEach((也同样有效,而且可能会更清晰一点

好吧,这在一定程度上取决于风格——这两个都可以实现相同的结果。不过,他们的方式不同,因此我认为reduce至少有微小的优势。原因如下:

  1. CCD_ 7和CCD_。虽然它们都可以用来实现类似的结果,但它们操作方式的差异确实使它们对某些操作有点偏见
  • 对于CCD_ 9;我想把这些东西收集起来,仔细看一遍,然后归还一件东西"。例如,它非常适合查找最小值、最大值或总和。因此,如果您在开始时有一个数组,并且希望以其他内容结束,则可以使用它(尽管有时,您也可以返回一个数组(
  • forEach的意图略有不同;我想浏览一下这个收藏,并对每一件物品做点什么;。从本质上讲,当你想对每个对象执行相同的操作时,比如说,你可能正在console.log对它们进行标记、验证或上传。通常,您将有一个some代码,它接受一个项并对其执行某些操作,您只需通过forEach将其应用于所有项
  1. 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

因此,由于变量的实例化,您使函数缩短了一行(没有返回(,但增加了一行。现在看起来完全可以了,因为它是孤立的,但在更大的代码库中,每个部分之间可能都有代码。这让你很难把所有的逻辑都记在脑子里——如果你只阅读forEach循环,你就没有完整的上下文,因为当你滚动到它时,你需要知道frequencies,你可能看不到forEach。更重要的是,你甚至不知道frequencies会处于什么状态,然后你得到forEach——它会预先填充一些值吗?会设置为null吗?它会是一个数组而不是一个对象吗?您不仅必须找到frequencies的初始声明,而且还必须跟踪是否在调用函数之前的任何时候发生了更改。

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

关于这两点,我想说reduce更容易理解。forEach看起来更简单的解决方案的唯一原因是,如果您只使用常规for循环进行操作,并且需要进行功能替换。然而,声明式方法与命令式方法有其区别——forEachfor是不同的。两者都不是天生的更好,但根据情况,它们确实有优势和劣势。在这种情况下,reduce操作是更好的功能方法。

它是这样的:

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);

感谢所有的投入。以下是我解决问题的方法。

首先,我从一个助手功能开始:

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;
}

这里有另一个解决方案,它使用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 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对象,尽管这两种方法在实现上几乎没有什么不同。