\ d è meno efficiente di [0-9]


1249

Ho fatto un commento ieri una risposta in cui qualcuno aveva usato [0123456789]in un'espressione regolare piuttosto che [0-9]o \d. Ho detto che probabilmente era più efficiente usare un identificatore di intervallo o cifra di un set di caratteri.

Ho deciso di provarlo oggi e ho scoperto con mia sorpresa che (almeno nel motore regex C #) \dsembra essere meno efficiente di uno degli altri due che non sembrano differire molto. Ecco il mio output di test su oltre 10000 stringhe casuali di 1000 caratteri casuali con 5077 che in realtà contengono una cifra:

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

È una sorpresa per me per due motivi:

  1. Avrei pensato che la gamma sarebbe stata implementata in modo molto più efficiente rispetto al set.
  2. Non riesco a capire perché \dsia peggio di [0-9]. C'è di più \dche una semplice scorciatoia per [0-9]?

Ecco il codice di prova:

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
Forse si \doccupa di locali. Ad esempio l'ebraico usa le lettere per le cifre.
Barmar,


37
Questa è una domanda interessante proprio perché \dnon significa la stessa cosa in lingue diverse. In Java, ad esempio \d, corrisponde effettivamente solo allo 0-9
Ray Toal,

17
@Barmar L'ebraico non usa normalmente le lettere per le cifre, piuttosto le stesse cifre numeriche latine [0-9]. Le lettere possono essere sostituite da cifre, ma questo è un uso raro e riservato a termini speciali. Non mi aspetterei che un parser regex corrisponda a כ"ג יורדי סירה (con כ"ג che sostituisce 23). Inoltre, come si può vedere nella risposta di Sina Iravanian, le lettere ebraiche non appaiono come corrispondenze valide per \ d.
Yuval Adam,

7
Il porting del codice di Weston su Java produce: - Regex \ d ha richiesto 00: 00: 00.043922 risultato: 4912/10000 - Regex [0-9] ha preso 00: 00: 00.073658 risultato: 4912/10000 167% del primo - Regex [ 0123456789] ha richiesto 00: 00: 00.085799 risultato: 4912/10000 195% dei primi
Lunchbox

Risposte:


1566

\dcontrolla tutte le cifre Unicode, mentre [0-9]è limitato a questi 10 caratteri. Ad esempio, le cifre persiane۱۲۳۴۵۶۷۸۹ , sono un esempio di cifre Unicode che sono abbinate \d, ma non [0-9].

Puoi generare un elenco di tutti questi caratteri usando il seguente codice:

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

Che genera:

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


121
Ecco un elenco più completo di cifre che non sono 0-9: fileformat.info/info/unicode/category/Nd/list.htm
Robert McKee,

8
@weston Unicode ha 17 piani con 16 bit ciascuno. I personaggi più importanti si trovano sul piano di base, ma alcuni personaggi speciali, per lo più cinesi, si trovano sui piani supplementari. Trattare con quelli in C # è un po 'fastidioso.
Codici A Caos

9
@RobertMcKee: Nitpick: il set completo di caratteri unicode è in realtà a 21 bit (17 piani da 16 bit ciascuno). Ma ovviamente un tipo di dati a 21 bit non è pratico, quindi se si utilizza un tipo di dati power-of-2, è vero che sono necessari 32 bit.
sleske,

3
Secondo questo articolo di Wikipedia , il Consorzio Unicode ha dichiarato che il limite di 1.114.112 punti di codice (da 0 a 0x010FFFF) non verrà mai modificato. Si collega a unicode.org, ma non ho trovato la dichiarazione lì (probabilmente l'ho persa).
Keith Thompson,

14
Non sarà mai cambiato - fino a quando non dovranno cambiarlo.
Robert McKee,

271

Ringraziamo ByteBlast per averlo notato nei documenti. Basta cambiare il costruttore di regex:

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

Dà nuovi tempi:

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
Che cosa RegexOptions.ECMAScriptfa?
laurent

7
Da Opzioni di espressione regolari : "Abilita comportamento conforme a ECMAScript per l'espressione".
chrisaycock,

28
@ 0xFE: non proprio. Gli escape Unicode sono ancora validi in ECMAScript( \u1234). Sono "solo" le classi di caratteri abbreviati che cambiano significato (like \d) e le proprietà Unicode / script shorthands che scompaiono (like \p{N}).
Tim Pietzcker,

9
Questa non è una risposta alla parte "perché". È una risposta "ripara i sintomi". Ancora preziose informazioni.
usr

In generale, Regrex supporta la corrispondenza unicode. Ma ECMAScript no. Quindi, quando si utilizza RegexOptions.ECMAScript, corrisponde solo a ascii, ovvero 0-9.
lzlstyle,

119

Da "\ d" in regex significa una cifra? :

[0-9]non è equivalente a \d. [0-9]corrisponde solo a 0123456789caratteri, mentre \dcorrisponde [0-9]e altri caratteri numerici, ad esempio numeri arabi orientali٠١٢٣٤٥٦٧٨٩


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

2
eh, sbaglio o questa frase dal link dice il contrario. "\ d corrisponde a qualsiasi cifra decimale. Equivale al modello di espressione regolare \ p {Nd}, che include le cifre decimali standard 0-9 e le cifre decimali di un numero di altri set di caratteri."
Ismet Alkan,

3
@ByteBlast grazie, usando il costruttore: var rex = new Regex(regex, RegexOptions.ECMAScript);li rende praticamente indistinguibili in termini di prestazioni.
Weston,

2
oh comunque, grazie a tutti. questa domanda si è rivelata un grande apprendimento per me.
Ismet Alkan,

3
Per favore, non "copiare" le risposte da altre domande. Se la domanda è duplicata, contrassegnala come tale.
BoltClock

20

Oltre alla risposta principale di Sina Iravianian , ecco una versione .NET 4.5 (poiché solo quella versione supporta l'output UTF16, cfr. Le prime tre righe) del suo codice, utilizzando l'intera gamma di punti di codice Unicode. A causa della mancanza di supporto adeguato per i piani Unicode superiori, molte persone non sono consapevoli di controllare sempre e includere i piani Unicode superiori. Tuttavia a volte contengono alcuni personaggi importanti.

Aggiornare

Poiché \dnon supporta i caratteri non BMP in regex (grazie a xanatos ), qui una versione che utilizza il database dei caratteri 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();
}

Produrre il seguente output:

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

LetterNumber

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

OtherNumber²³¹¼½¾৴৵৶.৸৹ ୲୳୴୵୶୷ ௰௱௲ ౸౹౺౻౼౽౾ ൰൱൲൳൴൵ ༪ ༫ ༬ ༭ ༮ ༯ ༰ ༱ ༲ ༳ ፩፪፫፬፭፮፯፰፱፲፳፴፵፶፷፸፹፺፻፼ ៰ ៱ ៲ ៳ ៴ ៵ ៶ ៷ ៸ ៹ ᧚⁰⁴⁵⁶⁷⁸⁹₀₁₂₃₄₅₆₇₈₉⅐⅑⅒⅓⅔⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞⅟↉①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇⒈⒉⒊⒋⒌⒍⒎⒏⒐⒑⒒⒓⒔⒕⒖⒗⒘⒙⒚⒛⓪⓫⓬⓭⓮⓯⓰⓱⓲⓳⓴⓵⓶⓷⓸⓹⓺⓻⓼⓽⓾⓿❶❷❸❹❺❻❼❽❾❿➀➁➂➃➄➅➆➇➈➉➊➋➌➍➎➏➐➑➒➓ ⳽ ㆒ ㆓ ㆔ ㆕ ㈠㈡㈢㈣㈤㈥㈦㈧㈨㈩㉈㉉㉊㉋㉌㉍㉎㉏㉑㉒㉓㉔㉕㉖㉗㉘㉙㉚㉛㉜㉝㉞㉟㊀㊁㊂㊃㊄㊅㊆㊇㊈㊉㊱㊲㊳㊴㊵㊶㊷㊸㊹㊺㊻㊼㊽㊾㊿꠰꠱꠲꠳꠴꠵𐄇𐄈𐄉𐄊𐄋𐄌𐄍𐄎𐄏𐄐𐄑𐄒𐄓𐄔𐄕𐄖𐄗𐄘𐄙𐄚𐄛𐄜𐄝𐄞𐄟𐄠𐄡𐄢𐄣𐄤𐄥𐄦𐄧𐄨𐄩𐄪𐄫𐄬𐄭𐄮𐄯𐄰𐄱𐄲𐄳𐅵𐅶𐅷𐅸𐆊𐆋𐋡𐋢𐋣𐋤𐋥𐋦𐋧𐋨𐋩𐋪𐋫𐋬𐋭𐋮𐋯𐋰𐋱𐋲𐋳𐋴𐋵𐋶𐋷𐋸𐋹𐋺𐋻 𐌠𐌡𐌢𐌣 𐡘𐡙𐡚𐡛𐡜𐡝𐡞𐡟 𐡹𐡺𐡻𐡼𐡽𐡾𐡿 𐢧𐢨𐢩𐢪𐢫𐢬𐢭𐢮𐢯 𐣻𐣼𐣽𐣾𐣿 𐤖𐤗𐤘𐤙𐤚𐤛 𐦼𐦽𐧀𐧁𐧂𐧃𐧄𐧅𐧆𐧇𐧈𐧉𐧊𐧋𐧌𐧍𐧎𐧏𐧒𐧓𐧔𐧕𐧖𐧗𐧘𐧙𐧚𐧛𐧜𐧝𐧞𐧟𐧠𐧡𐧢𐧣𐧤𐧥𐧦𐧧𐧨𐧩𐧪𐧫𐧬𐧭𐧮𐧯𐧰𐧱𐧲𐧳𐧴𐧵𐧶𐧷𐧸𐧹𐧺𐧻𐧼𐧽𐧾𐧿 𐩀𐩁𐩂𐩃𐩄𐩅𐩆𐩇 𐩽𐩾 𐪝𐪞𐪟 𐫫𐫬𐫭𐫮𐫯 𐭘𐭙𐭚𐭛𐭜𐭝𐭞𐭟 𐭸𐭹𐭺𐭻𐭼𐭽𐭾𐭿 𐮩𐮪𐮫𐮬𐮭𐮮𐮯 𐳺𐳻𐳼𐳽𐳾𐳿 𐹠𐹡𐹢𐹣𐹤𐹥𐹦𐹧𐹨𐹩𐹪𐹫𐹬𐹭𐹮𐹯𐹰𐹱𐹲𐹳𐹴𐹵𐹶𐹷𐹸𐹹𐹺𐹻𐹼𐹽𐹾 𑁒𑁓𑁔𑁕𑁖𑁗𑁘𑁙𑁚𑁛𑁜𑁝𑁞𑁟𑁠𑁡𑁢𑁣𑁤𑁥 𑇡𑇢𑇣𑇤𑇥𑇦𑇧𑇨𑇩𑇪𑇫𑇬𑇭𑇮𑇯𑇰𑇱𑇲𑇳𑇴 𑜺𑜻 𑣪𑣫𑣬𑣭𑣮𑣯𑣰𑣱𑣲 𖭛𖭜𖭝𖭞𖭟𖭠𖭡𝍠𝍡𝍢𝍣𝍤𝍥𝍦𝍧𝍨𝍩𝍪𝍫𝍬𝍭𝍮𝍯𝍰𝍱 𞣇𞣈𞣉𞣊𞣋𞣌𞣍𞣎𞣏🄀🄁🄂🄃🄄🄅🄆🄇🄈🄉🄊🄋🄌


La cosa triste è che la console Win32 non mostra personaggi astrali
Sebastian

4
Se ricordo bene, purtroppo in .NET Regexnon supporta i caratteri non BMP. Quindi, alla fine, controllare i caratteri> 0xffff con una regex è inutile.
xanatos,

-1

\ d controlla tutto Unicode, mentre [0-9] è limitato a questi 10 caratteri. Se solo 10 cifre, dovresti usare. Altri ti consiglio di usare \ d , Perché scrivere di meno.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.