从文本中删除停用词 C#

4
我希望您可以从输入的字符串中删除一个停用词数组,以下是我的步骤:
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)个停用词并且输入字符串很长时,这是执行此任务的最佳方式吗?我更喜欢使用replace方法,因为我想在不同形态出现停用词时删除它们。例如,如果停用词是“do”,则从(doing,does等)中删除“do”。是否有更好、更快的处理建议?提前致谢。


请查看以下链接:https://dev59.com/B2445IYBdhLWcg3wpL1_ - Dot_Refresh
6个回答

4

我可以建议使用 StringBuilder 吗?

http://msdn.microsoft.com/zh-cn/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, "");
}

由于它在自己的数据结构内进行所有处理,且不分配数百个新字符串,因此我相信您会发现它更具有内存效率。


2
仍然,输入被扫描了450次。 - Nicolas Repiquet
2
尼古拉斯,你有什么建议可以避免每次都扫描检查工作吗?我想不出任何避免这种情况的实现方法。 - akatakritos

3

这个问题有几个方面需要考虑

过早优化
给出的方法有效且易于理解/维护。它是否导致性能问题? 如果没有问题,则不用担心。如果它曾经引起了问题,则再去看看。

期望结果
在示例中,您想要输出的是什么?

"Did you this asking"

或者

"Did you  this   asking"

您已经在 "try" 和 "before" 的末尾添加了空格,但是没有添加到 "yourself". 为什么?是打错字了吗?
string.Replace() 区分大小写。如果您关心大小写,您需要修改代码。
使用部分内容很麻烦。 单词在不同的时态下会发生变化。例如,'doing' 中的 'do' 被删除了,但是 'take' 和 'taking' 呢? 停用词的顺序很重要,因为您正在更改输入。可能(我不知道有多大可能性)在更改之前未在输入中出现的单词在更改后“出现”在输入中。您希望每次都返回并重新检查吗?
您真的需要删除部分内容吗?
优化: 当前方法将通过输入字符串 n 次,其中 n 是要删除的单词数,在每次替换发生时创建一个新字符串。这确实很慢。 使用 StringBuilder(如 akatakritos 所述)可以提高速度,因此我会首先尝试这样做。再次测试以查看是否足够快。
Linq 可以使用。
编辑: 只是按 ' ' 进行拆分以演示。您还需要考虑标点符号,并决定它们应该如何处理。
[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()方法(修改为使用字符串构建器并忽略大小写)似乎是一个很好的第一步解决方案。在尝试更复杂的内容之前,应在可能的性能条件下进行基准测试。

祝好,
艾伦。


对于这个例子中的"take"和"taking"并不是问题,因为我正在使用另一种语言,不存在前面提到的问题。 - Dedar
我使用的字符串只使用空格分隔。 - Dedar
@Dedar 目前,除非出现性能问题(如果有的话),否则我会选择修改后的 Replace(),使用 StringBuilder 并允许不同的情况。 - AlanT

2
这是您要的:
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)相对高效地完成,但当“do”不是某些要删除的东西的前缀时怎么办,例如“did”?或者当它是某些不应该被删除的东西的前缀时,例如“dog”?

顺便提一下,如果要删除不考虑大小写的单词,只需将相应的不区分大小写比较器传递给 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 在内部使用哈希表),但很整洁 ;)

1

如果您想要一种简单的方法从句子中删除一系列字符串,并将结果聚合在一起,可以按照以下步骤进行:

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

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

结果将是:"在提问之前你做过这个吗"


不知道它是否高效,但它很聪明! - Nicolas Repiquet

0

缩短您的代码,并使用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());

如果我使用哈希表来定位停用词,您是否同意? - Dedar

0
String.Join(" ",input.
          Split(' ').Where(w=>stop.Where(sW=>sW==w).
                   FirstOrDefault()==null).ToArray());

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