【问题标题】:string replace using a List<string>使用 List<string> 替换字符串
【发布时间】:2011-04-12 08:33:45
【问题描述】:

我有一个我想忽略的单词列表,就像这样:

public List<String> ignoreList = new List<String>()
        {
            "North",
            "South",
            "East",
            "West"
        };

对于给定的字符串,比如说"14th Avenue North",我希望能够删除“North”部分,所以基本上是一个在调用时会返回"14th Avenue "的函数。

我觉得我应该可以通过混合使用 LINQ、正则表达式和替换来做一些事情,但我就是想不通。

更大的图景是,我正在尝试编写地址匹配算法。在使用 Levenshtein 算法评估相似度之前,我想过滤掉“Street”、“North”、“Boulevard”等词。

【问题讨论】:

  • 但这不是一行@htw。如果不是一条线,您将不会获得任何极客积分。
  • 不要让这个程序在北卡罗来纳州夏洛特运行。著名的道路名称恰好是东大道、南大道、西大道。这些是道路的名称,而不是现在您在 West 1st Street。 在其他情况下,您的方向不是真正的方向,而是道路的关键部分标识符。北安普顿、北湖(夏洛特的购物中心/地区)、北卡罗来纳州、北达科他州等
  • @Anthony :这是真的,我会小心我在字典中输入的内容。但是,我首先匹配邮政编码 (zip),它必须完全匹配才能使函数甚至考虑地址。从那里开始,我真的不介意我宁愿得到误报而不是错过结果。
  • 那么你会很高兴知道东、西和南大道都相交!他们将共享一个拉链!我相信如果你能让你的程序在夏洛特运行,你就可以让它在任何地方运行。
  • 加拿大完全没有北/南街道/林荫大道?我认为安东尼的评论比你的问题陈述更笼统。

标签: c# .net regex string replace


【解决方案1】:

这个怎么样:

string.Join(" ", text.Split().Where(w => !ignoreList.Contains(w)));

或对于 .Net 3:

string.Join(" ", text.Split().Where(w => !ignoreList.Contains(w)).ToArray());

请注意,此方法将字符串拆分为单个单词,因此它只会删除整个单词。这样它就可以正常处理Northampton Way #123string.Replace 无法处理的地址。

【讨论】:

  • 这是一个很好的解决方案,比正则表达式版本更短更清晰。
  • 你不妨用text.Split(ignoreList.ToArray(), StringSplitOptions.None)这个词来分开。也就是说,调整您的方法来忽略大小写会更容易。
  • 单词前后的标点符号呢?
  • Kobi: text.Split(ignoreList.ToArray()) 不起作用的原因与所有 string.Replace 方法不起作用的原因相同。
  • Mark:大概他会认为标点符号是断词器。这取决于他,但我猜他会想要text.Split(new[]{' ','.',',','-'}),但他可以调整它以支持他拥有的任何算法。
【解决方案2】:
Regex r = new Regex(string.Join("|", ignoreList.Select(s => Regex.Escape(s)).ToArray()));
string s = "14th Avenue North";
s = r.Replace(s, string.Empty);

【讨论】:

  • 如果有特殊字符,应该将ignoreList中的内容转义:string.Join("|", ignoreList.select(s => Regex.Escape(s)).ToArray())
  • 由于列表中可能包含"St." 之类的词,因此建议转义。而且您必须只查找整个单词。
  • @Frank 正确。 . .尽管并没有真正指定列表的来源。最简单的方法可能是首先编写正确的正则表达式,而不是从列表中转换它,除非该列表确实需要。
  • 是的,动态构建正则表达式只有在列表内容可能发生变化的情况下才真正值得。一般来说,使用正则表达式只有在这个函数被大量使用时才有用,因为它可能比 N 个字符串替换更快。
【解决方案3】:

这样的事情应该可以工作:

string FilterAllValuesFromIgnoreList(string someStringToFilter)
{
  return ignoreList.Aggregate(someStringToFilter, (str, filter)=>str.Replace(filter, ""));
}

【讨论】:

  • 我可能已经将参数换成了第二个 lambda,但这肯定会奏效,Aggregate 是一种非常强大的方法,它的跛脚的人不经常使用它
  • 应该注意的是,我怀疑多次调用 Replace 不是最有效的方法。可能将列表的内容构建到静态正则表达式中并使用它来替换会更快,但我怀疑在这种情况下差异并不重要。
  • 这是不正确的,因为它使用了string.Replace,它不能仅在单词边界上匹配。但是,如果您要使用 RegEx,则应该使用单个编译的。
  • 好点@Gabe这个例子更多的是关于Aggregate的使用而不是Replace。
【解决方案4】:

简单的 for 循环有什么问题?

string street = "14th Avenue North";
foreach (string word in ignoreList)
{
    street = street.Replace(word, string.Empty);
}

【讨论】:

    【解决方案5】:

    如果您知道单词列表仅包含不需要在正则表达式中转义的字符,那么您可以这样做:

    string s = "14th Avenue North";
    Regex regex = new Regex(string.Format(@"\b({0})\b",
                            string.Join("|", ignoreList.ToArray())));
    s = regex.Replace(s, "");
    

    结果:

    第 14 大道

    如果有特殊字符,你需要修复两件事:

    • 对忽略列表的每个元素使用 Regex.Escape。
    • 字边界\b 将不匹配后跟符号的空格,反之亦然。您可能需要改为使用环视断言来检查空格(或其他分隔字符,如标点符号)。

    以下是解决这两个问题的方法:

    Regex regex = new Regex(string.Format(@"(?<= |^)({0})(?= |$)",
        string.Join("|", ignoreList.Select(x => Regex.Escape(x)).ToArray())));
    

    【讨论】:

    • 很有可能他的话需要转义,因为它们会像"St.", "Blvd.", "Rd."
    • 这是处理另一条评论中提出的空间问题的好方法。
    • 这非常聪明,而且它似乎适用于所有单词。我会为它编写一些测试并正确尝试。
    【解决方案6】:

    如果它是您示例中的短字符串,您可以循环遍历字符串并一次替换一个。如果你想变得花哨,你可以使用 LINQ Aggregate 方法来做到这一点:

    address = ignoreList.Aggregate(address, (a, s) => a.Replace(s, String.Empty));
    

    如果它是一个大字符串,那会很慢。相反,您可以在一次遍历字符串中替换所有字符串,这要快得多。我在this answer 中为此做了一个方法。

    【讨论】:

    • 非常感谢。我的忽略列表显然会比我在这里发布的要长得多,但不确定它是否足够长以使用您的方法。我将对其进行分析并查看。
    【解决方案7】:

    LINQ 使这变得简单易读。不过,这需要规范化的数据,特别是它区分大小写。

    List<string> ignoreList = new List<string>()
    {
        "North",
        "South",
        "East",
        "West"
    };    
    
    string s = "123 West 5th St"
            .Split(' ')  // Separate the words to an array
            .ToList()    // Convert array to TList<>
            .Except(ignoreList) // Remove ignored keywords
            .Aggregate((s1, s2) => s1 + " " + s2); // Reconstruct the string
    

    【讨论】:

    • .ToList() 是不必要的。
    【解决方案8】:

    为什么不直接保持简单?

    public static string Trim(string text)
    {
       var rv = text.trim();
       foreach (var ignore in ignoreList) {
          if(tv.EndsWith(ignore) {
          rv = rv.Replace(ignore, string.Empty);
       }
      }
       return rv;
    }
    

    【讨论】:

      【解决方案9】:

      如果您愿意,可以使用 and 表达式来完成此操作,但它比使用聚合更容易扭转它。我会这样做:

      string s = "14th Avenue North"
      ignoreList.ForEach(i => s = s.Replace(i, ""));
      //result is "14th Avenue "
      

      【讨论】:

        【解决方案10】:
        public static string Trim(string text)
        {
           var rv = text;
           foreach (var ignore in ignoreList)
              rv = rv.Replace(ignore, "");
           return rv;
        }
        

        为 Gabe 更新


        public static string Trim(string text)
        {
           var rv = "";
           var words = text.Split(" ");
           foreach (var word in words)
           {
              var present = false;
              foreach (var ignore in ignoreList)
                 if (word == ignore)
                    present = true;
              if (!present)
                 rv += word;
           }
           return rv;
        }
        

        【讨论】:

        • 没有 LINQ,没有 RegExp,但它是正确的。我唯一要改变的是使用空字符串文字。
        • 不,不正确。这会将“123 Northampton”变成“123 ampton”。
        • 关闭...现在您需要确保放回单词之间的空格。
        【解决方案11】:

        如果你有一个列表,我认为你将不得不触摸所有项目。您可以使用所有忽略关键字创建一个大型正则表达式并替换为String.Empty

        这是一个开始:

        (^|\s+)(North|South|East|West){1,2}(ern)?(\s+|$)
        

        如果您有一个用于忽略单词的 RegEx,您可以对要传递给算法的每个短语进行一次替换。

        【讨论】:

        • 我想我们可以。不过,我们真的想要吗?
        • 这是一个好的开始。现在让它只匹配整个单词。
        • 我们使用这种方法根据查看数据生成的 RegEx 关键字将大量客户标记为商业或住宅客户。
        猜你喜欢
        • 1970-01-01
        • 2017-02-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-04-06
        • 1970-01-01
        • 1970-01-01
        • 2011-07-25
        相关资源
        最近更新 更多