【问题标题】:Regex Style Pattern Matching in .NET Object Lists.NET 对象列表中的正则表达式样式模式匹配
【发布时间】:2016-05-03 14:52:57
【问题描述】:

我有一组具有各种属性的 .NET 对象。可以说它是遗传密码中的一串染色体——尽管对象数据比这要复杂一些。我想在列表中搜索预定义的对象序列。我可以将对象定义为有限数量的唯一感兴趣类型。 R,B,D 并且在一个庞大的列表中我想找到某些对象序列:

一个大大简化的版本是:

public class Chromosome {
    public ChromosomeType CromosomeType { 
       get {
        // Some logic that works out and returns the correct chromosome type

       }
    }
}

public enum ChromosomeType {
  R, B, D
}

所以给定了大量这些类型的集合。我想匹配某些序列

例如"R+B{3}D+"

所以在上面的“正则表达式”中,以下子序列将在列表中匹配: RRRBBBDD

我需要能够从很长的对象列表中返回所有匹配项。

显然正则表达式非常适合这个,但我实际上没有字符串,我有对象的集合。

在对象集合中搜索预定义序列的最佳方法是什么?

更新

Colin 的解决方案是我最终采用的解决方案。它工作得很好。我对其进行了更新,使其能够处理多个匹配项,并使用数组以尽可能快

这是最终的工作解决方案:

    public static class ChromosomesExtensions
    {
        public static IEnumerable<Chromosome[]> FindBySequence(this Chromosome[] chromosomes, string patternRegex)
        {
            var sequenceString
                = String.Join(
                    String.Empty, //no separator
                    (
                        from c in chromosomes
                        select c.CromosomeType.ToString()
                    )
                );
            MatchCollection matches = Regex.Matches(sequenceString, patternRegex);

            foreach (Match match in matches)
            {
                Chromosome[] subset = new Chromosome[match.Value.Length];

                var j = 0;
                for (var i = match.Index; i < match.Index + match.Length; i++)
                {
                    subset[j++] = chromosomes[i];
                }
                yield return subset;
            }
        }
    }

    [TestFixture]
    public class TestClass
    {
        [Test]
        public void TestMethod()
        {
            var chromosomes =
                new[]
                {
                    new Chromosome(){ CromosomeType = ChromosomeType.D, Id = 1},
                    new Chromosome(){ CromosomeType = ChromosomeType.R, Id = 2 },
                    new Chromosome(){ CromosomeType = ChromosomeType.R, Id = 3 },
                    new Chromosome(){ CromosomeType = ChromosomeType.B, Id = 4 },
                    new Chromosome(){ CromosomeType = ChromosomeType.B, Id = 5 },
                    new Chromosome(){ CromosomeType = ChromosomeType.B, Id = 6 },
                    new Chromosome(){ CromosomeType = ChromosomeType.D, Id = 7 },
                    new Chromosome(){ CromosomeType = ChromosomeType.D, Id = 8 },
                    new Chromosome(){ CromosomeType = ChromosomeType.B, Id = 9 },
                    new Chromosome(){ CromosomeType = ChromosomeType.R, Id = 10 },
                    new Chromosome(){ CromosomeType = ChromosomeType.R, Id = 11 },
                    new Chromosome(){ CromosomeType = ChromosomeType.B, Id = 12 },
                    new Chromosome(){ CromosomeType = ChromosomeType.B, Id = 13 },
                    new Chromosome(){ CromosomeType = ChromosomeType.B, Id = 14 },
                    new Chromosome(){ CromosomeType = ChromosomeType.D, Id = 15 },
                    new Chromosome(){ CromosomeType = ChromosomeType.D, Id = 16 },
                    new Chromosome(){ CromosomeType = ChromosomeType.R, Id = 17 },
                    new Chromosome(){ CromosomeType = ChromosomeType.R, Id = 18 },
                    new Chromosome(){ CromosomeType = ChromosomeType.B, Id = 19 },
                    new Chromosome(){ CromosomeType = ChromosomeType.B, Id = 20 },
                    new Chromosome(){ CromosomeType = ChromosomeType.B, Id = 21 },
                    new Chromosome(){ CromosomeType = ChromosomeType.D, Id = 22 },
                    new Chromosome(){ CromosomeType = ChromosomeType.D, Id = 23 },
                };

            var matchIndex = 0;
            foreach (Chromosome[] match in chromosomes.FindBySequence("R+B{3}D+"))
            {
                Console.WriteLine($"Match {++matchIndex}");
                var result = new String(match.SelectMany(x => string.Join("", $"id: {x.Id} Type: {x.CromosomeType.ToString()}\n")).ToArray());
                Console.WriteLine(result);
            }

        }
    }

输出:

    Match 1
id: 2 Type: R
id: 3 Type: R
id: 4 Type: B
id: 5 Type: B
id: 6 Type: B
id: 7 Type: D
id: 8 Type: D

Match 2
id: 10 Type: R
id: 11 Type: R
id: 12 Type: B
id: 13 Type: B
id: 14 Type: B
id: 15 Type: D
id: 16 Type: D

Match 3
id: 17 Type: R
id: 18 Type: R
id: 19 Type: B
id: 20 Type: B
id: 21 Type: B
id: 22 Type: D
id: 23 Type: D

【问题讨论】:

  • 我不太清楚。是否有一个属性包含您要匹配的值,或者您想在类型名称本身上应用正则表达式。你能用你的对象结构更新问题吗?
  • 假设我理解正确,您可以覆盖 ToString() 让它返回该特定对象是 R、B 还是 D。然后,为整个链构建一个字符串,然后执行你的Regex
  • 您的对象的示例会有所帮助。
  • 我加了一个类型的例子
  • @AlexGravely 我想到了这一点,但如果可能的话,我想避免遍历集合并构建字符串。但是,是的,这可能是最有效的方式。除非有办法让对象集合在正则表达式类中看起来像一个字符串?

标签: c# .net regex


【解决方案1】:

一种使用扩展方法的简单、干净的方式(实际上支持通过正则表达式进行搜索)。

类:

public static class ChromosomesExtensions
{
    public static IEnumerable<Chromosome> FindBySequence(this IEnumerable<Chromosome> chromosomes, string patternRegex)
    {
        var sequenceString
            = String.Join(
                String.Empty, //no separator
                (
                    from c in chromosomes
                    select c.CromosomeType.ToString()
                )
            );
        var match = Regex.Match(sequenceString, patternRegex);
        //returns empty if no match is found
        return chromosomes.ToList().GetRange(sequenceString.IndexOf(match.Value), match.Value.Length);
    }
}

用法:

var chromosomes =
    new[]
    {
        new Chromosome(){ CromosomeType = ChromosomeType.D },
        new Chromosome(){ CromosomeType = ChromosomeType.R },
        new Chromosome(){ CromosomeType = ChromosomeType.R },
        new Chromosome(){ CromosomeType = ChromosomeType.B },
        new Chromosome(){ CromosomeType = ChromosomeType.B },
        new Chromosome(){ CromosomeType = ChromosomeType.B },
        new Chromosome(){ CromosomeType = ChromosomeType.D },
        new Chromosome(){ CromosomeType = ChromosomeType.D },
        new Chromosome(){ CromosomeType = ChromosomeType.B },
    };

var queryResult = chromosomes.FindBySequence("R+B{3}D+");

【讨论】:

  • 这将返回具有 Prop = R|B|D 的单个对象。但我想要符合模式的对象序列。
  • 啊,我明白了。我只是略过你的问题。我会调整答案。
  • 我用一个例子强化了这个问题
  • 这看起来不错!!我明天会好好检查一下,但看起来像我所追求的!!!干杯!!
  • 感谢您的回答。它工作得很好。我稍微调整了您的版本以使其更快并支持多个匹配。我在问题中发布了最终版本 - 如果您有任何想法,请告诉我。
【解决方案2】:

Colin's answer 似乎让你接近你想去的地方。我有两个想法要补充:

  1. 您真的需要引入“RegEx”来完成任务吗?您正在为量词使用 RegEx 库的一个子集,但这是以向复杂工具添加依赖项为代价的。如果您只是制作自己的简单(尽管不太灵活)语法,您可能会拥有更便携的应用程序。

  2. 我会考虑避免使用ToString,并简单地给你的对象一个常量字符串属性,你可以用它来反弹正则表达式。如果您正在处理“大量”数据,似乎在任何地方调用 ToString() 都会给您带来相当多的开销。

【讨论】:

  • 有效积分。我的代码肯定是为了简单而不是性能而优化的,但我应该注意到 FindBySequence() 花了大约 0.6 秒来爬过我本地开发盒上的一百万条染色体(如果你从 char 开始而不是 enum,则需要 0.2 秒)。我不确定 OP 有什么性能或规模要求,但我的经验法则是,如果现实世界的影响可以忽略不计,则选择简单而不是性能。另外,我不确定 OP 是否说 Regex 是一项要求,我只是利用它使查询易于构建/理解。
【解决方案3】:

也许为时已晚,但我必须回答。我实现了 ORegex 引擎(不是为了你的问题,但非常接近你的问题)。 它的使用与您使用 Regex 非常相似,并且将完美地解决您在任意集合上的模式匹配问题。它比上面的方法快得多,你甚至可以传递一些非常复杂的条件函数(例如,也检查一些对象属性)。它完全免费,可通过 Nuget 获得。

素数序列示例: How to search patterns in arbitrary sequences?

包含更多示例的项目页面:https://github.com/eocron/ORegex

【讨论】:

    猜你喜欢
    • 2017-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-14
    相关资源
    最近更新 更多