.NET RegEx - первые N символов первых M строк
Я хочу 4 общих выражения RegEx для следующих 4 основных случаев:
- До символов A, начинающихся после символов B от начала строки до строк C, начинающихся после строк D от начала файла
- До символов A, начинающихся после символов B от начала строки до строк C, встречающихся до строк D от конца файла
- До символов A, начинающихся до символов B от конца строки до строк C, начинающихся после строк D от начала файла
- До символов A, начинающихся до символов B от конца строки до строк C, начинающихся до строк D от конца файла
Это позволит выбрать произвольные текстовые блоки в любом месте файла.
До сих пор мне удавалось придумать случаи, которые работают только для строк и символов отдельно:
(?<=(?m:^[^\r]{N}))[^\r]{1,M}
= До 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)
= ДО М строк, ДО ПОСЛЕДНИХ 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 ответов
Для начала вот ответ для "Базового случая 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
Вот одно регулярное выражение для основного случая 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) и т. д.)
Изменить: На основе ваших комментариев, кажется, что это действительно что-то вне вашего контроля. Причина, по которой я опубликовал этот ответ, заключается в том, что я чувствую, что часто, особенно когда речь идет о регулярных выражениях, разработчики легко попадают в технические проблемы и упускают из виду реальную цель: решение проблемы. Я знаю, что и я тоже. Я думаю, что это просто неудачное следствие того, чтобы быть технически и творчески мыслящими.
Поэтому я хотел перефокусировать вас, если это возможно, на имеющуюся проблему и подчеркнуть, что при наличии хорошо укомплектованного набора инструментов Regex не является подходящим инструментом для этой работы. Если это единственный инструмент в вашем распоряжении по независящим от вас причинам, то, конечно, у вас нет выбора.
Я полагал, что у вас, вероятно, были реальные причины требовать решения 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;)
Вот один для основного случая 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) и т. д.)
И, наконец, одно решение для основного случая 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) и т. д.)
Извините за два момента:
Я предлагаю решения, которые не полностью основаны на 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)
результат
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 ']
А теперь я буду изучать хитрые решения Тима Пицкера
Почему бы вам просто не сделать что-то вроде этого:
//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, чтобы получить именно то, что вам нужно.