如何有效地检查二进制模式是否是另一个二进制模式的子模式

时间:2014-05-15 01:13:54

标签: c# string

我有一个由01*(通配符)组成的字符串,这称为二进制模式,例如0*10*

假设我有一个模式列表,例如[11010, 0010*, 0*11*, 1*100],只有0010*0*10*的子架构。

所有被比较的模式保证长度相同,但最初可以在程序中设置。

编辑: 到目前为止,这是我能想到的步骤解决方案:

  1. 在两个模式中查找通配符索引,并比较一个是否是另一个的超集。
  2. 如果为true,则将超集索引中的所有字符都删除到两个模式字符串。
  3. 如果两个修剪后的模式具有相同的字符串,则
  4. 返回true。
  5. 有更有效的方法吗?我的意思是高效的是执行速度/尽可能少的迭代,因为将非常频繁地调用检查器。

3 个答案:

答案 0 :(得分:2)

如果我正确地理解了这个问题,那么这应该做你想要的,并且假设不匹配的位置是公正的,它尽可能少地工作。但是,不使用LINQ可能会更快。如果您只需要一次结果列表就可以在不将结果转换为列表的情况下离开。

var s = "0*10*";

var sscs = new[] { "11010", "0010*", "0*11*", "1*100" };

var sss = sscs.Where(ssc => s.Zip(ssc, (a, b) => (a == b) || (a == '*')).All(_ => _)).ToList();

每个子模式候选者都会逐个符号地与指定的模式进行比较。如果所有符号匹配或者模式在不匹配的情况下具有通配符,则子模式候选者是子模式。如果存在不匹配且架构没有通配符,则会立即中止比较。

我大量缩写变量名称以使其(几乎)适合。

s     schema
sscs  subschema candidates
ssc   subschema candidate
sss   subschemas
a     symbol in schema
b     symbol in subschema candidate

答案 1 :(得分:0)

完全确定你在问什么,但我假设你有一个你正在使用的模式的起始列表,并且该列表是唯一的(没有子集等)。

定义一个简单的IsSubsetOf()函数,然后将其作为Linq'any'调用的一部分调用,或者你可以在for循环中执行:

var startingSchemas = new [] { "100**101", "110*101*", "1111*000" };

startingSchemas.Any(x => IsSubsetOf(x, "11****11")); // false
startingSchemas.Any(x => IsSubsetOf(x, "11011010")); // true

public bool IsSubsetOf(string main, string sub)
{
    for (var i = 0; i < main.Length; i++)
    {
        if (main[i] == '*') continue;    // main is '*', so anything is ok
        if (main[i] == sub[i]) continue; // equal is ok, both 1/0/*
        return false; // if not equal, sub[i] could be *, or the opposite of main[i]
    }
    return true;
}

我认为您可能需要澄清的一个问题是,当您发现不是子集的内容时,您想要做什么,但当与其他架构结合使用时,它

1*1 => 101 or 111
0*1 => 001 or 011  (not a subset of 1*)

但这两个组合= **1架构或{001, 011, 101, 111}

您是否想要获取字符串列表然后将其缩减为仍然匹配相同输入的最小模式集? IsSubset(x,y) = false,但IsSubset(y,x) = true

<强>增加: 如果还没有,那么使起始数据非常容易:

var uniqueSchemas = startingSchemas.Distinct().ToList();
uniqueSchemas.RemoveAll(x => uniqueSchemas.Any(y => y != x && IsSubsetOf(y, x)));

已编译(发布,无pdb,优化):

for (int index = 0; index < main.Length; ++index)
{
    if ((int) main[index] != 42 && (int) main[index] != (int) sub[index])
      return false;
}

<强>性能

非常粗糙的性能检查。在分配给vm的i7 / 4gb 1核心上运行并行VM,运行其他进程等。

  • 200个模式(随机生成,800长度)
  • 1个测试字符串(800长度,以相同的方式随机生成)
  • 每个运行1百万

输出:(所有运行在外部+/- 500ms,通常一致

// unsafe = new 'public static void unsafe IsSubsetOf' function using pointers
//   safe = standard method
Any() warmup : elapsed = 11965 (.0120 ms)
Any() safe   : elapsed = 11300 (.0113 ms)
Any() unsafe : elapsed = 10754 (.0108 ms)
for() safe   : elapsed = 11480 (.0115 ms)
for() unsafe : elapsed =  7964 (.008  ms)

所以,这就是我从中获得的。如果有一个聪明的数据结构,我不知道。

不安全版本

不保证100%正确。我通常不这样做,我不知道我看到的差异是因为测试工具还是代码。此外,免责声明,自从我写了一小段不安全的代码以来,已经过了6年。但是我没有以这种方式为性能提供.net,通常存在更大的瓶颈......如果你使用unsafe代码,我唯一的建议就是不要修改任何东西。如果您只是read,那么您应该非常安全。检查所有界限!

private unsafe static bool IsSubsetOfUnsafe(String main, String sub)
{
    if (main == null && sub == null) return true; // is this what you want? decide
    if (main == null || sub == null) return false; // this too? maybe if null then say "true" and discard?
    if (main.Length != sub.Length) return false;

    fixed (char* m = main)
    fixed (char* s = sub)
    {
        var m1 = m;
        var s1 = s;
        int len = main.Length;
        for (int i = 0; i < len; ++i)
        {
            if ((int)m1 != 42 && m1 != s1) return false;
            m1++;
            s1++;
        }
        return true;
    }
} 

答案 2 :(得分:0)

不幸的是,我仍然不完全明白你在做什么,但无论如何我会提出我的想法,也许它很有用。

中心思想是用更紧凑的位表示替换字符串表示 - 例如,字符串1*10变为110011100xCE。由于一个符号占用2位,因此可以将32个符号打包到一个UInt64中,较长的字符串将成为UInt64 s的数组。

0 => 10
1 => 11
* => 00
     01 => unused

现在您可以找到具有以下LINQ表达式的子文档

var sss = sscs.Where(ssc => s.Zip(ssc, (a, b) => (a ^ b) & ((a & 0xAAAAAAAAAAAAAAAA) | ((a & 0xAAAAAAAAAAAAAAAA) >> 1))).All(x => x == 0)).ToList();

这就像我之前的回答一样,使比较更有意义。显而易见的优点是它可以并行处理32个符号,实际上它比我以前的答案快30倍。但实际上我有点失望,因为我的速度提高了100倍,因为压缩表示也意味着更少的内存流量,但使用LINQ的开销可能是实际的瓶颈。所以我把它变成了普通的for循环,这使得它比LINQ字符串版本快130倍。但是,如果它可以深度集成到您的应用程序中,这只是非常有用,因为字符串表示和此表示之间的转换非常昂贵。