.NET RegEx - первые N символов первых M строк - PullRequest
1 голос
/ 29 декабря 2010

Я хочу 4 общих выражения RegEx для следующих 4 основных случаев:

  1. До символов A, начинающихся после символов B от начала строки до строк C, начинающихся после строк D от начала файла
  2. До символов A, начинающихся после символов B от начала строки до строк C, встречающихся до строк D от конца файла
  3. До символов A, начинающихся до символов B от конца строки до строк C, начинающихся после строк D от начала файла
  4. До символов A, начинающихся до символов B от конца строки до строк C, начинающихся до строк D от конца файла

Это позволит выбирать произвольные текстовые блоки в любом месте файла.

До сих пор мне удавалось придумать случаи, которые работают только для строк и символов отдельно:

  • (?<=(?m:^[^\r]{N}))[^\r]{1,M} = ВВЕРХ М ЧАРСЫ КАЖДОЙ ЛИНИИ, ПОСЛЕ ПЕРВЫХ N символов
  • [^\r]{1,M}(?=(?m:.{N}\r$)) = До M символов каждой строки, до последних N символов

Вышеприведенные 2 выражения предназначены для символов и возвращают МНОГИЕ совпадения (по одному на каждую строку).

  • (?<=(\A([^\r]*\r\n){N}))(?m:\n*[^\r]*\r$){1,M} = до M строк ПОСЛЕ ПЕРВЫХ N строк
  • (((?=\r?)\n[^\r]*\r)|((?=\r?)\n[^\r]+\r?)){1,M}(?=((\n[^\r]*\r)|(\n[^\r]+\r?)){N}\Z) = до M строк ДО ПОСЛЕДНИХ N строк от конца

Эти 2 выражения являются эквивалентами для строк, но они всегда возвращают только ОДНО совпадение.

Задача состоит в том, чтобы объединить эти выражения, чтобы учесть сценарии 1-4. Кто-нибудь может помочь?

Обратите внимание, что регистр в названии вопроса - это просто подкласс сценария # 1, где оба B = 0 и D = 0.

ПРИМЕР 1: Символы 3-6 строк 3-5. Всего 3 матча.

ИСТОЧНИК:

line1 blah 1
line2 blah 2
line3 blah 3
line4 blah 4
line5 blah 5
line6 blah 6

РЕЗУЛЬТАТ:

<match>ne3 </match>
<match>ne4 </match>
<match>ne5 </match>

ПРИМЕР 2: последние 4 символа из 2 строк перед 1 последней строкой. Всего 2 матча.

ИСТОЧНИК:

line1 blah 1
line2 blah 2
line3 blah 3
line4 blah 4
line5 blah 5
line6 blah 6

РЕЗУЛЬТАТ:

<match>ah 4</match>
<match>ah 5</match>

Ответы [ 7 ]

2 голосов
/ 30 декабря 2010

Вот одно регулярное выражение для основного случая 2:

Regex regexObj = new Regex(
    @"(?<=              # Assert that the following can be matched before the current position
     ^                # Start of line
     .{2}             # 2 characters (B = 2)
    )                 # End of lookbehind assertion
    .{1,3}            # Match 1-3 characters (A = 3)
    (?=               # Assert that the following can be matched after the current position
     .*$              # rest of the current line
     (?:\r\n.*){2,4}  # 2 to 4 entire lines (D = 2, C = 4+1-2)
     \z               # end of the string
    )", 
    RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace);

В тексте

line1 blah 1
line2 blah 2
line3 blah 3
line4 blah 4
line5 blah 5
line6 blah 6

будет соответствовать

ne2
ne3
ne4

(ne2 начинается с третьего символа (B = 2) в пятой строке (C + D = 5) и т.

1 голос
/ 30 декабря 2010

И, наконец, одно решение для основного случая 4:

Regex regexObj = new Regex(
    @"(?=             # Assert that the following can be matched after the current position
     .{8}             # 8 characters (B = 8)
     (?:\r\n.*){2,4}  # 2 to 4 entire lines (D = 2, C = 4+1-2)
     \z               # end of the string
    )                 # End of lookahead assertion
    .{1,3}            # Match three characters (A = 3)", 
    RegexOptions.IgnorePatternWhitespace);

В тексте

line1 blah 1
line2 blah 2
line3 blah 3
line4 blah 4
line5 blah 5
line6 blah 6

это будет соответствовать

2 b
3 b
4 b

(2 b, потому чтоэто три символа (A = 3), начиная с 8-го до последнего символа (B = 8) в пятой-последней строке (C + D = 5) и т. д.)

1 голос
/ 30 декабря 2010

Вот один для основного случая 3:

Regex regexObj = new Regex(
    @"(?<=            # Assert that the following can be matched before the current position
     \A               # Start of string
     (?:.*\r\n){2,4}  # 2 to 4 entire lines (D = 2, C = 4+1-2)
     .*               # any number of characters
    )                 # End of lookbehind assertion
    (?=               # Assert that the following can be matched after the current position
     .{8}             # 8 characters (B = 8)
     $                # end of line
    )                 # End of lookahead assertion
    .{1,3}            # Match 1-3 characters (A = 3)", 
    RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace);

Так в тексте

line1 blah 1
line2 blah 2
line3 blah 3
line4 blah 4
line5 blah 5
line6 blah 6

будет соответствовать

3 b
4 b
5 b

(3 b, поскольку это 3 символа (A = 3), начиная с 8-го до последнего символа (B = 8), начиная с третьей строки (D = 2) и т. Д.)

1 голос
/ 29 декабря 2010

Для начала вот ответ для «Базового варианта 1»:

Regex regexObj = new Regex(
    @"(?<=            # Assert that the following can be matched before the current position
     \A               # Start of string
     (?:.*\r\n){2,4}  # 2 to 4 entire lines (D = 2, C = 4+1-2)
     .{2}             # 2 characters (B = 2)
    )                 # End of lookbehind assertion
    .{1,3}            # Match 1-3 characters (A = 3)", 
    RegexOptions.IgnorePatternWhitespace);

Теперь вы можете перебирать совпадения, используя

Match matchResults = regexObj.Match(subjectString);
while (matchResults.Success) {
    // matched text: matchResults.Value
    // match start: matchResults.Index
    // match length: matchResults.Length
    matchResults = matchResults.NextMatch();
}

Итак, в тексте

line1 blah 1
line2 blah 2
line3 blah 3
line4 blah 4
line5 blah 5
line6 blah 6

будет соответствовать

ne3
ne4
ne5
1 голос
/ 29 декабря 2010

Редактировать : Судя по вашим комментариям, это действительно что-то вне вашего контроля.Причина, по которой я опубликовал этот ответ, заключается в том, что я чувствую, что часто, особенно когда речь идет о регулярных выражениях, разработчики легко попадают в техническую задачу и теряют из виду реальную цель: решение проблемы.Я знаю, что и я тоже.Я думаю, что это всего лишь печальное следствие того, что мы обладаем как техническими, так и творческими способностями.

Поэтому я хотел бы, если возможно, перефокусировать вас на имеющейся проблеме и подчеркнуть это в присутствии хорошо укомплектованного набора инструментов., Regex не подходит для этой работы.Если это инструмент only в вашем распоряжении по независящим от вас причинам, то, конечно, у вас нет выбора.

Я полагал, что у вас, вероятно, есть реальные причины требовать решения Regex;но так как эти причины не были полностью объяснены, я чувствовал, что все еще есть шанс, что вы просто упрямы;)


Вы говорите, что это нужно сделать в Regex, но я не уверен!

Прежде всего я ограничен .NET 2.0 [.,,]

Нет проблем.Кто сказал, что нужно LINQ для такой проблемы?LINQ просто делает вещи проще ;это не делает невозможными невозможные вещи.

Вот один из способов, например, реализовать первый случай из своего вопроса (и было бы довольно просто преобразовать его в нечто более гибкое, позволяющее охватывать случаи2–3):

public IEnumerable<string> ScanText(TextReader reader,
                                    int start,
                                    int count,
                                    int lineStart,
                                    int lineCount)
{
    int i = 0;
    while (i < lineStart && reader.Peek() != -1)
    {
        reader.ReadLine();
        ++i;
    }

    i = 0;
    while (i < lineCount && reader.Peek() != -1)
    {
        string line = reader.ReadLine();

        if (line.Length < start)
        {
            yield return ""; // or null? or continue?
        }
        else
        {
            int length = Math.Min(count, line.Length - start);
            yield return line.Substring(start, length);
        }

        ++i;
    }
}

Итак, есть решение для общей проблемы, совместимое с .NET 2.0, без использования регулярных выражений (или LINQ).

Во-вторых, янужна гибкость RegEx, чтобы учесть более сложные выражения, которые будут основываться на этих [.,,]

Может быть, я просто плотный;Что мешает вам начать с чего-то не-Regex, а затем использовать Regex для более «изощренного» поведения?Например, если вам нужно выполнить дополнительную обработку строк , возвращаемых на ScanText выше, вы можете сделать это с помощью Regex.Но настаивать на использовании Regex с самого начала кажется ... Я не знаю, просто ненужно.

К сожалению, из-за характера проекта это должно быть сделано в RegEx [.,,]

Если это действительно так, то очень хорошо.Но если ваши причины основаны только на приведенных выше выдержках, то я не согласен с тем, что этот конкретный аспект проблемы (сканирование определенных символов из определенных строк текста) необходимо устранить с помощью Regex, даже если Regex потребуется для других аспектов проблемы, не охваченных в этом вопросе.

Если, с другой стороны, вы вынуждены использовать Regex длякакая-то произвольная причина - скажем, кто-то решил написать в каком-то требовании / спецификации, возможно, не задумываясь об этом, что для этой задачи будут использоваться регулярные выражения - ну, я бы лично посоветовал бороться с этим.Объясните тому, кто в состоянии изменить это требование, что Regex не является необходимым и что проблема может быть легко решена без использования Regex ... или с использованием комбинации "нормального" кода и Regex.

Единственноея могу подумать о другой возможности (хотя это может быть результатом моей собственной нехватки воображения), которая объяснила бы вам необходимость для использования Regex для проблемы, которую вы описали в своем вопросе является то, что вы ограничены в использовании определенного инструмента, который принимает регулярные выражения исключительно как пользовательский ввод.Но ваш вопрос помечен помечен .net, и поэтому я должен предположить, что есть некоторая степень, в которой вы можете написать свой собственный код, который будет использоваться для решения этой проблемы.И если это так, то я скажу это снова: я не думаю, что вам нужно Regex;)

0 голосов
/ 30 декабря 2010

Извините за два момента:

  • Я предлагаю решения, которые не полностью основаны на Regex.Я знаю, я читал, что вам нужны чистые решения Regex.Но я вошел в интересную проблему и быстро пришел к выводу, что использование регулярных выражений для этой проблемы усложняет ее.Я не смог ответить чистыми решениями Regex.Я нашел следующие, и я показываю их;возможно, они могли бы дать вам идеи.

  • Я не знаю C # или .NET, только Python.Поскольку регулярные выражения почти одинаковы во всех языках, я подумал, что собираюсь ответить только регулярными выражениями, поэтому я начал искать проблему.Теперь я все равно показываю свои решения на Python, потому что я думаю, что в любом случае это легко понять.текст с помощью уникального регулярного выражения, потому что поиск нескольких букв в нескольких строках кажется мне проблемой поиска вложенных совпадений в совпадениях (может быть, я недостаточно квалифицирован в регулярных выражениях).

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

    Для поиска букв в строке мне показалось, что регулярное выражение в порядкепервый.Так что решение с функцией selectRE ().

    После войны я понял, что выделение букв в строке - это то же самое, что разрезание строки в удобных индексах, и это то же самое, что и разрезание списка.Отсюда функция select ().

    Я даю два решения вместе, поэтому можно проверить равенство двух результатов двух функций.

    import re
    
    def selectRE(a,which_chars,b,x,which_lines,y,ch):
        ch = ch[:-1] if ch[1]=='\n' else ch # to obtain an exact number of lines
        NL = ch.count('\n') +1 # number of lines
    
        def pat(a,which_chars,b):
            if which_chars=='to':
                print repr(('.{'+str(a-1)+'}' if a else '') + '(.{'+str(b-a+1)+'}).*(?:\n|$)')
                return re.compile(('.{'+str(a-1)+'}' if a else '') + '(.{'+str(b-a+1)+'}).*(?:\n|$)')
            elif which_chars=='before':
                print repr('.*(.{'+str(a)+'})'+('.{'+str(b)+'}' if b else '')+'(?:\n|$)')
                return re.compile('.*(.{'+str(a)+'})'+('.{'+str(b)+'}' if b else '')+'(?:\n|$)')
            elif which_chars=='after':
                print repr(('.{'+str(b)+'}' if b else '')+'(.{'+str(a)+'}).*(?:\n|$)')
                return re.compile(('.{'+str(b)+'}' if b else '')+'(.{'+str(a)+'}).*(?:\n|$)')
    
        if   which_lines=='to'    :  x   = x-1
        elif which_lines=='before':  x,y = NL-x-y,NL-y
        elif which_lines=='after' :  x,y = y,y+x
    
        return pat(a,which_chars,b).findall(ch)[x:y]
    
    
    def select(a,which_chars,b,x,which_lines,y,ch):
        ch = ch[:-1] if ch[1]=='\n' else ch # to obtain an exact number of lines
        NL = ch.count('\n') +1 # number of lines
    
        if   which_chars=='to'    :  a   = a-1
        elif which_chars=='after' :  a,b = b,a+b
    
        if   which_lines=='to'    :  x   = x-1
        elif which_lines=='before':  x,y = NL-x-y,NL-y
        elif which_lines=='after' :  x,y = y,y+x
    
        return [ line[len(line)-a-b:len(line)-b] if which_chars=='before' else line[a:b]
                 for i,line in enumerate(ch.splitlines()) if x<=i<y ]
    
    
    ch = '''line1 blah 1
    line2 blah 2
    line3 blah 3
    line4 blah 4
    line5 blah 5
    line6 blah 6
    '''
    print ch,'\n'
    
    print 'Characters 3-6 of lines 3-5. A total of 3 matches.'
    print selectRE(3,'to',6,3,'to',5,ch)
    print   select(3,'to',6,3,'to',5,ch)
    print
    print 'Characters 1-5 of lines 4-5. A total of 2 matches.'
    print selectRE(1,'to',5,4,'to',5,ch)
    print   select(1,'to',5,4,'to',5,ch)
    print
    print '7 characters before the last 3 chars of lines 2-6. A total of 5 matches.'
    print selectRE(7,'before',3,2,'to',6,ch)
    print   select(7,'before',3,2,'to',6,ch)
    print
    print '6 characters before the 2 last characters of 3 lines before the 3 last lines.'
    print selectRE(6,'before',2,3,'before',3,ch)
    print   select(6,'before',2,3,'before',3,ch)
    print 
    print '4 last characters of 2 lines before 1 last line. A total of 2 matches.'
    print selectRE(4,'before',0,2,'before',1,ch)
    print   select(4,'before',0,2,'before',1,ch)
    print
    print 'last 1 character of 4 last lines. A total of 2 matches.'
    print selectRE(1,'before',0,4,'before',0,ch)
    print   select(1,'before',0,4,'before',0,ch)
    print
    print '7 characters before the last 3 chars of 3 lines after the 2 first lines. A total of 5 matches.'
    print selectRE(7,'before',3,3,'after',2,ch)
    print   select(7,'before',3,3,'after',2,ch)
    print
    print '5 characters before the 3 last chars of the 5 first lines'
    print selectRE(5,'before',3,5,'after',0,ch)
    print   select(5,'before',3,5,'after',0,ch)
    print
    print 'Characters 3-6 of the 4 first lines'
    print selectRE(3,'to',6,4,'after',0,ch)
    print   select(3,'to',6,4,'after',0,ch)
    print
    print '9 characters after the 2 first chars of the 3 lines after the 1 first line'
    print selectRE(9,'after',2,3,'after',1,ch)
    print   select(9,'after',2,3,'after',1,ch)
    

    result

    line1 blah 1
    line2 blah 2
    line3 blah 3
    line4 blah 4
    line5 blah 5
    line6 blah 6
    
    
    Characters 3-6 of lines 3-5. A total of 3 matches.
    '.{2}(.{4}).*(?:\n|$)'
    ['ne3 ', 'ne4 ', 'ne5 ']
    ['ne3 ', 'ne4 ', 'ne5 ']
    
    Characters 1-5 of lines 4-5. A total of 2 matches.
    '.{0}(.{5}).*(?:\n|$)'
    ['line4', 'line5']
    ['line4', 'line5']
    
    7 characters before the last 3 chars of lines 2-6. A total of 5 matches.
    '.*(.{7}).{3}(?:\n|$)'
    ['ne2 bla', 'ne3 bla', 'ne4 bla', 'ne5 bla', 'ne6 bla']
    ['ne2 bla', 'ne3 bla', 'ne4 bla', 'ne5 bla', 'ne6 bla']
    
    6 characters before the 2 last characters of 3 lines before the 3 last lines.
    '.*(.{6}).{2}(?:\n|$)'
    ['2 blah', '3 blah', '4 blah']
    ['2 blah', '3 blah', '4 blah']
    
    4 last characters of 2 lines before 1 last line. A total of 2 matches.
    '.*(.{4})(?:\n|$)'
    ['ah 5', 'ah 6']
    ['ah 5', 'ah 6']
    
    last 1 character of 4 last lines. A total of 2 matches.
    '.*(.{1})(?:\n|$)'
    ['4', '5', '6']
    ['4', '5', '6']
    
    7 characters before the last 3 chars of 3 lines after the 2 first lines. A total of 5 matches.
    '.*(.{7}).{3}(?:\n|$)'
    ['ne3 bla', 'ne4 bla', 'ne5 bla']
    ['ne3 bla', 'ne4 bla', 'ne5 bla']
    
    5 characters before the 3 last chars of the 5 first lines
    '.*(.{5}).{3}(?:\n|$)'
    ['1 bla', '2 bla', '3 bla', '4 bla', '5 bla']
    ['1 bla', '2 bla', '3 bla', '4 bla', '5 bla']
    
    Characters 3-6 of the 4 first lines
    '.{2}(.{4}).*(?:\n|$)'
    ['ne1 ', 'ne2 ', 'ne3 ', 'ne4 ']
    ['ne1 ', 'ne2 ', 'ne3 ', 'ne4 ']
    
    9 characters after the 2 first chars of the 3 lines after the 1 first line
    '.{2}(.{9}).*(?:\n|$)'
    ['ne2 blah ', 'ne3 blah ', 'ne4 blah ']
    ['ne2 blah ', 'ne3 blah ', 'ne4 blah ']
    

    А теперь я буду изучать хитрые решения Тима Пицкера

0 голосов
/ 29 декабря 2010

Почему бы вам просто не сделать что-то вроде этого:

//Assuming you have it read into a string name sourceString
String[] SplitString = sourceString.Split(Environment.Newline); //You will probably need to account for any line delimeter
String[M] NewStrings;
for(i=0;i<M;i++) {
    NewStrings[i] = SplitString[i].SubString(0,N) //Or (N, SplitString[i].Length -1) depending on what you need
}

Вам не нужен RegEx, вам не нужен LINQ.

Ну, я перечитал начало вашего вопроса, и вы могли бы просто параметризовать начало и конец цикла for и Split, чтобы получить именно то, что вам нужно.

...