.NET RegEx - 前M行的前N个字符

时间:2010-12-29 09:01:39

标签: .net regex chars

对于以下4种基本情况,我想要4个通用的RegEx表达式:

  1. 从文件开头的D行开始,从行开始到C行之后的B字符开始之后的字符
  2. A行开始之后的字符开始从字符串开始到字符串之前的C行开始之前的字符
  3. 从文件开头的D行开始之后的C行开始,从B行开始之前的字符开始直至A字符
  4. 最多A个字符开始之前的字符开始从行结束开始直到C行开始从文件结尾开始的D行之前
  5. 这些将允许在文件中的任何位置选择任意文本块。

    到目前为止,我已经设法提出了仅适用于行和字符的案例:

    • (?<=(?m:^[^\r]{N}))[^\r]{1,M} = UP 在一开始之后,每一行都有 N个字符
    • [^\r]{1,M}(?=(?m:.{N}\r$)) =最多N个字符之前的每个字符的字符

    以上两个表达式用于字符,它们返回多个匹配(每行一个)。

    • (?<=(\A([^\r]*\r\n){N}))(?m:\n*[^\r]*\r$){1,M} =在第一行N行后最多M行
    • (((?=\r?)\n[^\r]*\r)|((?=\r?)\n[^\r]+\r?)){1,M}(?=((\n[^\r]*\r)|(\n[^\r]+\r?)){N}\Z) =从最后N行到最后M行

    这两个表达式是行的等价物,但它们总是只返回一个匹配。

    任务是组合这些表达式以允许方案1-4。有人可以帮忙吗?

    请注意,问题标题中的情况只是场景#1的子类,其中B = 0且D = 0。

    示例1:第3-5行的字符3-6。共有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:最后一行之前的2行的最后4个字符。共有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 个答案:

答案 0 :(得分:2)

这是基本案例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从倒数第五行(C + D = 5)的第三个字符(B = 2)开始,等等。)

答案 1 :(得分:1)

修改:根据您的评论,听起来这确实是您无法控制的。我发布这个答案的原因是我经常感觉到,特别是在正则表达式方面,开发人员很容易陷入技术挑战并忽视实际目标:解决问题即可。我知道我也是这样。我认为这只是技术上和创造性思维的一个不幸后果。

因此,如果可能的话,我想重新关注您手头的问题,并强调,在存放丰富的工具集的情况下,正则表达式不适合这项工作。如果它是您无法控制的唯一工具,那么,当然,您别无选择。

我认为你可能有理由要求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的灵活性,以允许更复杂的表达式构建在这些[。 。 。 ]

也许我只是在密集;是什么阻止你开始使用非正则表达式,然后使用正则表达式获得更复杂的“复杂”行为?例如,如果您需要在ScanText上面的返回行进行其他处理,那么您当然可以使用Regex进行处理。但是从一开始就坚持使用Regex似乎......我不知道,只是不必要。

  

不幸的是,由于项目的性质,它必须在RegEx [。 。 。 ]

如果确实如此,那就很好了。但是如果你的理由只是上述摘录的原因,那么我不同意这个问题的特定方面(扫描某些文本行中的某些字符)需要使用正则表达式解决,即使Regex <对于此问题范围内未涉及的其他方面,将需要

另一方面,如果你出于某些任意原因被迫使用正则表达式 - 比如说有人选择写一些要求/规范,可能没有多加考虑,那么正则表达式将被用于这项任务 - 我个人建议反对它。向有能力改变此要求的人解释不需要正则表达式,并且可以在不使用正则表达式或使用“普通”代码和正则表达式的组合的情况下轻松解决问题。

我能想到的唯一另一种可能性(虽然这可能是我自己缺乏想象力的结果)可以解释你需要使用Regex 来解决你所描述的问题在您的问题中,您只能使用专门接受正则表达式作为用户输入的特定工具。但是您的问题标记为.net,因此我必须假设某些度,您可以编写自己的代码来解决此问题。如果是这样的话,我会再说一遍:我认为你不需要Regex;)

答案 2 :(得分:1)

对于初学者来说,这是“基本案例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

答案 3 :(得分:1)

这是基本案例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 :(得分:1)

最后一个基本案例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),从倒数第五行(C + D = 5)的第8个到最后一个字符(B = 8)开始,等等。)

答案 5 :(得分:0)

你为什么不这样做:

//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循环的开始和结束以及分割,以获得你所需要的。

答案 6 :(得分:0)

请原谅我有两点:

  • 我提出的解决方案并非完全基于Regex。我知道,我读到你需要纯正的Regex解决方案。但是我进入了这个有趣的问题,我很快就得出结论,对这个问题使用正则表达式会让它过于复杂。我没有能够回答纯正的Regex解决方案。我发现了以下几个,我给他们看了;也许,他们可以给你一些想法。

  • 我不知道C#或.NET,只知道Python。由于正则表达式在所有语言中都几乎相同,我以为我只会用正则表达式来回答,这就是我开始搜索问题的原因。现在,我用Python显示我的解决方案,因为我认为无论如何它都很容易理解。

我认为通过一个独特的正则表达式来捕捉文本中你想要的所有字母是非常困难的,因为在几行中找到几个字母似乎是在匹配中找到嵌套匹配的问题(也许我是在正则表达式方面不够熟练。)

所以我认为最好先搜索所有行中所有字母的出现并将它们放在一个列表中,然后通过在列表中切片来选择发出的字母。

对于一行中的字母搜索,正则表达式对我来说似乎没问题。那么函数selectRE()的解决方案。

Afterwarrds,我意识到选择一行中的字母与在方便索引处切片一行相同,这与切片列表相同。因此函数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 ']

现在我将研究Tim Pietzcker的棘手解决方案