\ d менее эффективен, чем [0-9]

1203

Вчера я сделал комментарий, где кто-то использовал [0123456789] в регулярном выражении, а не [0-9] или \d. Я сказал, что, вероятно, более эффективно использовать спецификатор диапазона или цифры, чем набор символов.

Я решил проверить это сегодня и, с удивлением обнаружил, что (по крайней мере, в двигателе с регулярным выражением С#) \d оказывается менее эффективным, чем любой из двух других, которые, похоже, не сильно отличаются друг от друга. Вот мой тестовый вывод более 10000 случайных строк из 1000 случайных символов с 5077, фактически содержащим цифру:

Regular expression \d           took 00:00:00.2141226 result: 5077/10000
Regular expression [0-9]        took 00:00:00.1357972 result: 5077/10000  63.42 % of first
Regular expression [0123456789] took 00:00:00.1388997 result: 5077/10000  64.87 % of first

Это сюрприз для меня по двум причинам:

  • Я бы подумал, что диапазон будет реализован намного эффективнее, чем набор.
  • Я не понимаю, почему \d хуже, чем [0-9]. Есть ли больше \d, чем просто сокращение для [0-9]?

Вот тестовый код:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Text.RegularExpressions;

namespace SO_RegexPerformance
{
    class Program
    {
        static void Main(string[] args)
        {
            var rand = new Random(1234);
            var strings = new List<string>();
            //10K random strings
            for (var i = 0; i < 10000; i++)
            {
                //Generate random string
                var sb = new StringBuilder();
                for (var c = 0; c < 1000; c++)
                {
                    //Add a-z randomly
                    sb.Append((char)('a' + rand.Next(26)));
                }
                //In roughly 50% of them, put a digit
                if (rand.Next(2) == 0)
                {
                    //Replace one character with a digit, 0-9
                    sb[rand.Next(sb.Length)] = (char)('0' + rand.Next(10));
                }
                strings.Add(sb.ToString());
            }

            var baseTime = testPerfomance(strings, @"\d");
            Console.WriteLine();
            var testTime = testPerfomance(strings, "[0-9]");
            Console.WriteLine("  {0:P2} of first", testTime.TotalMilliseconds / baseTime.TotalMilliseconds);
            testTime = testPerfomance(strings, "[0123456789]");
            Console.WriteLine("  {0:P2} of first", testTime.TotalMilliseconds / baseTime.TotalMilliseconds);
        }

        private static TimeSpan testPerfomance(List<string> strings, string regex)
        {
            var sw = new Stopwatch();

            int successes = 0;

            var rex = new Regex(regex);

            sw.Start();
            foreach (var str in strings)
            {
                if (rex.Match(str).Success)
                {
                    successes++;
                }
            }
            sw.Stop();

            Console.Write("Regex {0,-12} took {1} result: {2}/{3}", regex, sw.Elapsed, successes, strings.Count);

            return sw.Elapsed;
        }
    }
}
  • 166
    Возможно \d имеет дело с локалями. Например, иврит использует буквы для цифр.
  • 1
    По сути, когда вам приходится иметь дело с Юникодом, тогда он будет намного медленнее (поскольку он должен делать больше проверок).
Показать ещё 12 комментариев
Теги:
performance

6 ответов

1552
Лучший ответ

\d проверяет все цифры в Unicode, а [0-9] ограничивается этими 10 символами. Например, цифры Persian, ۱۲۳۴۵۶۷۸۹, являются примером цифр Unicode, которые сопоставляются с \d, но не [0-9].

Вы можете создать список всех таких символов, используя следующий код:

var sb = new StringBuilder();
for(UInt16 i = 0; i < UInt16.MaxValue; i++)
{
    string str = Convert.ToChar(i).ToString();
    if (Regex.IsMatch(str, @"\d"))
        sb.Append(str);
}
Console.WriteLine(sb.ToString());

Что генерирует:

+012345678901234567890123456789 ߀߁߂߃߄߅߆߇߈߉012345678 9 01২345678901234567890123456789 ୦୧୨୩୪୫୬୭୮୯ 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 ᠐᠑᠒᠓᠔᠕᠖᠗᠘᠙ ᥆᥇᥈᥉᥊᥋᥌᥍᥎᥏ ᧐᧑᧒᧓᧔᧕᧖᧗᧘᧙ ᭐᭑᭒᭓᭔᭕᭖᭗᭘᭙᮰᮱᮲᮳᮴᮵᮶᮷᮸᮹᱀᱁᱂᱃᱄᱅᱆᱇᱈᱉᱐᱑᱒᱓᱔᱕᱖᱗᱘᱙꘠꘡꘢꘣꘤꘥꘦꘧꘨꘩꣐꣑꣒꣓꣔꣕꣖꣗꣘꣙꤀꤁꤂꤃꤄꤅꤆꤇꤈꤉꩐꩑꩒꩓꩔꩕꩖꩗꩘꩙0123456789

  • 114
    Вот более полный список цифр, которые не являются 0-9: fileformat.info/info/unicode/category/Nd/list.htm
  • 2
    Круто, хотя должен быть UInt16 ? Кроме того, эта ссылка от Роберта показывает символы выше \uFFFF которые меня удивили, я думал, что это всего лишь 16 бит. Так что ваш код не найдет их, например, \ u104A0.
Показать ещё 14 комментариев
253

Кредит ByteBlast для уведомления об этом в документах. Просто изменив конструктор регулярных выражений:

var rex = new Regex(regex, RegexOptions.ECMAScript);

Дает новые тайминги:

Regex \d           took 00:00:00.1355787 result: 5077/10000
Regex [0-9]        took 00:00:00.1360403 result: 5077/10000  100.34 % of first
Regex [0123456789] took 00:00:00.1362112 result: 5077/10000  100.47 % of first
  • 10
    Что делает RegexOptions.ECMAScript ?
  • 5
    Из параметров регулярного выражения : «Включить ECMAScript-совместимое поведение для выражения».
Показать ещё 4 комментария
109

Из Имеет ли значение "\d" в регулярном выражении цифра?:

[0-9] не эквивалентен \d. [0-9] соответствует только символам 0123456789, а \d соответствует [0-9] и другим цифровым символам, например восточным арабским цифрам ٠١٢٣٤٥٦٧٨٩

  • 48
    Согласно: msdn.microsoft.com/en-us/library/20bw873z.aspx If ECMAScript-compliant behavior is specified, \d is equivalent to [0-9].
  • 2
    да, я не прав или это предложение по ссылке говорит об обратном. «\ d соответствует любой десятичной цифре. Это эквивалентно шаблону регулярного выражения \ p {Nd}, который включает стандартные десятичные цифры 0–9, а также десятичные цифры ряда других наборов символов».
Показать ещё 7 комментариев
15

В дополнение к верхнему ответу от Сина Иревианян, вот версия .NET 4.5 (так как только эта версия поддерживает выход UTF16, cf первые три строки) его кода, используя полный диапазон кодовых точек Unicode. Из-за отсутствия надлежащей поддержки для более высоких плоскостей Unicode многие люди не знают, что нужно всегда проверять и включать верхние юникодные плоскости. Тем не менее они иногда содержат некоторые важные символы.

Обновление

Так как \d не поддерживает символы без BMP в regex (спасибо xanatos), здесь версия, использующая базу данных символов Unicode

public static void Main()
{
    var unicodeEncoding = new UnicodeEncoding(!BitConverter.IsLittleEndian, false);
    Console.InputEncoding = unicodeEncoding;
    Console.OutputEncoding = unicodeEncoding;

    var sb = new StringBuilder();
    for (var codePoint = 0; codePoint <= 0x10ffff; codePoint++)
    {
        var isSurrogateCodePoint = codePoint <= UInt16.MaxValue 
               && (  char.IsLowSurrogate((char) codePoint) 
                  || char.IsHighSurrogate((char) codePoint)
                  );

        if (isSurrogateCodePoint)
            continue;

        var codePointString = char.ConvertFromUtf32(codePoint);

        foreach (var category in new []{
        UnicodeCategory.DecimalDigitNumber,
            UnicodeCategory.LetterNumber,
            UnicodeCategory.OtherNumber})
        {
        sb.AppendLine($"{category}");
            foreach (var ch in charInfo[category])
        {
                sb.Append(ch);
            }
            sb.AppendLine();
        }
    }
    Console.WriteLine(sb.ToString());

    Console.ReadKey();
}

Выход следующего выхода:

DecimalDigitNumber 012345678901234567890123456789߀߁߂߃߄߅߆߇߈߉012345678 9 01২345678901234567890123456789 ୦୧୨୩୪୫୬୭୮୯ 0123456789012345678901234567890123456789 ෦෧෨෩෪෫෬෭෮෯ 012345678901234567890123456789012345678901234567890123456789 ᠐᠑᠒᠓᠔᠕᠖᠗᠘᠙ ᥆᥇᥈᥉᥊᥋᥌᥍᥎᥏ ᧐᧑᧒᧓᧔᧕᧖᧗᧘᧙᪀᪁᪂᪃᪄᪅᪆᪇᪈᪉᪐᪑᪒᪓᪔᪕᪖᪗᪘᪙ ᭐᭑᭒᭓᭔᭕᭖᭗᭘᭙᮰᮱᮲᮳᮴᮵᮶᮷᮸᮹᱀᱁᱂᱃᱄᱅᱆᱇᱈᱉᱐᱑᱒᱓᱔᱕᱖᱗᱘᱙꘠꘡꘢꘣꘤꘥꘦꘧꘨꘩꣐꣑꣒꣓꣔꣕꣖꣗꣘꣙꤀꤁꤂꤃꤄꤅꤆꤇꤈꤉꧐꧑꧒꧓꧔꧕꧖꧗꧘꧙꧰꧱꧲꧳꧴꧵꧶꧷꧸꧹꩐꩑꩒꩓꩔꩕꩖꩗꩘꩙ ꯰꯱꯲꯳꯴꯵꯶꯷꯸꯹0123456789?????????? ?????????? ?????????? ?????????? ?????????? ?????????? ?????????? ?????????? ?????????? ?????????? ?????????? ?????????? ????????????????????????????????????????????????????????????

LetterNumber

ᛮᛯᛰⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾⅿↀↁↂↅↆↇↈ〇〡〢〣〤〥〦〧〨〩〸〹〺ꛦꛧꛨꛩꛪꛫꛬꛭꛮꛯ ????????????????????????????????????????????????????? ?? ????? ???????????????????????????????????????????????????????????????????????????????????????????????????????????????

OtherNumber ²³¹¼½¾৴৵৶.৸৹ ୲୳୴୵୶୷ ௰௱௲ ౸౹౺౻౼౽౾ ൰൱൲൳൴൵ ༪ ༫ ༬ ༭ ༮ ༯ ༰ ༱ ༲ ༳ ፩፪፫፬፭፮፯፰፱፲፳፴፵፶፷፸፹፺፻፼ ៰ ៱ ៲ ៳ ៴ ៵ ៶ ៷ ៸ ៹ ᧚⁰⁴⁵⁶⁷⁸⁹₀₁₂₃₄₅₆₇₈₉⅐⅑⅒⅓⅔⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞⅟↉①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇⒈⒉⒊⒋⒌⒍⒎⒏⒐⒑⒒⒓⒔⒕⒖⒗⒘⒙⒚⒛⓪⓫⓬⓭⓮⓯⓰⓱⓲⓳⓴⓵⓶⓷⓸⓹⓺⓻⓼⓽⓾⓿❶❷❸❹❺❻❼❽❾❿➀➁➂➃➄➅➆➇➈➉➊➋➌➍➎➏➐➑➒➓ ⳽ ㆒ ㆓ ㆔ ㆕ ㈠㈡㈢㈣㈤㈥㈦㈧㈨㈩㉈㉉㉊㉋㉌㉍㉎㉏㉑㉒㉓㉔㉕㉖㉗㉘㉙㉚㉛㉜㉝㉞㉟㊀㊁㊂㊃㊄㊅㊆㊇㊈㊉㊱㊲㊳㊴㊵㊶㊷㊸㊹㊺㊻㊼㊽㊾㊿꠰꠱꠲꠳꠴꠵?????????????????????????????????? ???????????????????????????????????????????? ???? ???????? ??????? ????????? ????? ?????? ???????????????????????????????????????????????????????????????? ???????? ?? ??? ????? ???????? ???????? ??????? ?????? ??????????????????????????????? ???????????????????? ????? ??????????????? ?? ????????? ????????????????????????? ??????????????????????

  • 0
    Печально то, что консоль Win32 не отображает астральные символы
  • 4
    Если я правильно помню, к сожалению, в .NET Regex не поддерживает не-BMP символы. Так что в конце проверка символов> 0xffff с регулярным выражением бесполезна.
0

\ d проверяет все Unicode, а [0-9] ограничивается этими 10 символами. Если всего 10 цифр, вы должны использовать. Другие рекомендую использовать \d, потому что писать меньше.

-13

\d будет менее эффективным, потому что он должен быть преобразован для сравнения.

Например, если я хочу, чтобы Regex находил IP-адреса, я предпочел бы \d, чем [0123456789] или даже [0-9] представлять любую цифру.

Вообще говоря, в моем использовании Regex, функция, если важнее скорости.

  • 1
    Хотя это может повлечь за собой небольшое наказание, это можно сделать один раз, прежде чем сравнивать шаблон с любой входной строкой. Таким образом, сложность по времени составляет O(1) , а не O(n) , (где n - длина входной строки.) Короче говоря, влияние в лучшем случае минимально.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню