c# - 大型 RegEx 匹配导致程序挂起

标签 c# wpf regex parsing

我前几天试过问这个问题,诚然一开始没有很好地表述问题或发布代码,答案被关闭了。所以我在这里再次尝试,因为老实说,这很快就会让我发疯。 :)

我正在尝试实现这个 Address Parser ,它最初是一个基于控制台的 c# 程序。我已经成功地将它转换成一个独立的 WPF 程序,它只包含一个用于输入的 TextBox、一个用于激活解析的 Button 和一个 TextBlock显示结果。在写这篇文章时,我确实将输出截断为我在主程序中需要的输出,但它仍然运行良好。我在下面包含了完整的代码。

我的下一步是将它移植到我的主程序中,我通过字面上使用复制/粘贴来完成。但是,在运行此程序时,程序会在按下按钮后挂起。最终 VS 给出了一个错误,说进程已经走了太长时间,没有发出消息,并且 TaskManager 中的内存使用量从 ~70k 逐渐增加到 3,000,000。为此,我将Parsing方法分配给了一个后台worker,希望能减轻主进程的工作量。这确实解决了程序卡住问题,但后台线程只是做了同样的事情,提高了 RAM 使用率并且什么都不返回。

所以现在我有点陷入僵局。我知道问题出在 var result = parser.ParseAddress(input); 语句中的某处,因为当为每一行代码使用断点时,这是最后一个触发的代码。但基本上我无法理解为什么这会导致一个 WPF 程序出现问题而不是另一个。

如果有必要,我会非常乐意在某处发布主程序的完整源代码,但我无法想象在这里发布大约 20 个不同的类文件和项目代码是个好主意。 :)

独立 WPF 应用

namespace AddressParseWPF
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        public void Execute()
        {
            AddressParser.AddressParser parser = new AddressParser.AddressParser();
            var input = inputTextBox.Text;

            var result = parser.ParseAddress(input);

            if (result == null)
            {
                outputTextBlock.Text = "ERROR. Input could not be parsed.";
            }
            else
            {
                outputTextBlock.Text = (result.StreetLine + ", " + result.City + ", " + result.State + "  " + result.Zip);
            }
        }

        private void actionButton_Click(object sender, RoutedEventArgs e)
        {
            Execute();
        }
    }
}

嫁接Parser的主程序

public void ExecuteAddressParse()
{
    AddressParser.AddressParser parser = new AddressParser.AddressParser();
    var input = inputTextBox.Text;

    var result = parser.ParseAddress(input);

    if (result == null)
    {
        outputTextBlock.Text = "ERROR. Input could not be parsed.";
    }
    else
    {
        outputTextBlock.Text = (result.StreetLine + ", " + result.City + ", " + result.State + "  " + result.Zip);
    }
}       

private void actionButton_Click(object sender, RoutedEventArgs e)
{
    ExecuteAddressParse();
}

解析地址方法

public AddressParseResult ParseAddress(string input)
{
    if (!string.IsNullOrWhiteSpace(input))
    {
        var match = addressRegex.Match(input.ToUpperInvariant());
        if (match.Success)
        {
            var extracted = GetApplicableFields(match);
            return new AddressParseResult(Normalize(extracted));
        }
    }

    return null;
}

正则表达式匹配方法

private static void InitializeRegex()
{
    var suffixPattern = new Regex(
        string.Join(
            "|",
            new [] {
                string.Join("|", suffixes.Keys), 
                string.Join("|", suffixes.Values.Distinct())
            }),
        RegexOptions.Compiled);

    var statePattern = 
        @"\b(?:" + 
        string.Join(
            "|",
            new [] {
                string.Join("|", states.Keys.Select(x => Regex.Escape(x))),
                string.Join("|", states.Values)
            }) +
        @")\b";

    var directionalPattern =
        string.Join(
            "|",
            new [] {
                string.Join("|", directionals.Keys),
                string.Join("|", directionals.Values),
                string.Join("|", directionals.Values.Select(x => Regex.Replace(x, @"(\w)", @"$1\.")))
            });

    var zipPattern = @"\d{5}(?:-?\d{4})?";

    var numberPattern =
        @"(
            ((?<NUMBER>\d+)(?<SECONDARYNUMBER>(-[0-9])|(\-?[A-Z]))(?=\b))    # Unit-attached
            |(?<NUMBER>\d+[\-\ ]?\d+\/\d+)                                   # Fractional
            |(?<NUMBER>\d+-?\d*)                                             # Normal Number
            |(?<NUMBER>[NSWE]\ ?\d+\ ?[NSWE]\ ?\d+)                          # Wisconsin/Illinois
          )";

    var streetPattern =
        string.Format(
            CultureInfo.InvariantCulture,
            @"
                (?:
                  # special case for addresses like 100 South Street
                  (?:(?<STREET>{0})\W+
                     (?<SUFFIX>{1})\b)
                  |
                  (?:(?<PREDIRECTIONAL>{0})\W+)?
                  (?:
                    (?<STREET>[^,]*\d)
                    (?:[^\w,]*(?<POSTDIRECTIONAL>{0})\b)
                   |
                    (?<STREET>[^,]+)
                    (?:[^\w,]+(?<SUFFIX>{1})\b)
                    (?:[^\w,]+(?<POSTDIRECTIONAL>{0})\b)?
                   |
                    (?<STREET>[^,]+?)
                    (?:[^\w,]+(?<SUFFIX>{1})\b)?
                    (?:[^\w,]+(?<POSTDIRECTIONAL>{0})\b)?
                  )
                )
            ",
            directionalPattern,
            suffixPattern);

    var rangedSecondaryUnitPattern =
        @"(?<SECONDARYUNIT>" +
        string.Join("|", rangedSecondaryUnits.Keys) +
        @")(?![a-z])";
    var rangelessSecondaryUnitPattern =
        @"(?<SECONDARYUNIT>" +
        string.Join(
            "|",
            string.Join("|", rangelessSecondaryUnits.Keys)) +
        @")\b";
    var allSecondaryUnitPattern = string.Format(
        CultureInfo.InvariantCulture,
        @"
            (
                (:?
                    (?: (?:{0} \W*)
                        | (?<SECONDARYUNIT>\#)\W*
                    )
                    (?<SECONDARYNUMBER>[\w-]+)
                )
                |{1}
            ),?
        ",
         rangedSecondaryUnitPattern,
         rangelessSecondaryUnitPattern);

    var cityAndStatePattern = string.Format(
        CultureInfo.InvariantCulture,
        @"
            (?:
                (?<CITY>[^\d,]+?)\W+
                (?<STATE>{0})
            )
        ",
        statePattern);
    var placePattern = string.Format(
        CultureInfo.InvariantCulture,
        @"
            (?:{0}\W*)?
            (?:(?<ZIP>{1}))?
        ",
        cityAndStatePattern,
        zipPattern);

    var addressPattern = string.Format(
        CultureInfo.InvariantCulture,
        @"
            ^
            # Special case for APO/FPO/DPO addresses
            (
                [^\w\#]*
                (?<STREETLINE>.+?)
                (?<CITY>[AFD]PO)\W+
                (?<STATE>A[AEP])\W+
                (?<ZIP>{4})
                \W*
            )
            |
            # Special case for PO boxes
            (
                \W*
                (?<STREETLINE>(P[\.\ ]?O[\.\ ]?\ )?BOX\ [0-9]+)\W+
                {3}
                \W*
            )
            |
            (
                [^\w\#]*    # skip non-word chars except # (eg unit)
                (  {0} )\W*
                   {1}\W+
                (?:{2}\W+)?
                   {3}
                \W*         # require on non-word chars at end
            )
            $           # right up to end of string
        ",
        numberPattern,
        streetPattern,
        allSecondaryUnitPattern,
        placePattern,
        zipPattern);
    addressRegex = new Regex(
        addressPattern,
        RegexOptions.Compiled | 
        RegexOptions.Singleline | 
        RegexOptions.IgnorePatternWhitespace);
}

最佳答案

当省略 RegexOptions.Compiled 标志时,Regex 是否工作?

回答是肯定的。

为什么?

似乎 Regex 编译器对于(某些?)大模式很慢。

这是您必须做出的权衡。

关于c# - 大型 RegEx 匹配导致程序挂起,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10688115/

相关文章:

c# - 如何绘制图像?

c# - 如何提示用户他们可以滚动?

c# - WPF MVVM DataBindings 停止更新

javascript - 正则表达式用 Grails 注释替换 JavaScript 注释

c# - C#中的唯一编号生成

c# - 如何使用X509证书和C#进行非对称加密?

c# - 将参数传递给自定义模板

c# - 如何在分离 View 和模型时将世界坐标映射到屏幕坐标?

R 正则表达式 : isolate parenthesized suffix

python - Django 中过滤内的正则表达式