\ d менш ефективний, ніж [0-9]


1246

Я вчора зробив коментар на відповідь , де хто - то використовується [0123456789]в регулярному виразі , а не [0-9]чи \d. Я сказав, що, мабуть, ефективніше використовувати специфікатор діапазону чи цифр, ніж набір символів.

Я вирішив перевірити це сьогодні і здивував, що (як мінімум, в двигуні C # regex) \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

Мене це здивує з двох причин:

  1. Я би подумав, що діапазон буде реалізований набагато ефективніше, ніж набір.
  2. Я не можу зрозуміти, чому \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;
        }
    }
}

178
Можливо, \dмає справу з локалями. Наприклад, іврит використовує літери для цифр.
Бармар

6
пов’язано: stackoverflow.com/a/6479605/674039
wim

37
Це цікаве питання саме тому, \dщо не означає одне й те саме на різних мовах. Наприклад, на Java \dсправді відповідає лише 0-9
Рей Тол

17
@Barmar Hebrew не використовує букви для цифр звичайно, скоріше однакові цифри з латинськими цифрами [0-9]. Букви можуть бути замінені цифрами, але це рідкісне використання і зарезервоване для спеціальних термінів. Я б не очікував, що аналізатор регулярного вирівнювання збігається з כ"ג יורדי סירה (причому כ"ג замінить 23). Також, як видно з відповіді Сини Іраваняна, літери на івриті не відображаються як дійсні збіги для \ d.
Ювал Адам

7
Перенесення коду Вестона на Java дає: - Regex \ d взяв 00: 00: 00.043922 результат: 4912/10000 - Regex [0-9] взяв 00: 00: 00.073658 результат: 4912/10000 167% першого - Regex [ 0123456789] взяв 00: 00: 00.085799 результат: 4912/10000 195% першого
Lunchbox

Відповіді:


1565

\dперевіряє всі цифри Unicode, хоча [0-9]він обмежений цими 10 символами. Наприклад, персидські цифри, ۱۲۳۴۵۶۷۸۹є прикладом цифр 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());

Що породжує:

0123456789٠١٢٣٤٥٦٧٨٩۰۱۲۳۴۵۶۷۸۹߀߁߂߃߄߅߆߇߈߉०१२३४५६७८ ९ ০১২৩৪৫৬৭৮৯੦੧੨੩੪੫੬੭੮੯૦૧૨૩૪૫૬૭૮૯ ୦୧୨୩୪୫୬୭୮୯ ௦௧௨௩௪௫௬௭௮௯౦౧౨౩౪౫౬౭౮౯೦೧೨೩೪೫೬೭೮೯൦൧൨൩൪൫൬൭൮൯๐๑๒๓๔๕๖๗๘๙໐໑໒໓໔໕໖໗໘໙༠༡༢༣༤༥༦༧༨༩၀၁၂၃၄၅၆၇၈၉႐႑႒႓႔႕႖႗႘႙០១២៣៤៥៦៧៨៩ ᠐᠑᠒᠓᠔᠕᠖᠗᠘᠙ ᥆᥇᥈᥉᥊᥋᥌᥍᥎᥏ ᧐᧑᧒᧓᧔᧕᧖᧗᧘᧙ ᭐᭑᭒᭓᭔᭕᭖᭗᭘᭙᮰᮱᮲᮳᮴᮵᮶᮷᮸᮹᱀᱁᱂᱃᱄᱅᱆᱇᱈᱉᱐᱑᱒᱓᱔᱕᱖᱗᱘᱙꘠꘡꘢꘣꘤꘥꘦꘧꘨꘩꣐꣑꣒꣓꣔꣕꣖꣗꣘꣙꤀꤁꤂꤃꤄꤅꤆꤇꤈꤉꩐꩑꩒꩓꩔꩕꩖꩗꩘꩙0123456789


121
Ось більш повний список цифр, яких немає 0-9: fileformat.info/info/unicode/category/Nd/list.htm
Роберт Маккі

8
@weston Unicode має 17 літаків з 16 бітами в кожній. Найбільш важливі символи знаходяться в базовій площині, але деякі спеціальні символи, переважно китайські, знаходяться в додаткових площинах. Справа з тими в C # трохи дратує.
CodesInChaos

9
@RobertMcKee: Nitpick: Повний набір символів Unicode насправді 21 біт (17 площин по 16 біт кожна). Але, звичайно, 21-бітний тип даних недоцільно, тому якщо ви використовуєте тип даних потужності-2, це правда, що вам потрібно 32-бітний.
sleske

3
Відповідно до цієї статті у Вікіпедії , Консорціум Unicode заявив, що ліміт 1,114,112 кодових пунктів (від 0 до 0x010FFFF) ніколи не буде змінено. Він посилається на unicode.org, але я там не знайшов заяву (я, мабуть, просто її пропустив).
Кіт Томпсон

14
Це ніколи не буде змінено - поки їм не потрібно буде це змінити.
Роберт Маккі

271

Подяка 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

11
Що робить RegexOptions.ECMAScript?
Лоран

7
З параметрів регулярних виразів : "Увімкніть поведінку, відповідну ECMAScript."
chrisaycock

28
@ 0xFE: Не зовсім. Уникнення Unicode все ще діє в ECMAScript( \u1234). Це "просто" класи скорочень символів, які змінюють значення (як \d) і скорочення (властивості) / скрипту Unicode, які відходять (як \p{N}).
Тім Піцкер

9
Це не відповідь на частину "чому". Це відповідь "виправити симптоми". Ще цінна інформація.
usr

Як правило, Regrex підтримує збіг унікодів. Але ECMAScript цього не робить. Отже, при використанні RegexOptions.ECMAScript він відповідає лише ascii, тобто 0-9.
lzlstyle

119

З Чи означає "\ d" в регулярному виразі цифру? :

[0-9]не еквівалентно \d. [0-9]відповідає лише 0123456789символам, тоді як \dзбіги [0-9]та інші цифри, наприклад, східноарабські цифри٠١٢٣٤٥٦٧٨٩


49
Відповідно до: msdn.microsoft.com/en-us/library/20bw873z.aspx If ECMAScript-compliant behavior is specified, \d is equivalent to [0-9].
Користувач 12345678

2
так, я помиляюся, або це речення із посилання говорить про зворотне. "\ d відповідає будь-якій десятковій цифрі. Це еквівалентно шаблону регулярного вираження \ p {Nd}, який включає стандартні десяткові цифри 0-9, а також десяткові цифри ряду інших наборів символів."
Ізмет Алкан

3
@ByteBlast дякує, використовуючи конструктор: var rex = new Regex(regex, RegexOptions.ECMAScript);робить їх все майже невідрізним у плані продуктивності.
Вестон

2
ой все одно, дякую всім. це питання виявилося для мене чудовим навчанням.
Ізмет Алкан

3
Будь ласка, не "просто копіюйте" відповіді з інших питань. Якщо питання є дублікатом, позначте його як таке.
BoltClock

20

Доповнення до топової відповіді від Ірану Сіна , ось версія .NET 4.5 (оскільки лише ця версія підтримує вихід UTF16, див. Перші три рядки) його коду, використовуючи весь спектр кодових точок Unicode. Через відсутність належної підтримки для вищих літаків Unicode багато людей не знають, як завжди перевіряти та включати верхні площини Unicode. Проте вони іноді містять важливі символи.

Оновлення

Оскільки \dне підтримує символи, що не належать до BMP, в регулярному виразі (спасибі 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 0123456789٠١٢٣٤٥٦٧٨٩۰۱۲۳۴۵۶۷۸۹߀߁߂߃߄߅߆߇߈߉०१२३४५६७८ ९ ০১২৩৪৫৬৭৮৯੦੧੨੩੪੫੬੭੮੯૦૧૨૩૪૫૬૭૮૯ ୦୧୨୩୪୫୬୭୮୯ ௦௧௨௩௪௫௬௭௮௯౦౧౨౩౪౫౬౭౮౯೦೧೨೩೪೫೬೭೮೯൦൧൨൩൪൫൬൭൮൯ ෦෧෨෩෪෫෬෭෮෯ ๐๑๒๓๔๕๖๗๘๙໐໑໒໓໔໕໖໗໘໙༠༡༢༣༤༥༦༧༨༩၀၁၂၃၄၅၆၇၈၉႐႑႒႓႔႕႖႗႘႙០១២៣៤៥៦៧៨៩ ᠐᠑᠒᠓᠔᠕᠖᠗᠘᠙ ᥆᥇᥈᥉᥊᥋᥌᥍᥎᥏ ᧐᧑᧒᧓᧔᧕᧖᧗᧘᧙᪀᪁᪂᪃᪄᪅᪆᪇᪈᪉᪐᪑᪒᪓᪔᪕᪖᪗᪘᪙ 𐒠𐒡𐒢𐒣𐒤𐒥𐒦𐒧𐒨𐒩 𑁦𑁧𑁨𑁩𑁪𑁫𑁬𑁭𑁮𑁯 𑃰𑃱𑃲𑃳𑃴𑃵𑃶𑃷𑃸𑃹 𑄶𑄷𑄸𑄹𑄺𑄻𑄼𑄽𑄾𑄿 𑇐𑇑𑇒𑇓𑇔𑇕𑇖𑇗𑇘𑇙 𑋰𑋱𑋲𑋳𑋴𑋵𑋶𑋷𑋸𑋹 𑓐𑓑𑓒𑓓𑓔𑓕𑓖𑓗𑓘𑓙 𑙐𑙑𑙒𑙓𑙔𑙕𑙖𑙗𑙘𑙙 𑛀𑛁𑛂𑛃𑛄𑛅𑛆𑛇𑛈𑛉 𑜰𑜱𑜲𑜳𑜴𑜵𑜶𑜷𑜸𑜹 𖩠𖩡𖩢𖩣𖩤𖩥𖩦𖩧𖩨𖩩 𖭐𖭑𖭒𖭓𖭔𖭕𖭖𖭗𖭘𖭙𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗𝟘𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡𝟢𝟣𝟤𝟥𝟦𝟧𝟨𝟩𝟪𝟫𝟬𝟭𝟮𝟯𝟰𝟱𝟲𝟳𝟴𝟵𝟶𝟷𝟸𝟹𝟺𝟻𝟼𝟽𝟾𝟿

LetterNumber

ᛮᛯᛰⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾⅿↀↁↂↅↆↇↈ〇〡〢〣〤〥〦〧〨〩〸〹〺ꛦꛧꛨꛩꛪꛫꛬꛭꛮꛯ 𐅀𐅁𐅂𐅃𐅄𐅅𐅆𐅇𐅈𐅉𐅊𐅋𐅌𐅍𐅎𐅏𐅐𐅑𐅒𐅓𐅔𐅕𐅖𐅗𐅘𐅙𐅚𐅛𐅜𐅝𐅞𐅟𐅠𐅡𐅢𐅣𐅤𐅥𐅦𐅧𐅨𐅩𐅪𐅫𐅬𐅭𐅮𐅯𐅰𐅱𐅲𐅳𐅴 𐍁𐍊 𐏑𐏒𐏓𐏔𐏕 𒐀𒐁𒐂𒐃𒐄𒐅𒐆𒐇𒐈𒐉𒐊𒐋𒐌𒐍𒐎𒐏𒐐𒐑𒐒𒐓𒐔𒐕𒐖𒐗𒐘𒐙𒐚𒐛𒐜𒐝𒐞𒐟𒐠𒐡𒐢𒐣𒐤𒐥𒐦𒐧𒐨𒐩𒐪𒐫𒐬𒐭𒐮𒐯𒐰𒐱𒐲𒐳𒐴𒐵𒐶𒐷𒐸𒐹𒐺𒐻𒐼𒐽𒐾𒐿𒑀𒑁𒑂𒑃𒑄𒑅𒑆𒑇𒑈𒑉𒑊𒑋𒑌𒑍𒑎𒑏𒑐𒑑𒑒𒑓𒑔𒑕𒑖𒑗𒑘𒑙𒑚𒑛𒑜𒑝𒑞𒑟𒑠𒑡𒑢𒑣𒑤𒑥𒑦𒑧𒑨𒑩𒑪𒑫𒑬𒑭𒑮

OtherNumber²³¹¼½¾৴৵৶৷৸৹ ୲୳୴୵୶୷ ௰௱௲ ౸౹౺౻౼౽౾ ൰൱൲൳൴൵ ༪ ༫ ༬ ༭ ༮ ༯ ༱ ༲ ༳ ፩፪፫፬፭፮፯፰፱፲፳፴፵፶፷፸፹፺፻፼ ៰ ៱ ៲ ៳ ៵ ៶ ៷ ៹ ៹ ᧚⁰⁴⁵⁶⁷⁸⁹₀₁₂₃₄₅₆₇₈₉⅐⅑⅒⅓⅔⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞⅟↉①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇⒈⒉⒊⒋⒌⒍⒎⒏⒐⒑⒒⒓⒔⒕⒖⒗⒘⒙⒚⒛⓪⓫⓬⓭⓮⓯⓰⓱⓲⓳⓴⓵⓶⓷⓸⓹⓺⓻⓼⓽⓾⓿❶❷❸❹❺❻❼❽❾❿➀➁➂➃➄➅➆➇➈➉➊➋➌➍➎➏➐➑➒➓ ㆒ ㆓ ㆔ ㆔ ㆕ 𐌠𐌡𐌢𐌣 𐡘𐡙𐡚𐡛𐡜𐡝𐡞𐡟 𐡹𐡺𐡻𐡼𐡽𐡾𐡿 𐢧𐢨𐢩𐢪𐢫𐢬𐢭𐢮𐢯 𐣻𐣼𐣽𐣾𐣿 𐤖𐤗𐤘𐤙𐤚𐤛 𐩀𐩁𐩂𐩃𐩄𐩅𐩆𐩇 𐩀𐩁𐩂𐩃𐩄𐩅𐩆𐩇 𐩽𐩾 𐮩𐮪𐮫𐮬𐮭𐮮𐮯 𑁒𑁓𑁔𑁕𑁖𑁗𑁘𑁙𑁚𑁛𑁜𑁝𑁞𑁟𑁠𑁡𑁢𑁣𑁤𑁥 𐫫𐫬𐫭𐫮𐫯 𐫫𐫬𐫭𐫮𐫯 𐫫𐫬𐫭𐫮𐫯𑇧𑇨𑇩𑇪𑇫𑇬𑇭𑇮𑇯𑇰𑇱𑇲𑇳𑇴 𑜺𑜻 𑣪𑣫𑣬𑣭𑣮𑣯𑣰𑣱𑣲 𖭛𖭜𖭝𖭞𖭟𖭠𖭡𝍠𝍡𝍢𝍣𝍤𝍥𝍦𝍧𝍨𝍩𝍪𝍫𝍬𝍭𝍮𝍯𝍰𝍱 𞣇𞣈𞣉𞣊𞣋𞣌𞣍𞣎𞣏🄀🄁🄂🄃🄄🄅🄆🄇🄈🄉🄊🄋🄌


Сумно в тому, що консоль Win32 не відображає астральних персонажів
Себастьян

4
Якщо я пригадую з усією думкою, на жаль у .NET Regexне підтримує символів, що не належать до BMP. Отже, врешті-решт перевірка символів> 0xffff з регулярним виразом марно.
xanatos

-1

\ d перевіряє всі Unicode, тоді як [0-9] обмежено цими 10 символами. Якщо всього 10 цифр, ви повинні використовувати. Інші рекомендую використовувати \ d d Тому що писати менше.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.