【问题标题】:remove stop words from text C#从文本 C# 中删除停用词
【发布时间】:2012-05-05 05:00:25
【问题描述】:

我想从输入字符串中删除一组停用词,我有以下过程

string[] arrToCheck = new string[] { "try ", "yourself", "before " };

string input = "Did you try this yourself before asking";
foreach (string word in arrToCheck )
{
input = input.Replace(word, "");
}

这是执行此任务的最佳方法吗,特别是当我有 (450) 个停用词并且输入字符串很长时?我更喜欢使用替换方法,因为当停用词出现在不同的形态中时,我想删除它们。例如,如果停用词是“do”,则从(doing、does 等)中删除“do”。有什么更好和最快处理的建议吗?提前致谢。

【问题讨论】:

标签: c#


【解决方案1】:

我可以推荐一个StringBuilder吗?

http://msdn.microsoft.com/en-us/library/system.text.stringbuilder.aspx

string[] arrToCheck = new string[] { "try ", "yourself", "before " };

StringBuilder input = new StringBuilder("Did you try this yourself before asking");
foreach (string word in arrToCheck )
{
    input.Replace(word, "");
}

因为它在自己的数据结构中完成所有处理,并且不分配数百个新字符串,我相信您会发现它的内存效率要高得多。

【讨论】:

  • 仍然,输入被扫描了 450 次。
  • Nicolas,你有什么建议不要扫描每张支票吗?我想不出任何可以避免这种情况的实现。
【解决方案2】:

这有几个方面

过早的优化
给出的方法有效并且易于理解/维护。它会导致性能问题吗? 如果没有,那就不用担心了。如果它引起了问题,请查看它。

预期结果
在示例中,您希望输出是什么?

"Did you this asking"

"Did you  this   asking"

您在“try”和“before”的末尾添加了空格,但没有在“yourself”后面添加空格。为什么?错字?

string.Replace() 区分大小写。如果您关心大小写,则需要修改代码。

使用部分是一团糟。
单词在不同的时态中变化。从“doing”词中删除“do”的例子,但是“take”和“take”呢? 停用词的顺序很重要,因为您正在更改输入。有可能(我不知道有多大可能但有可能)在更改之前不在输入中的单词在更改之后“出现”在输入中。每次都要回去复查吗?

你真的需要删除部分吗?

优化
当前方法将通过输入字符串 n 次工作,其中 n 是要编辑的单词数,每次替换时创建一个新字符串。这慢。

使用 StringBuilder(上面的akatakritos)会加快速度,所以我会先试试这个。重新测试看看这是否足够快。

可以使用Linq

编辑
只是用''分割来演示。您还需要考虑标点符号并决定如何处理它们。
END EDIT

[TestMethod]
public void RedactTextLinqNoPartials() {

    var arrToCheck = new string[] { "try", "yourself", "before" };
    var input = "Did you try this yourself before asking";

    var output = string.Join(" ",input.Split(' ').Where(wrd => !arrToCheck.Contains(wrd)));

    Assert.AreEqual("Did you this asking", output);

}

将删除所有整个单词(以及空格。将无法看到从哪里删除了单词),但如果没有一些基准测试,我不会说它更快。

使用 linq 处理部分内容会变得很麻烦,但如果我们只需要一次通过(不检查“发现的”单词)就可以工作

[TestMethod]
public void RedactTextLinqPartials() {

    var arrToCheck = new string[] { "try", "yourself", "before", "ask" };
    var input = "Did you try this yourself before asking";

    var output = string.Join(" ", input.Split(' ').Select(wrd => {
        var found = arrToCheck.FirstOrDefault(chk => wrd.IndexOf(chk) != -1);
            return found != null
                   ? wrd.Replace(found,"")
                   : wrd;
    }).Where(wrd => wrd != ""));


    Assert.AreEqual("Did you this ing", output);

}

从这个角度来看,我会说它比 string.Replace() 慢,但没有一些数字就无法判断。这肯定更复杂。

底线
String.Replace() 方法(修改为使用字符串生成器并且不区分大小写)看起来是一个很好的首选解决方案。在尝试更复杂的事情之前,我会在可能的性能条件下对其进行基准测试。

hth,
艾伦。

【讨论】:

  • 对于示例“take”“take”不会有问题,因为我正在使用另一种语言,没有前面提到的问题。
  • 我使用的字符串只用空格分隔。
  • @Dedar 目前,直到出现性能问题(如果出现),我会使用修改后的 Replace(),使用 stringbuilder 并允许不同的情况。
【解决方案3】:

给你:

var words_to_remove = new HashSet<string> { "try", "yourself", "before" };
string input = "Did you try this yourself before asking";

string output = string.Join(
    " ",
    input
        .Split(new[] { ' ', '\t', '\n', '\r' /* etc... */ })
        .Where(word => !words_to_remove.Contains(word))
);

Console.WriteLine(output);

打印出来:

Did you this asking

HashSet 提供了极其快速的查找,因此words_to_remove 中的 450 个元素应该完全没有问题。此外,我们只遍历输入字符串一次(而不是像您的示例中那样每个单词删除一次)。

但是,如果输入字符串很长,有一些方法可以提高内存效率(如果不是更快的话),方法是不要一次将拆分结果保存在内存中。

不仅要删除“do”,还要删除“doing”、“does”等...您必须在words_to_remove 中包含所有这些变体。如果您想以一般方式删除前缀,则可以(相对)有效地使用要删除的单词trie(或者输入字符串的suffix tree),但是当“做”时该怎么办not 是不是应该删除的前缀,例如“did”?或者当它是不应该被删除的东西的前缀时,比如“狗”?

顺便说一句,要删除任何大小写的单词,只需将适当的不区分大小写的比较器传递给HashSet 构造函数,例如StringComparer.CurrentCultureIgnoreCase

--- 编辑 ---

这是另一种选择:

var words_to_remove = new[] { " ", "try", "yourself", "before" }; // Note the space!
string input = "Did you try this yourself before asking";

string output = string.Join(
    " ",
    input.Split(words_to_remove, StringSplitOptions.RemoveEmptyEntries)
);

我猜它应该会更慢(除非string.Split 在内部使用哈希表),但它很整洁;)

【讨论】:

    【解决方案4】:

    对于从句子中删除字符串列表并将结果重新聚合在一起的简单方法,您可以执行以下操作:

    var input = "Did you try this yourself before asking"; 
    var arrToCheck = new [] { "try ", "yourself", "before " };
    var result = input.Split(arrToCheck, 
                             arrToCheck.Count(), 
                             StringSplitOptions.None)
                      .Aggregate((first, second) => first + second);
    

    这将通过单词分隔符将原始字符串分开,并使用拆分数组中的结果集创建一个最终字符串。

    结果将是,"Did you this before asking"

    【讨论】:

    • 不知道它是否有效,但它很聪明!
    【解决方案5】:

    缩短您的代码,并使用 LINQ

    string[] arrToCheck = new string[] { "try ", "yourself", "before " };   
    var test = new StringBuilder("Did you try this yourself before asking"); 
    
    arrToCheck.ForEach(x=> test = test.Replace(x, "")); 
    
    Console.Writeln(test.ToString());
    

    【讨论】:

    • 如果我使用哈希表来定位停用词,你同意吗?
    【解决方案6】:
    String.Join(" ",input.
              Split(' ').Where(w=>stop.Where(sW=>sW==w).
                       FirstOrDefault()==null).ToArray());
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-06-21
      • 1970-01-01
      • 2021-03-21
      • 1970-01-01
      • 2015-04-11
      • 2011-05-03
      • 2013-05-12
      • 1970-01-01
      相关资源
      最近更新 更多