【问题标题】:String parsing and matching algorithm字符串解析和匹配算法
【发布时间】:2012-02-20 00:14:00
【问题描述】:

我正在解决以下问题:

假设我有一个软件包列表,它们的名称可能如下所示(唯一已知的是这些名称的格式类似于SOMETHING + VERSION,这意味着版本总是在之后 名字):

Efficient.Exclusive.Zip.Archiver-PROPER.v.122.24-EXTENDED
Efficient.Exclusive.Zip.Archiver.123.01
Efficient-Exclusive.Zip.Archiver(2011)-126.24-X
Zip.Archiver14.06
Zip-Archiver.v15.08-T
Custom.Zip.Archiver1.08
Custom.Zip.Archiver1

现在,我需要解析这个列表并只选择每个包的最新版本。对于这个例子,预期的结果是:

Efficient-Exclusive.Zip.Archiver(2011)-126.24-X
Zip-Archiver.v15.08-T
Custom.Zip.Archiver1.08

我目前使用的方法可以这样描述:

Split the initial strings into groups by their starting letter,
ignoring spaces, case and special symbols.
(`E`, `Z`, `C` for the example list above)

Foreach element {

    Apply the regular expression (or a set of regular expressions),
    which tries to deduce the version from the string and perform
    the following conversion `STRING -> (VERSION, STRING_BEFORE_VERSION)`

    // Example for this step:
    // 'Efficient.Exclusive.Zip.Archiver-PROPER.v.122.24-EXTENDED' ->
    // (122.24, Efficient.Exclusive.Zip.Archiver-PROPER)

    Search through the corresponding group (in this example - the 'E' group)
    and find every other strings, which starts from the 'STRING_BEFORE_VERSION' or
    from it's significant part. This comparison is performed in ignore-case and
    ignore-special-symbols mode.

    // The matches for this step:
    // Efficient.Exclusive.Zip.Archiver-PROPER, {122.24}
    // Efficient.Exclusive.Zip.Archiver, {123.01}
    // Efficient-Exclusive.Zip.Archiver, {126.24, 2011}

    // The last one will get picked, because year is ignored.

    Get the possible version from each match, ***pick the latest, yield that match.***

    Remove every possible match (including the initial element) from the list.
}

这个算法(我假设)应该适用于O(N * V + N lg N * M),其中M 代表平均字符串匹配时间,V 代表版本正则表达式工作时间。


不过,我怀疑有更好的解决方案(总是有!),可能是特定的数据结构或更好的匹配方法。

如果您可以提出一些建议对当前方法做一些说明,请不要犹豫。

【问题讨论】:

  • 也许找到最长的公共子串会有所帮助? en.wikipedia.org/wiki/Longest_common_substring_problem。您可以在 SO 和 Web 的其他地方找到许多讨论和实现。
  • @Jim 最长公共子串问题有助于两个字符串之间的比较,但不能优化要完成的比较次数。我知道 OP 已经知道如何确定 2 个包是否相等,并且只想使用某种集群更快地做到这一点。
  • 您的运行时分析是错误的:它是 O(N^2) 而不是 O(N log N) (去除了常数因子),因为您仅基于范围有限的第一个字符进行分组(字母表),然后将每个条目与同一组中的所有其他条目进行比较。

标签: c# string algorithm version string-matching


【解决方案1】:

这个怎么样? (伪代码)

Dictionary<string,string> latestPackages=new Dictionary<string,string>(packageNameComparer);

foreach element
{
    (package,version)=applyRegex(element);

    if(!latestPackages.ContainsKey(package) || isNewer)
    {
        latestPackages[package]=version;
    }
}

//print out latestPackages

字典操作是 O(1),所以你有 O(n) 的总运行时间。无需预先分组,无需存储所有匹配项,您只需存储当前最新的匹配项。

Dictionary 有一个接受 IEqualityComparer 对象的构造函数。在那里,您可以实现自己的包名称之间的相等语义。但是请记住,您需要在此 IEqualityComparer 中实现 GetHashCode 方法,该方法应为您认为相等的对象返回相同的值。要重现上面的示例,您可以返回字符串中第一个字符的哈希码,这将重现您在字典中的分组。但是,您将使用更智能的哈希码获得更高的性能,它没有太多的冲突。如果仍然能产生好的结果,也许可以使用更多字符。

【讨论】:

  • 问题是我不能总是执行 exact (package, version) 拆分,通常我会得到一个 (text_which_CONTAINS_name, version) 对。因此我应该能够匹配名称,例如在前 70% 处匹配(或类似的名称)。 可以对字典进行某种调整以进行这种比较(还包括忽略大小写和特殊符号)吗?
  • 我怀疑这不能使用基于哈希的字典轻松完成,但我可能错了。
  • 我也认为这可以使用一些经过调整的后缀树实现来完成,但我怀疑由此产生的速度是否值得(由于实现复杂性)。
  • 我添加了更多信息。如果您需要一个包含 n 个字符的公共前缀,以便没有很多包共享相同的前缀,您可以根据此前缀计算哈希码(在您的自定义比较器中)。
  • 字典版本可能是最容易实现和理解的,如果你做对了,它对于甚至大量 n 的性能应该足够了。如果您的 n 在几千个条目的范围内,即使您的原始版本在性能方面也不应该有问题。
【解决方案2】:

我认为您可以在这里使用 DAWG (http://en.wikipedia.org/wiki/Directed_acyclic_word_graph) 来获得良好的效果。我认为你可以简单地循环每个节点,直到你找到一个只有 1 个“孩子”的节点。在此节点上,您将在下面的树和版本字符串“向上”使用公共前缀。从那里,通过删除不是数字或句点的所有内容来解析版本字符串,按句点拆分字符串并将数组的每个元素转换为整数。这应该为每个版本字符串提供一个 int 数组。确定最高版本,记录它并前往只有 1 个孩子的下一个节点。

编辑:填充大型 DAWG 是一项相当昂贵的操作,但查找速度非常快。

【讨论】:

    猜你喜欢
    • 2013-06-17
    • 1970-01-01
    • 1970-01-01
    • 2013-12-24
    • 2016-07-19
    • 2021-11-06
    • 2012-07-24
    • 2010-09-08
    • 2013-07-02
    相关资源
    最近更新 更多