Как разбить эту строку - PullRequest
       14

Как разбить эту строку

2 голосов
/ 16 ноября 2009

У меня есть несколько строк, введенных пользователями, которые могут выглядеть так:

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

Это значит:

  1. Все до 7 включительно
    • Что-нибудь от 7 и выше
    • 1 и 7 и все, что между
    • 1 и 7 только
    • 1–7, 10–15, 20 и 30 и выше

Мне нужно проанализировать эти строки в реальных диапазонах. То есть мне нужно создать список объектов типа Range, которые имеют начало и конец. Для отдельных элементов я просто устанавливаю начало и конец одинаковыми, а для тех, которые выше или ниже, я устанавливаю начало или конец равными нулю. Например, для первого я получу один диапазон, для которого начальное значение равно нулю, а конечное значение равно 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 ]

4 голосов
/ 16 ноября 2009

Вот некоторый код, который использует регулярные выражения.

Обратите внимание, что вопрос, поднятый Бартом в комментариях к вашему вопросу, т.е. «Как вы справляетесь с 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» он будет обрабатывать отрицательные числа с начальным знаком минус и одной или несколькими цифрами. По понятным причинам он не обрабатывает знак плюс как часть числа.

Я интерпретировал "и выше", чтобы обозначить "до 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));
    }
}
4 голосов
/ 16 ноября 2009

Моя проблема в том, что мне нужно сначала разделить на +, а затем на ++. Но если я сначала разделю на +, то экземпляры ++ будут разрушены, и я получаю беспорядок.

Вы можете сначала разделить это регулярное выражение:

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

Таким образом, только «одиночные» + делятся, оставляя вас для разбора ++. Обратите внимание, что я предполагаю, что не может быть трех последовательных +.

Выражение выше простого говорит: «разделяйте на« + », только если нет« + »впереди или позади него».

Edit:

После прочтения, что может быть более двух последовательных +, я рекомендую написать небольшую грамматику и позволить генератору синтаксического анализатора создать анализатор лексера + для вашего небольшого языка. ANTLR также может генерировать исходный код C #.

Редактировать 2:

Но перед реализацией любого решения (синтаксического анализатора или регулярного выражения) вы должны сначала определить, что является , а что не является допустимым вводом. Если вы хотите, чтобы действовало более двух последовательных +, т.е. 1+++++5, то есть [1++, +, ++5], я бы написал небольшую грамматику. Посмотрите этот урок, как это работает: http://www.antlr.org/wiki/display/ANTLR3/Quick+Starter+on+Parser+Grammars+-+No+Past+Experience+Required

И если вы собираетесь отклонить ввод более чем 2 последовательных +, вы можете использовать либо предложение Лассе, либо мое (первое) предложение регулярного выражения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...