搜索时间时糟糕的正则表达式(xx:xx:xx)

时间:2014-04-28 19:32:13

标签: c# regex performance match

我必须处理一个大文件(几MB)并从中删除标记有时间的注释。一个例子:

blablabla  12:10:40 I want to remove this
blablabla some more
even more bla

过滤后,我希望它看起来像这样:

blablabla
blablabla some more
even more bla

最好的方法应该是放宽正则表达式:

Dataout = Regex.Replace(Datain, "[012][0123456789]:[012345][0123456789]:[012345][0123456789].*", string.Empty, RegexOptions.Compiled);

现在这完全适用于我的目的,但它有点慢..我假设这是因为前两个字符[012]和[0123456789]匹配了很多数据(它是一个包含十六进制的ASCII文件数据,所以像“0045ab0123”等。)。所以Regex经常匹配前两个角色。

当我将正则表达式更改为

Dataout = Regex.Replace(Datain, ":[012345][0123456789]:[012345][0123456789].*", string.Empty, RegexOptions.Compiled);

这是一个巨大的加速,可能是因为文件中没有多少':'。好!但是我仍然需要在第一个':'之前检查两个字符,然后删除该行的其余部分。

所以我的问题归结为:

  • 我怎样才能让Regex 首先搜索“:”的频繁出现次数,并且只有在找到匹配项后,检查之前的两个字符?

或许还有更好的方法?

3 个答案:

答案 0 :(得分:2)

您可以在问题中使用两个正则表达式。首先匹配前导冒号表达式以快速查找或排除可能的行。如果成功,则使用完整替换表达式。

MatchCollection mc = Regex.Matches(Datain, ":[012345][0123456789]:[012345][0123456789].*"));

if ( mc != null && mc.Length > 0 )
{
    Dataout = Regex.Replace(Datain, "[012][0123456789]:[012345][0123456789]:[012345][0123456789].*", string.Empty, RegexOptions.Compiled);
}
else
{
    Dataout = Datain;
}

变体可能是

Regex finder = new Regex(":[012345][0123456789]:[012345][0123456789].*");
Regex changer = new regex("[012][0123456789]:[012345][0123456789]:[012345][0123456789].*");

if ( finder.Match(Datain).Success)
{
    Dataout = changer.Replace(Datain, string.Empty);
}
else
{
    Dataout = Datain;
}

另一种变体是使用上述finder。如果找到该字符串,则只需检查前两个字符是否为数字。

Regex finder = new Regex(":[012345][0123456789]:[012345][0123456789].*");

Match m = finder.Match(Datain);
if ( m.Success && m.Index > 1)
{
    if ( char.IsDigit(DataIn[m.index-1]) && char.IsDigit(DataIn[m.index-2])
    {
        Dataout = m.Index-2 == 0 ? string.Empty : DataIn.Substring(0, m.Index-2);
    }
    else
    {
        Dataout = Datain;
    }
}
else
{
    Dataout = Datain;
}

在第二和第三个想法中,finderchanger应该在读取任何行之前声明并给定值。无需在行读取循环内执行new Regex(...)

答案 1 :(得分:0)

您可以使用DateTime.TryParseExact来检查某个单词是否是时间,然后再记录所有单词。这是一个LINQ查询,用于清除路径中的所有行,也许它更有效:

string format = "HH:mm:ss";
DateTime time;
var cleanedLines = File.ReadLines(path)
    .Select(l => string.Join(" ", l.Split().TakeWhile(w => w.Length != format.Length
       ||  !DateTime.TryParseExact(w, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out time))));

如果性能非常关键,您还可以创建针对此任务优化的专用方法。这是一种应该更有效的方法:

public static string SubstringBeforeTime(string input, string timeFormat = "HH:mm:ss")
{
    if (string.IsNullOrWhiteSpace(input))
        return input;
    DateTime time;

    if (input.Length == timeFormat.Length && DateTime.TryParseExact(input, timeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out time))
    {
        return ""; // full text is time
    }
    char[] wordSeparator = {' ', '\t'};
    int lastIndex = 0;
    int spaceIndex = input.IndexOfAny(wordSeparator);
    if(spaceIndex == -1)
        return input;
    char[] chars = input.ToCharArray();
    while(spaceIndex >= 0)
    {
        int nonSpaceIndex = Array.FindIndex<char>(chars, spaceIndex + 1, x => !wordSeparator.Contains(x));
        if(nonSpaceIndex == -1)
            return input;
        string nextWord = input.Substring(lastIndex, spaceIndex - lastIndex);
        if( nextWord.Length == timeFormat.Length 
         && DateTime.TryParseExact(nextWord, timeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out time))
        {
            return input.Substring(0, lastIndex);
        }
        lastIndex = nonSpaceIndex;
        spaceIndex = input.IndexOfAny(wordSeparator, nonSpaceIndex + 1);
    }
    return input;
}

样本数据和测试:

string[] lines = { "blablabla  12:10:40 I want to remove this", "blablabla some more", "even more bla  ", "14:22:11" };
foreach(string line in lines)
{
    string newLine = SubstringBeforeTime(line, "HH:mm:ss");
    Console.WriteLine(string.IsNullOrEmpty(newLine) ? "<empty>" : newLine);
}

输出:

blablabla  
blablabla some more
even more bla  
<empty>

答案 2 :(得分:0)

最后我去了这个:

        bool MeerCCOl = true;
        int startpositie = 0;
        int CCOLfound = 0; // aantal keer dat terminal output is gevonden

        while(MeerCCOl)
        {
            Regex rgx = new Regex(":[0-5][0-9]:[0-5][0-9]", RegexOptions.Multiline | RegexOptions.Compiled);
            Match GevondenColon = rgx.Match(VlogDataGefilterd,startpositie);

            MeerCCOl = GevondenColon.Success; // CCOL terminal data gevonden, er is misschien nog meer..

            if (MeerCCOl && GevondenColon.Index >= 2)
            {
                CCOLfound++;
                int GevondenUur = 10 * (VlogDataGefilterd[GevondenColon.Index - 2] - '0') +
                                        VlogDataGefilterd[GevondenColon.Index - 1] - '0';
                if (VlogDataGefilterd[GevondenColon.Index - 2] >= '0' && VlogDataGefilterd[GevondenColon.Index - 2] <= '2' &&
                    VlogDataGefilterd[GevondenColon.Index - 1] >= '0' && VlogDataGefilterd[GevondenColon.Index - 1] <= '9' &&
                    GevondenUur>=0 && GevondenUur<=23)
                {
                    Regex rgx2 = new Regex("[012][0-9]:[0-5][0-9]:[0-5][0-9].*", RegexOptions.Multiline);
                    VlogDataGefilterd = rgx2.Replace(VlogDataGefilterd, string.Empty, 1, (GevondenColon.Index - 2));
                    startpositie = GevondenColon.Index - 2; // start volgende match vanaf de plek waar we de 
                }
            }
        }

首先搜索匹配到:xx:xx,然后检查之前的2个字符。如果它被识别为一个时间它将删除整个事物。奖金是通过单独检查小时,我可以确保小时数读取00-23,而不是00-29。比赛次数也是这样计算的。

原始的简单正则表达式大约需要550毫秒。对于相同的数据文件,此代码(虽然更麻烦)大约需要12毫秒。这是一个惊人的40倍加速: - )

全部谢谢!