JavaScript中的单词频率

8

enter image description here

如何实现JavaScript函数来计算给定句子中每个单词的频率。

这是我的代码:

function search () {
  var data = document.getElementById('txt').value;
  var temp = data;
  var words = new Array();
  words = temp.split(" ");
  var uniqueWords = new Array();
  var count = new Array();


  for (var i = 0; i < words.length; i++) {
    //var count=0;
    var f = 0;
    for (j = 0; j < uniqueWords.length; j++) {
      if (words[i] == uniqueWords[j]) {
        count[j] = count[j] + 1;
        //uniqueWords[j]=words[i];
        f = 1;
      }
    }
    if (f == 0) {
      count[i] = 1;
      uniqueWords[i] = words[i];
    }
    console.log("count of " + uniqueWords[i] + " - " + count[i]);
  }
}

我无法跟踪问题...非常感谢任何帮助。

is的计数 - 1

the的计数 - 2...

输入: 这是安尼尔是库姆安尼尔


我们应该如何知道这段代码是否存在问题? - zerkms
抱歉,我已经添加了截图。 - Anil
1
使用字面量:var words = [],而不是 var words = new Array() - royhowie
以下是被接受的答案,作为一个常规函数,并且使用压缩工具function wordCounts(n){return n.match(/\w+/g).reduce(function(n,r){return n.hasOwnProperty(r)?++n[r]:n[r]=1,n},{})} - ashleedawg
7个回答

24

下面是一个JavaScript函数,用于获取句子中每个单词的频率:

function wordFreq(string) {
    var words = string.replace(/[.]/g, '').split(/\s/);
    var freqMap = {};
    words.forEach(function(w) {
        if (!freqMap[w]) {
            freqMap[w] = 0;
        }
        freqMap[w] += 1;
    });

    return freqMap;
}

它将返回一个单词到单词计数的哈希表。例如,如果我们这样运行它:
console.log(wordFreq("I am the big the big bull."));
> Object {I: 1, am: 1, the: 2, big: 2, bull: 1}

您可以使用Object.keys(result).sort().forEach(result) {...}循环遍历单词。因此,我们可以这样连接:

var freq = wordFreq("I am the big the big bull.");
Object.keys(freq).sort().forEach(function(word) {
    console.log("count of " + word + " is " + freq[word]);
});

这将输出:

count of I is 1
count of am is 1
count of big is 2
count of bull is 1
count of the is 2

JSFiddle:http://jsfiddle.net/ah6wsbs6/

以下是 ES6 中的 wordFreq 函数:

function wordFreq(string) {
  return string.replace(/[.]/g, '')
    .split(/\s/)
    .reduce((map, word) =>
      Object.assign(map, {
        [word]: (map[word])
          ? map[word] + 1
          : 1,
      }),
      {}
    );
}

JSFiddle: http://jsfiddle.net/r1Lo79us/


非常感谢您提供的解决方案。我正在努力理解您的代码,并将尝试实现它。但是,您能否指出我的代码中的错误? - Anil
1
第一个错误是尝试通过将事物放入数组来计数。使用哈希表进行计数要简单得多。在JavaScript中,对象或 {} 就像哈希表一样,因此请利用最容易使用的东西。我会看一下。 - Cymen
@KalalAnil 是的,我试过了,但最终还是得重写成与上面相同的形式。 - Cymen
是的,我使用了两个数组,只是为了清晰明了,并且因为我不太了解映射对象。我会尝试研究一下对象概念...谢谢。 - Anil
1
使用多个数组会更加困难,虽然可以做到,但需要付出太多的努力。最好先学习哈希表。 - Cymen

19

我觉得你在处理上过于复杂,使用了多个数组、字符串,并且经常(而且很难跟进)在循环和嵌套循环之间切换。

下面是我建议你考虑采取的方法。我已内联注释以解释每个步骤。如果有任何不清楚的地方,请在评论中让我知道,我会重新访问以改进清晰度。

(function () {

    /* Below is a regular expression that finds alphanumeric characters
       Next is a string that could easily be replaced with a reference to a form control
       Lastly, we have an array that will hold any words matching our pattern */
    var pattern = /\w+/g,
        string = "I I am am am yes yes.",
        matchedWords = string.match( pattern );

    /* The Array.prototype.reduce method assists us in producing a single value from an
       array. In this case, we're going to use it to output an object with results. */
    var counts = matchedWords.reduce(function ( stats, word ) {

        /* `stats` is the object that we'll be building up over time.
           `word` is each individual entry in the `matchedWords` array */
        if ( stats.hasOwnProperty( word ) ) {
            /* `stats` already has an entry for the current `word`.
               As a result, let's increment the count for that `word`. */
            stats[ word ] = stats[ word ] + 1;
        } else {
            /* `stats` does not yet have an entry for the current `word`.
               As a result, let's add a new entry, and set count to 1. */
            stats[ word ] = 1;
        }

        /* Because we are building up `stats` over numerous iterations,
           we need to return it for the next pass to modify it. */
        return stats;

    }, {} );

    /* Now that `counts` has our object, we can log it. */
    console.log( counts );

}());

1
为什么把所有逻辑都放在return语句中,即使它并没有被返回呢?我认为这会使代码更难读、管理和理解。 - Muhammad Umer
3
@MuhammadUmer 因为我喜欢使用单行的美学;而且它足够短,你可以看到最终返回了 words。如果您更喜欢两行,请使用两行 ;) - Sampson
1
我理解这一点,但编写代码应该是易于阅读的,以便您或其他人在六个月后也能轻松理解它。当单行代码不会分散主要逻辑时,它们是很好的选择,因为它们可以提高理解度。但是,在这里,很容易忽略最后一个逗号...如果只是阅读,它看起来很奇怪,"return abc, d" 只返回 d。 - Muhammad Umer
1
@MuhammadUmer 我理解并原则上同意。 - Sampson
1
@KalalAnil 你的方法包括将标点符号与单词一起使用,需要多个数组以及过多的上下文切换。与其试图让这种方法运行得更好,我鼓励你完全重新思考问题。 - Sampson
显示剩余10条评论

3

const sentence = 'Hi my friend how are you my friend';

const countWords = (sentence) => {
    const convertToObject = sentence.split(" ").map( (i, k) => {
        return {
          element: {
              word: i,
              nr: sentence.split(" ").filter(j => j === i).length + ' occurrence',
          }

      }
  });
    return Array.from(new Set(convertToObject.map(JSON.stringify))).map(JSON.parse)
};

console.log(countWords(sentence));


0

这是您自己的代码的更新版本...

<!DOCTYPE html>
<html>
<head>
<title>string frequency</title>
<style type="text/css">
#text{
    width:250px;
}
</style>
</head>

<body >

<textarea id="txt" cols="25" rows="3" placeholder="add your text here">   </textarea></br>
<button type="button" onclick="search()">search</button>

    <script >

        function search()
        {
            var data=document.getElementById('txt').value;
            var temp=data;
            var words=new Array();
            words=temp.split(" ");

            var unique = {};


            for (var i = 0; i < words.length; i++) {
                var word = words[i];
                console.log(word);

                if (word in unique)
                {
                    console.log("word found");
                    var count  = unique[word];
                    count ++;
                    unique[word]=count;
                }
                else
                {
                    console.log("word NOT found");
                    unique[word]=1;
                }
            }
            console.log(unique);
        }

    </script>

</body>

我认为你的循环过于复杂了。此外,在第一次遍历单词数组时尝试生成最终计数是注定会失败的,因为在检查数组中的每个单词之前无法测试唯一性。

我使用了Javascript对象作为关联数组来代替所有计数器,这样我们就可以存储每个唯一单词及其出现次数。

然后,一旦我们退出循环,我们就可以看到最终结果。

此外,此解决方案不使用正则表达式;)

我还要补充说,仅基于空格计算单词数量非常困难。在此代码中,“one,two,one”将导致“one,”和“one”被视为不同的唯一单词。


这对像我这样的新手来说非常好理解和易于操作,非常感谢。 - Anil
1
通过在空格上分割,"这个世界是你的世界。" 将把 "world" 和 "world." 视为两个不同的单词。此外,JavaScript 没有 关联数组,因此不要期望对象像它们一样运行。在这里使用 in 运算符是危险的,因为它包括原型链上的属性。因此,如果您的字符串中有像 "length" 这样的单词,您将得到误导性的结果。最后,"没有正则表达式" 不一定是解决方案的好或可赎回的特性。正则表达式是一个强大的实用工具,可以增强任何投入时间去理解它们的开发人员 :) - Sampson
嗨@JonathanSampson。我确实同意正则表达式很强大。只是OP想要修复他们的代码,而不是基于正则表达式提供完全不同的解决方案,所以我尝试修改原始代码。我相信正则表达式也有一个“单词”运算符,可能也可以解决“单词在空格上断开”的问题,因此正则表达式可能是解决这个问题的好方法。至于“in”运算符,我将不得不研究一下。我不知道它有问题。 - Lucien Stals

0

虽然这里的两个答案都是正确的,但它们都没有回答 OP 的问题(他的代码有什么问题)。

OP 代码的问题在这里:

if(f==0){
    count[i]=1;
    uniqueWords[i]=words[i];
}

在每个新单词(唯一单词)上,代码将其添加到uniqueWords中,索引为单词在words中的位置。因此,在uniqueWords数组中存在间隙。这就是某些undefined值的原因。

尝试打印uniqueWords。它应该会给出类似以下的结果:

["this", "is", "anil", 4: "kum", 5: "the"]

请注意,索引3没有元素。

此外,最终计数的打印应该在处理words数组中的所有单词之后。

以下是已更正版本:

function search()
{
    var data=document.getElementById('txt').value;
    var temp=data;
    var words=new Array();
    words=temp.split(" ");
    var uniqueWords=new Array();
    var count=new Array();


    for (var i = 0; i < words.length; i++) {
        //var count=0;
        var f=0;
        for(j=0;j<uniqueWords.length;j++){
            if(words[i]==uniqueWords[j]){
                count[j]=count[j]+1;
                //uniqueWords[j]=words[i];
                f=1;
            }
        }
        if(f==0){
            count[i]=1;
            uniqueWords[i]=words[i];
        }
    }
    for ( i = 0; i < uniqueWords.length; i++) {
        if (typeof uniqueWords[i] !== 'undefined')
            console.log("count of "+uniqueWords[i]+" - "+count[i]);       
    }
}

我刚刚将计数的打印从处理循环移到了一个新的循环中,并添加了一个if not undefined的检查。

示例:https://jsfiddle.net/cdLgaq3a/


0

我曾经有过类似的作业。这是我的做法:

作业:清理以下文本并找出最常见的单词(提示:使用replace和正则表达式)。

const sentence = '%I $am@% a %tea@cher%, &and& I lo%#ve %te@a@ching%;. The@re $is no@th@ing; &as& mo@re rewarding as educa@ting &and& @emp%o@weri@ng peo@ple. ;I found tea@ching m%o@re interesting tha@n any ot#her %jo@bs. %Do@es thi%s mo@tiv#ate yo@u to be a tea@cher!? %Th#is 30#Days&OfJavaScript &is al@so $the $resu@lt of &love& of tea&ching'

console.log(`\n\n 03.Clean the following text and find the most frequent word (hint, use replace and regular expressions) \n\n ${sentence} \n\n`)

console.log(`Cleared sentence : ${sentence.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()@]/g, "")}`)

console.log(mostFrequentWord(sentence))


function mostFrequentWord(sentence) {
  sentence = sentence.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()@]/g, "").trim().toLowerCase()
  let sentenceArray = sentence.split(" ")
  let word = null
  let count = 0
  for (i = 0; i < sentenceArray.length; i++) {
    word = sentenceArray[i]
    count = sentence.match(RegExp(sentenceArray[i], 'gi')).length
    if (count > count) {
      count = count
      word = word
    }
  }
  return `\n Count of most frequent word "${word}" is ${count}`
}


-1

我会选择Sampson的匹配-减少方法,以获得稍微更好的效率。这是一个修改过的版本,更适合生产环境。它并不完美,但应该可以涵盖绝大多数情况(即“足够好”)。

function calcWordFreq(s) {
  // Normalize
  s = s.toLowerCase();
  // Strip quotes and brackets
  s = s.replace(/["“”(\[{}\])]|\B['‘]([^'’]+)['’]/g, '$1');
  // Strip dashes and ellipses
  s = s.replace(/[‒–—―…]|--|\.\.\./g, ' ');
  // Strip punctuation marks
  s = s.replace(/[!?;:.,]\B/g, '');
  return s.match(/\S+/g).reduce(function(oFreq, sWord) {
    if (oFreq.hasOwnProperty(sWord)) ++oFreq[sWord];
    else oFreq[sWord] = 1;
    return oFreq;
  }, {});
}

calcWordFreq('A ‘bad’, “BAD” wolf-man...a good ol\' spook -- I\'m frightened!') 返回

{
  "a": 2
  "bad": 2
  "frightened": 1
  "good": 1
  "i'm": 1
  "ol'": 1
  "spook": 1
  "wolf-man": 1
}

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