我有一些用户输入的字符串,可能如下所示:
这意味着:
我需要将这些字符串解析为实际范围。那就是我需要创建一个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);
}
}
它有效,但我不认为它特别易读或易于维护。例如,将'+'和'++'改为','和' - '并不是那么简单。
答案 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”,根本没有处理。
要解决这个问题,除非你的代码已经出现并且不会改变行为,否则我建议你将语法改为:
查看以下两个字符串之间的区别:
第二个字符串更容易解析,更容易阅读。
它也会消除所有歧义:
没有办法解析错误的第二种语法。
无论如何,这是我的代码。基本上它通过为四种类型的模式添加四个正则表达式模式来工作:
对于“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));
}
}