\ d é menos eficiente que [0-9]


1249

Ontem fiz um comentário sobre uma resposta em que alguém usava [0123456789]uma expressão regular em vez de [0-9]ou \d. Eu disse que provavelmente era mais eficiente usar um especificador de intervalo ou dígito do que um conjunto de caracteres.

Decidi testar isso hoje e descobri, para minha surpresa, que (pelo menos no mecanismo de regex C #) \dparece ser menos eficiente do que qualquer um dos outros dois que não parecem diferir muito. Aqui está minha saída de teste com mais de 10000 seqüências aleatórias de 1000 caracteres aleatórios, com 5077 na verdade contendo um dígito:

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

É uma surpresa para mim por dois motivos:

  1. Eu teria pensado que o intervalo seria implementado com muito mais eficiência do que o conjunto.
  2. Não consigo entender por que \dé pior que [0-9]. Existe mais do \dque simplesmente abreviação para [0-9]?

Aqui está o código do teste:

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
Talvez \dlide com locais. Por exemplo, o hebraico usa letras para dígitos.
Barmar


37
Esta é uma pergunta interessante precisamente porque \dnão significa a mesma coisa em diferentes idiomas. Em Java, por exemplo \d, de fato corresponde apenas de 0 a 9 #
Ray Toal

17
@Barmar O hebraico não usa letras para dígitos normalmente, mas os mesmos dígitos dos números latinos [0-9]. As letras podem ser substituídas por dígitos, mas esse é um uso raro e reservado a termos especiais. Eu não esperaria que um analisador de expressões regulares correspondesse a כ"ג יורדי סירה (com כ"ג substituindo 23). Além disso, como pode ser visto na resposta de Sina Iravanian, as letras hebraicas não aparecem como correspondências válidas para \ d.
Yuval Adam

7
Portar o código da weston para Java produz: - Regex \ d levou 00: 00: 00.043922 resultado: 4912/10000 - Regex [0-9] levou 00: 00: 00.073658 resultado: 4912/10000 167% do primeiro - Regex [ 0123456789] levou 00: 00: 00.085799 resultado: 4912/10000 195% do primeiro
Lunchbox

Respostas:


1566

\dverifica todos os dígitos Unicode, enquanto [0-9]está limitado a esses 10 caracteres. Por exemplo, dígitos persas ,, ۱۲۳۴۵۶۷۸۹são um exemplo de dígitos Unicode correspondentes \d, mas não correspondentes [0-9].

Você pode gerar uma lista de todos esses caracteres usando o seguinte código:

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());

O que gera:

0123456789٠١٢٣٤٥٦٧٨٩۰۱۲۳۴۵۶۷۸۹߀߁߂߃߄߅߆߇߈߉०१२३४५६७८ ९ ০১২৩৪৫৬৭৮৯੦੧੨੩੪੫੬੭੮੯૦૧૨૩૪૫૬૭૮૯ ୦୧୨୩୪୫୬୭୮୯ ௦௧௨௩௪௫௬௭௮௯౦౧౨౩౪౫౬౭౮౯೦೧೨೩೪೫೬೭೮೯൦൧൨൩൪൫൬൭൮൯๐๑๒๓๔๕๖๗๘๙໐໑໒໓໔໕໖໗໘໙༠༡༢༣༤༥༦༧༨༩၀၁၂၃၄၅၆၇၈၉႐႑႒႓႔႕႖႗႘႙០១២៣៤៥៦៧៨៩ ᠐᠑᠒᠓᠔᠕᠖᠗᠘᠙ ᥆᥇᥈᥉᥊᥋᥌᥍᥎᥏ ᧐᧑᧒᧓᧔᧕᧖᧗᧘᧙.


121
Aqui está uma lista mais completa de dígitos que não são 0-9: fileformat.info/info/unicode/category/Nd/list.htm
Robert McKee

8
O @weston Unicode possui 17 planos com 16 bits cada. Os caracteres mais importantes estão no plano básico, mas alguns caracteres especiais, principalmente chineses, estão nos planos suplementares. Lidar com aqueles em C # é um pouco chato.
CodesInChaos

9
@RobertMcKee: Nitpick: Na verdade, o conjunto de caracteres unicode completo é de 21 bits (17 planos de 16 bits cada). Mas é claro que um tipo de dados de 21 bits é impraticável; portanto, se você usa um tipo de dados com potência de 2, é verdade que precisa de 32 bits.
Sleske

3
De acordo com este artigo da Wikipedia , o Consórcio Unicode declarou que o limite de 1.114.112 pontos de código (0 a 0x010FFFF) nunca será alterado. Ele está vinculado ao unicode.org, mas eu não encontrei a declaração lá (provavelmente só a perdi).
Keith Thompson

14
Isso nunca será alterado - até que eles precisem mudar.
Robert McKee

271

Os nossos agradecimentos à ByteBlast por ter observado isso nos documentos. Apenas alterando o construtor regex:

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

Dá novos horários:

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
O que RegexOptions.ECMAScriptfaz?
21413

7
Em Opções de expressão regular : "Habilite o comportamento compatível com ECMAScript para a expressão."
Chrisaycock #

28
@ 0xFE: Não é bem assim. Escapes Unicode ainda são válidas em ECMAScript( \u1234). São "apenas" as classes de caracteres abreviados que mudam de significado (como \d) e as propriedades / scripts Unicode que desaparecem (como \p{N}).
Tim Pietzcker

9
Esta não é uma resposta para a parte "por que". É uma resposta "corrija os sintomas". Ainda informação valiosa.
usr

Geralmente, o Regrex suporta a correspondência unicode. Mas o ECMAScript não. Portanto, ao usar o RegexOptions.ECMAScript, ele corresponde apenas ao ascii, ou seja, 0-9.
Lzlstyle

119

De "\ d" em regex significa um dígito? :

[0-9]não é equivalente a \d. [0-9]corresponde apenas a 0123456789caracteres, enquanto \dcorrespondências [0-9]e outros caracteres de dígitos, por exemplo, algarismos arábicos orientais٠١٢٣٤٥٦٧٨٩


49
De acordo com: msdn.microsoft.com/en-us/library/20bw873z.aspx If ECMAScript-compliant behavior is specified, \d is equivalent to [0-9].
Usuário 12345678

2
Estou errado ou esta frase do link está dizendo o contrário. "\ d corresponde a qualquer dígito decimal. É equivalente ao padrão de expressão regular \ p {Nd}, que inclui os dígitos decimais padrão de 0 a 9, bem como os dígitos decimais de vários outros conjuntos de caracteres."
Ismet Alkan

3
@ByteBlast obrigado, usando o construtor: var rex = new Regex(regex, RegexOptions.ECMAScript);torna todos praticamente indistinguíveis em termos de desempenho.
weston

2
de qualquer forma, obrigado a todos. essa pergunta acabou sendo um grande aprendizado para mim.
Ismet Alkan

3
Por favor, não "copie" as respostas de outras perguntas. Se a pergunta for uma duplicata, sinalize-a como tal.
BoltClock

20

Além da resposta principal do Sina Iravianian , aqui está uma versão do .NET 4.5 (já que apenas essa versão suporta saída UTF16, nas três primeiras linhas) de seu código, usando toda a gama de pontos de código Unicode. Devido à falta de suporte adequado para planos Unicode mais altos, muitas pessoas não estão cientes de sempre procurar e incluir os planos Unicode superiores. No entanto, eles às vezes contêm alguns caracteres importantes.

Atualizar

Como \dnão suporta caracteres não BMP no regex (obrigado xanatos ), aqui uma versão que usa o banco de dados de caracteres 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();
}

Rendendo a seguinte saída:

DecimalDigitNumber 0123456789٠١٢٣٤٥٦٧٨٩۰۱۲۳۴۵۶۷۸۹߀߁߂߃߄߅߆߇߈߉०१२३४५६७८ ९ ९ ০১২৩৪৫৬৭৮৯੦੧੨੩੪੫੬੭੮੯૦૧૨૩૪૫૬૭૮૯ ୦୧୨୩୪୫୬୭୮୯ ௦௧௨௩௪௫௬௭௮௯౦౧౨౩౪౫౬౭౮౯೦೧೨೩೪೫೬೭೮೯൦൧൨൩൪൫൬൭൮൯ ๐๑๒๓๔๕๖๗๘๙໐໑໒໓໔໕໖໗໘໙༠༡༢༣༤༥༦༧༨༩၀၁၂၃၄၅၆၇၈၉႐႑႒႓႔႕႖႗႘႙០១២៣៤៥៦៧៨៩ ᠐᠑᠒᠓᠔᠕᠖᠗᠘᠙ ᥆᥇᥈᥉᥊᥋᥌᥍᥎᥏ ᭐᭑᭒᭓᭔᭕᭖᭗᭘᭙᮰᮱᮲᮳᮴᮵᮶᮷᮸᮹᱀᱁᱂᱃᱄᱅᱆᱇᱈᱉᱐᱑᱒᱓᱔᱕᱖᱗᱘᱙꘠꘡꘢꘣꘤꘥꘦꘧꘨꘩꣐꣑꣒꣓꣔꣕꣖꣗꣘꣙꤀꤁꤂꤃꤄꤅꤆꤇꤈꤉꧐꧑꧒꧓꧔꧕꧖꧗꧘꧙꧰꧱꧲꧳꧴꧵꧶꧷꧸꧹꩐꩑꩒꩓꩔꩕꩖꩗꩘꩙꯰꯱꯲꯳꯴꯵꯶꯷꯸꯹0123456789 𐒠𐒡𐒢𐒣𐒤𐒥𐒦𐒧𐒨𐒩 𑁦𑁧𑁨𑁩𑁪𑁫𑁬𑁭𑁮𑁯 𑃰𑃱𑃲𑃳𑃴𑃵𑃶𑃷𑃸𑃹 𑄶𑄷𑄸𑄹𑄺𑄻𑄼𑄽𑄾𑄿 𑇐𑇑𑇒𑇓𑇔𑇕𑇖𑇗𑇘𑇙 𑋰𑋱𑋲𑋳𑋴𑋵𑋶𑋷𑋸𑋹 𑓐𑓑𑓒𑓓𑓔𑓕𑓖𑓗𑓘𑓙 𑙐𑙑𑙒𑙓𑙔𑙕𑙖𑙗𑙘𑙙 𑜰𑜱𑜲𑜳𑜴𑜵𑜶𑜷𑜸𑜹 𖭐𖭑𖭒𖭓𖭔𖭕𖭖𖭗𖭘𖭙𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗𝟘𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡𝟢𝟣𝟤𝟥𝟦𝟧𝟨𝟩𝟪𝟫𝟬𝟭𝟮𝟯𝟰𝟱𝟲𝟳𝟴𝟵𝟶𝟷𝟸𝟹𝟺𝟻𝟼𝟽𝟾𝟿 𑇐𑇑𑇒𑇓𑇔𑇕𑇖𑇗𑇘𑇙

LetterNumber

ᛮᛯᛰⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾⅿↀↁↂↅↆↇↈ〇〡〢〣〤〥〦〧〨〩〸〹〺ꛦꛧꛨꛩꛪꛫꛬꛭꛮꛯ 𐅀𐅁𐅂𐅃𐅄𐅅𐅆𐅇𐅈𐅉𐅊𐅋𐅌𐅍𐅎𐅏𐅐𐅑𐅒𐅓𐅔𐅕𐅖𐅗𐅘𐅙𐅚𐅛𐅜𐅝𐅞𐅟𐅠𐅡𐅢𐅣𐅤𐅥𐅦𐅧𐅨𐅩𐅪𐅫𐅬𐅭𐅮𐅯𐅰𐅱𐅲𐅳𐅴 𐍁𐍊 𐏑𐏒𐏓𐏔𐏕

OtherNumber²³¹¼½¾৴৵৶৷৸৹ ୲୳୴୵୶୷ ௰௱௲ ౸౹౺౻౼౽౾ ༮ ༯ ༰ ༱ ༲ ፩፪፫፬፭፮፯፰፱፲፳፴፵፶፷፸፹፺፻፼ ៰ ៱ ៲ ៳ ៴ ៵ ៶ ៷ ៹ ៹ ᧚⁰⁴⁵⁶⁷⁸⁹₀₁₂₃₄₅₆₇₈₉⅐⅑⅒⅓⅔⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞⅟↉①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇⒈⒉⒊⒋⒌⒍⒎⒏⒐⒑⒒⒓⒔⒕⒖⒗⒘⒙⒚⒛⓪⓫⓬⓭⓮⓯⓰⓱⓲⓳⓴⓵⓶⓷⓸⓹⓺⓻⓼⓽⓾⓿❶❷❸❹❺❻❼❽❾❿➀➁➂➃➄➅➆➇➈➉➊➋➌➍➎➏➐➑➒➓ ᧚⁰⁴⁵⁶⁷⁸⁹₀₁₂₃₄₅₆₇₈₉⅐⅑⅒⅓⅔⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞⅟↉①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇⒈⒉⒊⒋⒌⒍⒎⒏⒐⒑⒒⒓⒔⒕⒖⒗⒘⒙⒚⒛⓪⓫⓬⓭⓮⓯⓰⓱⓲⓳⓴⓵⓶⓷⓸⓹⓺⓻⓼⓽⓾⓿❶❷❸❹❺❻❼❽❾❿➀➁➂➃➄➅➆➇➈➉➊➋➌➍➎➏➐➑➒➓ ㆒ ㆓ ㆔ ㆕ ㆕ ㆕ 𐢧𐢨𐢩𐢪𐢫𐢬𐢭𐢮𐢯 𐭸𐭹𐭺𐭻𐭼𐭽𐭾𐭿 𐪝𐪞𐪟 𐪝𐪞𐪟 𐪝𐪞𐪟 𐭸𐭹𐭺𐭻𐭼𐭽𐭾𐭿 𐪝𐪞𐪟 𐪝𐪞𐪟 𐪝𐪞𐪟 𐭸𐭹𐭺𐭻𐭼𐭽𐭾𐭿 𐭸𐭹𐭺𐭻𐭼𐭽𐭾𐭿.𑇧𑇨𑇩𑇪𑇫𑇬𑇭𑇮𑇯𑇰𑇱𑇲𑇳𑇴 𑜺𑜻 𑣪𑣫𑣬𑣭𑣮𑣯𑣰𑣱𑣲 𖭛𖭜𖭝𖭞𖭟𖭠𖭡𝍠𝍡𝍢𝍣𝍤𝍥𝍦𝍧𝍨𝍩𝍪𝍫𝍬𝍭𝍮𝍯𝍰𝍱


O triste é que o Console Win32 não exibir caracteres astrais
Sebastian

4
Se bem me lembro, infelizmente, o .NET Regexnão suporta caracteres não BMP. Portanto, no final, verificar caracteres> 0xffff com um regex é inútil.
Xanatos # 12/17

-1

\ d verifica todos os Unicode, enquanto [0-9] está limitado a esses 10 caracteres. Se apenas 10 dígitos, você deve usar. Outros eu recomendo usar \ d , porque escrever menos.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.