如何拆分此字符串

时间:2009-11-16 12:12:58

标签: c# string parsing range

我有一些用户输入的字符串,可能如下所示:

  1. ++ 7
    • 7 ++
    • 1 ++ 7
    • 1 + 7
    • 1 ++ 7 + 10 ++ 15 + 20 + 30 ++
  2. 这意味着:

    1. 任何事情,包括7
      • 7岁以上的任何事情
      • 1和7以及中间的任何内容
      • 仅限1和7
      • 1至7,10至15,20和30及以上
    2. 我需要将这些字符串解析为实际范围。那就是我需要创建一个Range类型的对象列表,它有一个开头和一个结尾。对于单个项目,我只是将开始和结束设置为相同,对于那些高于或低于的项目,我将start或end设置为null。例如,对于第一个,我将获得一个范围,其开始设置为null并且结束设置为7.

      我目前有一种使用正则表达式进行拆分和解析的混乱方法,我想简化它。我的问题是我需要首先拆分+,然后拆分++。但是,如果我首先拆分+,那么++实例就会被破坏,最终我会陷入混乱。

      查看这些字符串应该很容易解析它们,我只是无法想出一个聪明的方法来实现它。它必须是一种更简单(更清晰,更易读)的方式。可能涉及一些我以前从未听说过的简单概念:P


      正则表达式如下所示:

      private readonly Regex Pattern = new Regex(@"  ( [+]{2,} )?
                ([^+]+)
                (?:
                  (?: [+]{2,} [^+]* )*
                  [+]{2,} ([^+]+)
                )?
              ( [+]{2,} )?   ", RegexOptions.IgnorePatternWhitespace);
      

      然后像这样使用:

      public IEnumerable<Range<T>> Parse(string subject, TryParseDelegate<string, T> itemParser)
      {
          if (string.IsNullOrEmpty(subject))
              yield break;
      
      
          for (var item = RangeStringConstants.Items.Match(subject); item.Success; item = item.NextMatch())
          {
              var startIsOpen = item.Groups[1].Success;
              var endIsOpen = item.Groups[4].Success;
              var startItem = item.Groups[2].Value;
              var endItem = item.Groups[3].Value;
      
              if (endItem == string.Empty)
                  endItem = startItem;
      
              T start, end;
      
              if (!itemParser(startItem, out start) || !itemParser(endItem, out end))
                  continue;
      
              yield return Range.Create(startIsOpen ? default(T) : start,
                                        endIsOpen ? default(T) : end);
          }
      }
      

      它有效,但我不认为它特别易读或易于维护。例如,将'+'和'++'改为','和' - '并不是那么简单。

2 个答案:

答案 0 :(得分:4)

  

我的问题是我需要首先拆分+,然后拆分++。但是,如果我首先拆分+,那么++实例就会被破坏,最终我会陷入混乱。

你可以先拆分这个正则表达式:

(?<!\+)\+(?!\+)

这样,只有'单个'+被分开,让你解析++。请注意,我假设不能连续三个+

上面的正则表达式说:“只有在前面或后面没有'+'时才会分开'+'。”

修改

在阅读完毕后可以有两个以上的+之后,我建议编写一个小语法,让一个解析器生成器为你的小语言创建一个词法分析器+解析器。 ANTLR也可以生成C#源代码。

编辑2:

但在实现任何解决方案(解析器或正则表达式)之前,您首先必须定义以及不是有效输入。如果你要让两个以上的连续+有效,即。 1+++++5,即[1+++++5],我会写一点语法。请参阅本教程,了解其工作原理:http://www.antlr.org/wiki/display/ANTLR3/Quick+Starter+on+Parser+Grammars+-+No+Past+Experience+Required

如果您要拒绝超过2个连续+的输入,您可以使用Lasse或我的(第一个)正则表达式建议。

答案 1 :(得分:4)

这是一些使用正则表达式的代码。

请注意Bart在您的问题评论中提出的问题,即。 “你如何处理1 +++ 5”,根本没有处理。

要解决这个问题,除非你的代码已经出现并且不会改变行为,否则我建议你将语法改为:

  • 使用..来表示范围
  • 允许+和 - 表示数字,正数和负数
  • 使用逗号和/或分号分隔不同的数字或范围
  • 允许空白

查看以下两个字符串之间的区别:

  • 1 ++ 7 + 10 ++ 15 + 20 + 30 ++
  • 1..7,10..15,20,30 ..

第二个字符串更容易解析,更容易阅读。

它也会消除所有歧义:

  • 1 +++ 5 = 1 ++ + 5 = 1 ..,5
  • 1 +++ 5 = 1 + ++ 5 = 1,.5

没有办法解析错误的第二种语法。


无论如何,这是我的代码。基本上它通过为四种类型的模式添加四个正则表达式模式来工作:

  • NUM
  • NUM ++
  • ++ NUM
  • NUM ++ NUM

对于“num”,它将处理带有前导减号和一个或多个数字的负数。出于显而易见的原因,它不会将加号作为数字的一部分来处理。

我将“up up”解释为“up to Int32.MaxValue”,并将其解释为“Int32.MinValue”。

public class Range
{
    public readonly Int32 From;
    public readonly Int32 To;

    public Range(Int32 from, Int32 to)
    {
        From = from;
        To = to;
    }

    public override string ToString()
    {
        if (From == To)
            return From.ToString();
        else if (From == Int32.MinValue)
            return String.Format("++{0}", To);
        else if (To == Int32.MaxValue)
            return String.Format("{0}++", From);
        else
            return String.Format("{0}++{1}", From, To);
    }
}

public static class RangeSplitter
{
    public static Range[] Split(String s)
    {
        if (s == null)
            throw new ArgumentNullException("s");

        String[] parts = new Regex(@"(?<!\+)\+(?!\+)").Split(s);
        List<Range> result = new List<Range>();

        var patterns = new Dictionary<Regex, Action<Int32[]>>();

        patterns.Add(new Regex(@"^(-?\d+)$"),
            values => result.Add(new Range(values[0], values[0])));
        patterns.Add(new Regex(@"^(-?\d+)\+\+$"),
            values => result.Add(new Range(values[0], Int32.MaxValue)));
        patterns.Add(new Regex(@"^\+\+(-?\d+)$"),
            values => result.Add(new Range(Int32.MinValue, values[0])));
        patterns.Add(new Regex(@"^(-?\d+)\+\+(-?\d+)$"),
            values => result.Add(new Range(values[0], values[1])));

        foreach (String part in parts)
        {
            foreach (var kvp in patterns)
            {
                Match ma = kvp.Key.Match(part);
                if (ma.Success)
                {
                    Int32[] values = ma.Groups
                        .OfType<Group>()
                        .Skip(1) // group 0 is the entire match
                        .Select(g => Int32.Parse(g.Value))
                        .ToArray();
                    kvp.Value(values);
                }
            }
        }

        return result.ToArray();
    }
}

单元测试:

[TestFixture]
public class RangeSplitterTests
{
    [Test]
    public void Split_NullString_ThrowsArgumentNullException()
    {
        Assert.Throws<ArgumentNullException>(() =>
        {
            var result = RangeSplitter.Split(null);
        });
    }

    [Test]
    public void Split_EmptyString_ReturnsEmptyArray()
    {
        Range[] result = RangeSplitter.Split(String.Empty);
        Assert.That(result.Length, Is.EqualTo(0));
    }

    [TestCase(01, "++7", Int32.MinValue, 7)]
    [TestCase(02, "7", 7, 7)]
    [TestCase(03, "7++", 7, Int32.MaxValue)]
    [TestCase(04, "1++7", 1, 7)]
    public void Split_SinglePatterns_ProducesExpectedRangeBounds(
        Int32 testIndex, String input, Int32 expectedLower,
        Int32 expectedUpper)
    {
        Range[] result = RangeSplitter.Split(input);
        Assert.That(result.Length, Is.EqualTo(1));
        Assert.That(result[0].From, Is.EqualTo(expectedLower));
        Assert.That(result[0].To, Is.EqualTo(expectedUpper));
    }

    [TestCase(01, "++7")]
    [TestCase(02, "7++")]
    [TestCase(03, "1++7")]
    [TestCase(04, "1+7")]
    [TestCase(05, "1++7+10++15+20+30++")]
    public void Split_ExamplesFromQuestion_ProducesCorrectResults(
        Int32 testIndex, String input)
    {
        Range[] ranges = RangeSplitter.Split(input);
        String rangesAsString = String.Join("+",
            ranges.Select(r => r.ToString()).ToArray());

        Assert.That(rangesAsString, Is.EqualTo(input));
    }

    [TestCase(01, 10, 10, "10")]
    [TestCase(02, 1, 10, "1++10")]
    [TestCase(03, Int32.MinValue, 10, "++10")]
    [TestCase(04, 10, Int32.MaxValue, "10++")]
    public void RangeToString_Patterns_ProducesCorrectResults(
        Int32 testIndex, Int32 lower, Int32 upper, String expected)
    {
        Range range = new Range(lower, upper);
        Assert.That(range.ToString(), Is.EqualTo(expected));
    }
}