【问题标题】:How to replace list of strings in text with items from another list?如何用另一个列表中的项目替换文本中的字符串列表?
【发布时间】:2020-02-22 20:30:36
【问题描述】:

假设我有一个字符串列表 {"boy", "car", "ball"} 和一个文本“the boy sell his car to buy a ball”。

给定另一个字符串列表 {"dog", "bar", "bone"},我的目标是找到文本中第一个列表的所有匹配项,并将它们交换为第二个列表的字符串:

BEFORE: the [boy] sold his [car] to buy a [ball]
AFTER:  the [dog] sold his [bar] to buy a [bone]

我的第一个想法是使用正则表达式,但我不知道如何将字符串列表关联到正则表达式,我不想写 Aho-Corasick。

这样做的正确方法是什么?


另一个例子:

Text: aaa bbb abab aabb bbaa ubab
replacing {aa, bb, ab, ub} for {11, 22, 35, &x}

BEFORE: [aa]a [bb]b [ab][ab] [aa][bb] [bb][aa] [ub][ab]
AFTER:  [11]a [22]b [35][35] [11][22] [22][11] [&x][35]

【问题讨论】:

  • 你能简单地替换第一个目标字符串,然后是第二个,然后是第三个吗?你在优化性能吗?如果是这样,您会收到多少个字符串,它们可以有多长?
  • 我想采用 O(n) 方法。上面的例子很简单,但是文本可以很大,字符串列表也可以,而且它们可能会重复很多。
  • 如果是这样,我认为(但不确定)后缀树可以在 O(n) 中做到这一点,其中 n 是所有字符串的总长度。

标签: c# regex string-matching


【解决方案1】:

不需要使用正则表达式,string.Replace 就足够了

var input = "the boy sold his car to buy a ball";
var oldvalues = new List<string>() { "boy", "car", "ball" };
var newValues = new List<string>() { "dog", "bar", "bone" };
var output = input;
for (int i = 0; i < oldvalues.Count; i++)
{
    output = output.Replace(oldvalues[i], newValues[i]);
}
Console.WriteLine(output);

【讨论】:

  • 我正在寻找更接近 O(n) 的东西,其中 n 是文本的大小,这就是我提到 Aho-Corasick 的原因。
【解决方案2】:

如果你想使用正则表达式,你可以使用这样的:

var findList = new List<string>() { "boy", "car", "ball" };
var replaceList = new List<string>() { "dog", "bar", "bone" };

// Create a dictionary from the lists or have a dictionary from the beginning.
var dictKeywords = findList.Select((s, i) => new { s, i })
                           .ToDictionary(x => x.s, x => replaceList[x.i]);

string input = "the boy sold his car to buy a ball";
// Construct the regex pattern by joining the dictionary keys with an 'OR' operator.
string pattern = string.Join("|", dictKeywords.Keys.Select(s => Regex.Escape(s)));

string output =
    Regex.Replace(input, pattern, delegate (Match m)
    {
        string replacement;
        if (dictKeywords.TryGetValue(m.Value, out replacement)) return replacement;
        return m.Value;
    });

Console.WriteLine(output);

输出:

狗为了买一根骨头卖掉了他的酒吧

【讨论】:

  • Like :) 作为说明,不需要匿名类型,Select 的元组就足够了:var dictKeywords = findList.Select((s, i) =&gt; (s, i)).ToDictionary(x =&gt; x.s, x =&gt; replaceList[x.i]);(作为辅助说明,这不是O(n),而是这是要测试的东西)
  • 复杂度是多少?
  • @Jimi 正确。但这需要 C# 7.1(许多开发人员尚未升级)。出于同样的原因,我也没有对 out 参数使用内联声明(需要 C# 7.0,IIRC)。
  • @Daniel 弄清楚正则表达式的确切时间复杂度并不容易(我必须承认我不太擅长分析时间复杂度)。你必须做一些测试。在相关说明中,如果您使用RegexOptions.Compiled可能会获得更好的性能,但同样,这一切都归结为 使用真实数据测试这两个选项,因为 YMMV。查看thisthis 了解更多信息。
  • 这些是 Aho–Corasick 的 4 个实现。 FirstSecond(都很简单)、Third(可能不会在这里)和Fourth(最近的 NuGet 包)。正确使用此算法意味着使其适应特定要求。无论如何,它不是 O(n)(只是线性搜索模式)。
猜你喜欢
  • 1970-01-01
  • 2019-12-24
  • 2022-07-18
  • 1970-01-01
  • 2021-07-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多