在文本文件中计算单词出现的次数

6

我该如何跟踪文本文件中单词出现的次数?我想对每个单词都这样做。

例如,如果输入是:

"the man said hi to the boy."

每个单词"man said hi to boy"都将出现1次。

"the"将出现2次。

我考虑使用单词/次数对字典来保存数据,但我不知道如何在C语言中实现。如果有类似或相关问题的解决方案,请提供链接。


编辑:为了避免自己开发哈希表,我决定学习如何使用glib。在此过程中,我找到了一个优秀的教程,它介绍了一个类似的问题。http://bo.majewski.name/bluear/gnu/GLib/ch03s03.html

我对这么多不同的方法感到惊叹,特别是Ruby实现的简洁和优雅。


这个线程什么时候变成了“用你选择的编程语言分享解决此问题的解决方案”? - Mahmoud Al-Qudsi
8个回答

6

是的,一个包含单词和出现次数的字典可以很好地工作,实现这样的字典的常规方法是使用哈希表(或者有时使用二叉搜索树)。

您还可以使用trie(或其压缩版本"Patricia trie" / Radix trie),其复杂度在此问题上是渐进最优的,尽管我怀疑在实践中它可能比(良好的)哈希表实现要慢。

我真的认为哈希表或tries哪个更好取决于输入中单词的分布-例如,哈希表将需要将每个单词存储在其哈希桶中(以防止冲突),而如果您有很多具有共同前缀的单词,则在trie中这些共同前缀是共享的,每个只需要存储一次,但仍然存在所有指针的开销...如果您确实尝试了两者,我很想知道它们的比较情况。


5

对于好奇的人,这里有一个简单的 Ruby 解决方案来解决单词计数问题。它基本上应该与 C 中的算法相同,只是代码量更多。

h = Hash.new(0)
File.read("filename.txt").split.each do |w|
  h[w] += 1
end
p h

4

这算吗?

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
    char buffer[2048];
    if (argc != 2)
    {
        fprintf(stderr, "Usage: %s file\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    snprintf(buffer, sizeof(buffer), "tr -cs '[a-z][A-Z]' '[\\n*]' < %s |"
                                     " sort | uniq -c | sort -n", argv[1]);
    return(system(buffer));
}

它基本上封装了一个典型的脚本,演示了如何在Unix中使用shell脚本计算单词数。'tr'命令将非字母字符转换为换行符并挤出重复项。第一个'sort'将每个单词的所有出现分组在一起。'uniq -c'计算每个单词连续出现的次数,并打印单词及其计数。第二个'sort'按递增重复顺序排序。您可能需要调整'tr'的选项;它不是从系统到系统最稳定的命令,并且通常会让我手动操作。在Solaris 10中使用/usr/bin/tr,上面的代码(在其自身源上)产生:
   1
   1 A
   1 EXIT
   1 FAILURE
   1 Usage
   1 Z
   1 a
   1 c
   1 cs
   1 exit
   1 file
   1 fprintf
   1 if
   1 main
   1 return
   1 sizeof
   1 snprintf
   1 stderr
   1 stdio
   1 stdlib
   1 system
   1 tr
   1 uniq
   1 z
   2 argc
   2 char
   2 h
   2 include
   2 int
   2 s
   2 sort
   3 argv
   3 n
   4 buffer

这个很好用。我之前不知道'tr'命令。谢谢你的分享和精彩的解释 :-) - vinc456

2

您可以使用哈希表,每个哈希表条目指向一个包含单词及其出现次数的结构体。


有两个不同的单词哈希到相同的条目中发生碰撞的可能吗?我需要在条目处进行一些检查,还是存在完美的哈希函数?我有点生疏,但我会做些研究。谢谢。 - vinc456
这是通常的方法。您需要防止碰撞 - 通常通过将每个哈希桶作为单词计数结构的链接列表来实现。+1 - dmckee --- ex-moderator kitten
我上次看的时候不是+1吗?为什么会有人给正确答案点踩呢? :P 我给你点个赞+1。 - ShreevatsaR

2

对于单个词汇,除非这是某个更大项目的一部分,否则根本不需要编写程序:

sed -e 's/[[:space:]]/\n/g' < file.txt | grep -c WORD

1

在Perl中:

my %wordcount = ();
while(<>){map {$wordcount{$_}++} (split /\s+/)}
print "$_ = $wordcount{$_}\n" foreach sort keys %wordcount;

在 Perl Golf 中(只是为了好玩):

my%w;                       
map{$w{$_}++}split/\s+/while(<>); 
print"$_=$w{$_}\n"foreach keys%w; 

0
#include <conio.h>
#include <iostream.h>
#include <fstream.h>
#include <cstdlib>

struct stdt
{
       char name[20] ;
       int id ;

}; //std

int main()
{
      stdt boy ;
      int a = 0 ;
      ofstream TextFile ;
      cout << "Begin File Creation \n" ;
      TextFile.open("F:\\C++ Book Chapter Program\\Ch  7\\File.txt" );
      if ( !TextFile)
      {
           cerr <<"Erro 100 Openoing File.DAT" ;
           exit(100);     
      }//end if
      while ( a < 3 )
      {
            TextFile.write( (char*) &boy , sizeof (boy) ) ;
            cout << "\nEnter Name : " ;
            cin  >> boy.name;
            cout << "\nEnter ID : " ;
            cin  >> boy.id ;
            a++;
      }//end while

      TextFile.close();
      cout << "\nEnd File Creation" ;

      ifstream TextFile1 ;
      TextFile1.open("F:\\C++ Book Chapter Program\\Ch  7\\File.txt" );
      while ( TextFile1.read( (char*) &boy , sizeof (boy) ) )
      {
            cout << "\nEnter Name : " << boy.name; 
            cout << "\nEnter ID : " << boy.id ;


      }// end While

      getch();
      return 0 ;
}//end main

0

警告 未经测试的代码:

#include <stdio.h>

struct LLNode
{
    LLNode* Next;    
    char*   Word;
    int     Count;
};

void PushWord(LLNode** list, const char* word)
{
    LLNode* node = NULL;
    unsigned int len = 0;
    if (*list == NULL) 
    {
        $list = new LLNode;
        $list = "\0";
    }
    node = *list;
    while ((node = node->Next) != NULL) // yes we are skipping the first node
    {
        if (!strcmp(node->Word, word))
        {
            node->Count++;
            break;
        }

        if (!node->Next)
        {
            LLNode* nnode = new LLNode;
            nnode->Count = 1;
            node->Next = nnode;
            len = strlen(word);
            node->Word = new char[len + 1];
            strcpy(node->Word, word);
            break;
        }
    }
}

void GetCounts(LLNode* list)
{
    if (!list)
        return;
    LLNode* node = list;
    while ((node = node->Next) != NULL) // yes we are skipping the first node
    {
        printf("Word: %s, Count: %i", node->Word, node->Count);
    }
}

void PushWords(LLNode** list, const char* words)
{
    char ch = '\0';
    unsigned int len = strlen(words);
    char buff[len]; // to be sure we have no buffer ovverunes. May consume too much memery for your application though.
    int index = 0;
    for (unsigned int i = 0; i < len; i++)
    {
        ch = words[i];
        if (index > 0 && ch == ' ')
        {
            ch[index + 1] = '\0';
            PushWords(list, buff);
            index = 0;
        }
        else if (ch != ' ')
        {
            ch[index++] = ch;
        }
    }

    if (index > 0 && ch == ' ')
    {
        ch[index + 1] = '\0';
        PushWords(list, buff);
        index = 0;
    }
}

int main()
{
    LLNode* list = NULL;
    PushWords(&list, "Hello world this is a hello world test bla");
    GetCount(list);
    // release out memery here
}

我刚刚写的,可能不会起作用 - 但那是一般的想法。

另一个草稿,这次是用C++编写的(注意:std::map具有相当好的搜索时间):

#include <iostream>
#include <string>
#include <map>

using namespace std;

typedef map<string, int> CountMap;

void PushWords(CountMap& list, const char* words)
{
    char ch = '\0';
    unsigned int len = strlen(words);
    string str;
    int index = 0;
    for (unsigned int i = 0; i < len; i++)
    {
        ch = words[i];
        if (index > 0 && ch == ' ')
        {
            list[str] = list[str] + 1;
            index = 0;
        }
        else if (ch != ' ')
        {
            str += ch;
            index++;
        }
    }

    if (index > 0 && ch == ' ')
    {
        list[str] = list[str] + 1;
    }
}

void PrintCount(CountMap& list)
{
    CountMap::iterator iter = list.begin(), end = list.end();
    for (; iter != end; ++iter)
    {
        cout << (*iter).first << " : " << (*iter).second;
    }
}


int main()
{
    CountMap map;
    PushWords(map, "Hello world this is a hello world test bla");
    PrintCount(map);
}

我需要一些时间来研究你的代码,顺便感谢你的帮助。你编码的速度比我打字的速度还快! - vinc456

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