如何使用C#在文本中查找重复的单词组?

时间:2015-11-24 05:30:49

标签: c# regex text

我在 StringBuilder(sb)中得到了重复的字数,我在互联网上找到了这个代码,根据作者的说法,它与Word的字计数器非常一致。

StringBuilder wordBuffer = new StringBuilder();
        int wordCount = 0;
        // 1. Build the list of words used. Consider ''' (apostrophe) and '-' (hyphen) a word continuation character.
        Dictionary<string, int> wordList = new Dictionary<string, int>();
        foreach (char c in sb.ToString())
        {

            if (char.IsLetter(c) || c == '\'' || c == '-')
            {
                wordBuffer.Append(char.ToLower(c));
            }
            else
            {
                if (wordBuffer.Length > 3)
                {
                    int count = 0;
                    string word = wordBuffer.ToString();
                    wordList.TryGetValue(word, out count);
                    wordList[word] = ++count;

                    wordBuffer.Clear();
                    wordCount++;
                }
            }
        }

这是我的示例文字:

  

绿藻(单一:绿藻)是一种大型,非正式的藻类组合,由绿藻和藻类藻类组成,现在被放置在不同的区域。   土地植物或苔藓植物(高等植物)被认为是从Charophytes中出现的。[1]由于胚性植物不是藻类,因此被排除在外,绿藻是一种副系。然而,包括绿藻和胚性植物的进化枝是单系的并且被称为进化枝(Viridiplantae)和植物界(Plantae)。绿藻包括单细胞和殖民鞭毛虫,大多数每个细胞有两个鞭毛,以及各种殖民,球形和丝状形式,以及宏观,多细胞海藻。在Charales,高等植物的最亲近的亲属,发生组织的完全细胞分化。大约有8000种绿藻。[2]许多物种的大部分时间都是单细胞,而其他物种则形成绦虫(菌落),长丝或高度分化的宏观海藻。   一些其他生物依赖绿藻进行光合作用。苏格兰和氯霉素中的叶绿体是从摄入的绿藻中获得的,[1]后者保留了核形态(残留核)。绿色藻类也在纤毛虫草履虫(Hydra viridissima)和扁虫(flatworms)中共生发现。一些种类的绿藻,特别是Trebouxiophyceae和Trentepohlia(类Ulvophyceae)的Trebouxia属,可以与真菌共生组合形成地衣。一般而言,与地衣配合的真菌物种不能独立生存,而藻类物种通常在没有真菌的情况下生活在自然界中。 Trentepohlia是一种丝状绿藻,可以在潮湿的土壤,岩石或树皮上独立生活,或在Graphidaceae家族的地衣中形成光虫。

根据我的示例文字,我在第一行按照预期获得 绿色 藻类 字词

问题,我不需要单词,我也需要单词组。通过这个示例文本,我还需要 绿藻 字,以及 绿色 藻类 的话。

我的可选问题是:我需要以高性能来做,因为文本可能很长。正如我研究的那样,在这种情况下使用RegEx的性能不高,但我不确定是否有第二种方法可以实现。

提前致谢。

更新 如果你得到了我所询问的内容,则无需阅读这些内容。
由于我看到太多关于我的“小组”定义的评论不明确,我想我需要更详细地说明我的观点,我希望在评论部分写下这些内容,但这个更新的范围有点狭窄。首先,我知道StackOverflow不是编码服务。我试图在文章中找到最常用的单词组,并试图决定文章的内容,我们也称之为标签生成器。为此我试图找到最常用的单词,一开始就没问题。然后我意识到这不是决定主题的好方法,因为我不能认为这篇文章只是关于第一个或第二个词。在我的例子中,我不能说这篇文章只是关于绿色藻类,因为它们在这里意味着什么,而不是单独的。如果我试着写一篇关于三个名人的文章,比如“Helena Bonham Carter”(如果我认为它的文章是全文写的,不仅仅是姓氏),我想把这些文字一个接一个地放在一起。我正在努力实现更聪明的算法,它以最准确的方式一次性地猜测主题。我不想限制字数,因为文章可能是关于“联合国工业发展组织”(我再次假设它现在写成文章中的“工发组织”)。我可以通过尝试将每个单词组从任何索引开始到任意长度的文本结尾来实现这一点。好吧,这真的不是一个好方法,尤其是长篇文章,但这不是不可能的吗?但我正在寻找一种更好的方法来做到这一点,我只是问了一个更好的算法想法和最好的工具,我可以自己编写代码。我希望我最终明确表达我的目标。

3 个答案:

答案 0 :(得分:5)

实现此目的的方法是获取初始文本,并使用string.split(' ');

将空格分割为字符串数组

接下来,您需要迭代数组中的每个字符串。 这对于单个单词来说很容易,但对于组来说则更复杂。 因此,您需要定义组大小。您必须控制指针在每次迭代时前进的位置数。

一旦你能够迭代数组,就需要获取一组单词(无论你选择它多长时间),并将其存储在某个地方。 示例中的词典是一种很好的方法。

如果字典包含单词group,则将其值增加1。 如果它不包含该组,只需使用默认值1添加它。

 if (wordList.ContainsKey(theKey)) {
   wordList[theKey]++;
 } else {
   wordList.Add(theKey, 1);
 }

您确实正确地提到您的研究表明正则表达式并不是高性能。对于这个任务,正则表达式是完全错误的工具 - 你不是在寻找模式,而是在检查组。 为此,您必须从头到尾检查文本,检查值。

任何涉及迭代文本并在其上运行重复函数的任务都不应该使用正则表达式。

编辑:我最初对Regex性能的假设是不正确的 - 在C#中,它似乎比在Java中更快,但我仍然认为纯正则表达式方法不如使用正则表达式快对文本进行标记,然后使用循环或linq表达式来查找组。

陈述

  

@galakt正如我上面提到的,让我们说3.这有关系吗?

单词组的想法完全是抽象的。是的,它是一组单词,但整个文本块是一组单词。 必须应用规则来管理你对这组词的行为。

下面是一个示例方法,它将根据通过方法调用传递的大小返回所有单词组的字典。 它不会从文本中删除任何非字母数字字符,但即使组大小较大,它也很快。

要拨打电话,请使用Dictionary<String, int> SingleWordGroups = GetWordGroupInstances(1);

    private Dictionary<String, int> GetWordGroupInstances(int GroupSize) {

        Dictionary<String, int> WordGroupInstances = new Dictionary<string, int>();

        //Grab the string to work from...
        String[] sourceText = GetSourceText().Split(' ');
        int pointer = 0;
        StringBuilder groupBuilder = new StringBuilder();
        while (pointer < sourceText.Length - GroupSize) {
            groupBuilder.Clear();
            int offset = pointer + GroupSize;
            for (int i = pointer; i < offset; i++) {
                //prepend a space to allow separation between words in groups. 
                //We can make a substring from this later starting from char 1 
                //to lose the initial whitespace from the string.
                groupBuilder.Append(" ").Append(sourceText[i]);
            }

            String key = groupBuilder.ToString().Substring(1);
            if (!WordGroupInstances.ContainsKey(key)) {
                WordGroupInstances.Add(key, 1);
            } else {
                WordGroupInstances[key]++;
            }

            /**
             * Setting the pointer to increase by group size grabs a group, moves on
             * to the end of the group, and grabs the next.
             * 
             */
            pointer += GroupSize;

            /**
             * Setting the point to increment by 1 grabs a group, advances by 1 word, then
             * grabs the next, so from the phrase - "Hello world, I'm some text", the groups of size 2 would be
             * "Hello world,", "world, I'm", "I'm some" etc...
             */
            //pointer++;
        }

        return WordGroupInstances;

    }

下面的方法被修改为依次生成所有组输出,所以 该 绿色的 绿藻 绿藻 等...

值得注意的是,整个文本必须转换为大写或大写,以便单词不依赖于案例。 我对此进行了一些改进以提高性能(并且不需要中断指令)。

   private Dictionary<String, int> GetAllGroups() {
        Dictionary<string, int> WordGroupInstances = new Dictionary<string, int>();
        StringBuilder groupBuilder = new StringBuilder();
        String[] sourceText = GetSourceText().Split(' ');

        for (int i = 0; i < sourceText.Length; i++) {
            groupBuilder.Clear();
            for (int j = i; j < sourceText.Length; j++) {
                groupBuilder.Append(" ").Append(sourceText[j]);
                String key = groupBuilder.ToString().Substring(1);
                if (!WordGroupInstances.ContainsKey(key)) {
                    WordGroupInstances.Add(key, 1);
                } else {
                    WordGroupInstances[key]++;
                }
            }
        }
        return WordGroupInstances;
    }

在使用文本语料库(288个单词)进行性能测试后,它将在0.171886秒内创建41773个单词组。

答案 1 :(得分:2)

这是一种流式方法,它从可枚举的单词中递归地构建大小为N的组(在本例中为3)。将输入标记为单词并不重要(在本例中我使用了一个简单的正则表达式)。

//tokenize input (enumerable of string)
var words = Regex.Matches(input, @"\w+").Cast<Match>().Select(m => m.Value);

//get word groups (enumerable of string[])
var groups = GetWordGroups(words, 3);

//do what you want with your groups; suppose you want to count them
var counts = new Dictionary<string, int>(StringComparer.CurrentCultureIgnoreCase);
foreach (var group in groups.Select(g => string.Join(" ", g)))
{
    int count;
    counts.TryGetValue(group, out count);
    counts[group] = ++count;
}


IEnumerable<string[]> GetWordGroups(IEnumerable<string> words, int size)
{
    if (size <= 0) throw new ArgumentOutOfRangeException();
    if (size == 1)
    {
        foreach (var word in words)
        {
            yield return new string[] { word };
        }

        yield break;
    }

    var prev = new string[0];

    foreach (var next in GetWordGroups(words, size - 1))
    {
        yield return next;

        //stream of groups includes all groups up to size - 1, but we only combine groups of size - 1
        if (next.Length == size - 1)
        {
            if (prev.Length == size - 1)
            {
                var group = new string[size];
                Array.Copy(prev, 0, group, 0, prev.Length);
                group[group.Length - 1] = next[next.Length - 1];
                yield return group;
            }

            prev = next;
        }
    }
}

这种流式处理方法的一个优点是可以最大限度地减少内存中必须保留的字符串数量(这会减少大型文本内存的使用)。根据您收到输入的方式,另一种优化可能是在您阅读输入时使用TextReader生成标记枚举。

下面是一个中间分组输出的例子(每个项目实际上是一个标记数组,在这里用白色空格连接起来):

The 
green 
The green 
algae 
green algae 
The green algae 
singular 
algae singular 
green algae singular 
green 
singular green 
algae singular green 
alga 
green alga 
singular green alga 

答案 2 :(得分:1)

我认为这很有效。

var text = @"The green algae (singular: green alga) are ..."; // include all your text

var remove = "().,:[]0123456789".Select(x => x.ToString()).ToArray();

var words =
    Regex
        .Matches(text, @"(\S+)")
        .Cast<Match>()
        .SelectMany(x => x.Captures.Cast<Capture>())
        .Select(x => remove.Aggregate(x.Value, (t, r) => t.Replace(r, "")))
        .Select(x => x.Trim().ToLowerInvariant())
        .Where(x => !String.IsNullOrWhiteSpace(x))
        .ToArray();

var groups =
    from n1 in Enumerable.Range(0, words.Length)
    from n2 in Enumerable.Range(1, words.Length - n1)
    select String.Join(" ", words.Skip(n1).Take(n2));

var frequencies =
    groups
        .GroupBy(x => x)
        .Select(x => new { wordgroup = x.Key, count = x.Count() })
        .OrderByDescending(x => x.count)
        .ThenBy(x => x.wordgroup.Count(y => y == ' '))
        .ThenBy(x => x.wordgroup)
        .ToArray();

这给出了连续单词序列的每个单词分组的频率,包括所有单词的单个单词组。

单词数为288.单词组的数为288 x (288 + 1) / 2 = 41,616最终字组数量(在对重复字组进行分组并删除空/空格字符串之后)为41,449。

以下是41,449中的前100个:

  

20 x“the”,13 x“和”,12 x“藻类”,12 x“in”,11 x“green”,10 x“of”,9 x“绿藻”,8 x“ “,6×”为“,6×”种类,5דa”,4ד是”,4ד或”,4ד至”,3ד胚植物”,3ד形式”, 3 x“发现”,3 x“地衣”,3 x“活”,3 x“on”,3 x“植物”,3 x“that”,3 x“藻类和”,3 x“和”, 3 x“as”,3 x“in”,3 x“of”,2 x“alga”,2 x“can”,2 x“clade”,2 x“class”,2 x“colonial” ,2 x“丝状”,2 x“从”,2 x“更高”,2 x“宏观”,2 x“最”,2 x“其他”,2 x“海藻”,2 x“他们的”,2 x“trentepohlia”,2 x“while”,2 x“with”,2 x“藻类”,2 x“是”,2 x“绿藻”,2 x“高等植物”,2 x“在地衣中“,2 x”绿色“,2 x”种类“,2 x”绿色“,2 x”绿色“,2 x”绿藻“,2 x”绿藻“,2 x”of绿藻“,2 x”绿色“,2 x”绿藻“,2 x”绿藻“,1 x”约“,1 x”获得“,1 x”藻类“,1 x”也是“,1 x”关联“,1 x”树皮“,1 x”be“,1 x”both“,1 x”不能“ ,1 x“细胞”,1 x“细胞”,1 x“细胞”,1 x“charales”,1 x“charophyte”,1 x“charophytes”,1 x“chlorarachniophytes”,1 x“chlorophyte”,1 x“叶绿体”,1 x“纤毛”,1 x“最接近”,1 x“球形”,1 x“coenobia”,1 x“菌落”,1 x“导管”,1 x“组成”,1 x“区分“,1 x”分化“,1 x”分裂“,1 x”出现“,1 x”euglenids“,1 x”排除“,1 x”家族“,1 x”少数“,1 x”细丝“ ,1 x“鞭毛”,1 x“鞭毛虫”,1 x“扁虫”,1 x“for”,1 x“形式”,1 x“完整”,1 x“真菌”,1 x“真菌”