\ d ist weniger effizient als [0-9]

1247

Ich habe gestern einen Kommentar zu einer Antwort abgegeben, bei der jemand [0123456789]einen regulären Ausdruck anstelle von [0-9]oder verwendet hat \d. Ich sagte, es sei wahrscheinlich effizienter, einen Bereichs- oder Ziffernbezeichner als einen Zeichensatz zu verwenden.

Ich habe mich heute entschlossen, das zu testen, und zu meiner Überraschung herausgefunden, dass (zumindest in der C # -Regex-Engine) \dweniger effizient zu sein scheint als die beiden anderen, die sich nicht sehr zu unterscheiden scheinen. Hier ist meine Testausgabe über 10000 zufällige Zeichenfolgen mit 1000 zufälligen Zeichen, wobei 5077 tatsächlich eine Ziffer enthält:

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

Es ist eine Überraschung für mich aus zwei Gründen:

  1. Ich hätte gedacht, dass der Bereich viel effizienter implementiert werden würde als das Set.
  2. Ich kann nicht verstehen, warum \des schlimmer ist als [0-9]. Gibt es mehr \dals nur eine Abkürzung [0-9]?

Hier ist der Testcode:

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;
        }
    }
}
Weston
quelle
178
Vielleicht \dbefasst sich mit Gebietsschemas. ZB verwendet Hebräisch Buchstaben für Ziffern.
Barmar
37
Dies ist gerade deshalb eine interessante Frage, weil \dsie in verschiedenen Sprachen nicht dasselbe bedeutet. In Java zum Beispiel \dstimmt tatsächlich nur 0-9 überein
Ray Toal
17
@Barmar Hebrew verwendet normalerweise keine Buchstaben für Ziffern, sondern dieselben lateinischen Ziffern [0-9]. Buchstaben können durch Ziffern ersetzt werden, dies ist jedoch eine seltene Verwendung und für spezielle Begriffe reserviert. Ich würde nicht erwarten, dass ein Regex-Parser mit כ"ג יורדי סירה übereinstimmt (wobei כ"ג 23 ersetzt). Wie aus der Antwort von Sina Iravanian hervorgeht, erscheinen hebräische Buchstaben nicht als gültige Übereinstimmungen für \ d.
Yuval Adam
7
Das Portieren von Westons Code nach Java ergibt: - Regex \ d dauerte 00: 00: 00.043922 Ergebnis: 4912/10000 - Regex [0-9] nahm 00: 00: 00.073658 Ergebnis: 4912/10000 167% des ersten - Regex [ 0123456789] nahm 00: 00: 00.085799 Ergebnis: 4912/10000 195% der ersten
Lunchbox

Antworten:

1565

\dÜberprüft alle Unicode-Ziffern, [0-9]ist jedoch auf diese 10 Zeichen beschränkt. Beispielsweise sind persische Ziffern ۱۲۳۴۵۶۷۸۹ein Beispiel für Unicode-Ziffern, die mit übereinstimmen \d, aber nicht [0-9].

Mit dem folgenden Code können Sie eine Liste aller dieser Zeichen erstellen:

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

Was erzeugt:

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

Sina Iravanian
quelle
121
Hier ist eine vollständigere Liste von Ziffern, die nicht 0-9 sind: fileformat.info/info/unicode/category/Nd/list.htm
Robert McKee
8
@weston Unicode hat 17 Ebenen mit jeweils 16 Bit. Die wichtigsten Zeichen befinden sich in der Grundebene, aber einige Sonderzeichen, hauptsächlich Chinesen, befinden sich in den Ergänzungsebenen. Der Umgang mit denen in C # ist etwas nervig.
CodesInChaos
9
@RobertMcKee: Nitpick: Der vollständige Unicode-Zeichensatz besteht aus 21 Bit (17 Ebenen mit jeweils 16 Bit). Aber natürlich ist ein 21-Bit-Datentyp unpraktisch. Wenn Sie also einen 2-Potenz-Datentyp verwenden, benötigen Sie 32-Bit.
Sleske
3
Laut diesem Wikipedia-Artikel hat das Unicode-Konsortium angegeben, dass das Limit von 1.114.112 Codepunkten (0 bis 0x010FFFF) niemals geändert wird. Es verlinkt auf unicode.org, aber ich habe die Aussage dort nicht gefunden (ich habe sie wahrscheinlich nur verpasst).
Keith Thompson
14
Es wird niemals geändert werden - bis sie es ändern müssen.
Robert McKee
271

Dank an ByteBlast, dass Sie dies in den Dokumenten bemerkt haben. Ändern Sie einfach den Regex-Konstruktor:

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

Gibt neue Zeiten:

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
Weston
quelle
11
Was macht das RegexOptions.ECMAScript?
Laurent
7
Von Regular Expression Optionen : "Enable ECMAScript-konformes Verhalten für den Ausdruck."
Chrisaycock
28
@ 0xFE: Nicht ganz. Unicode-Escapezeichen sind in ECMAScript( \u1234) weiterhin gültig . Es sind "nur" die Kurzzeichen-Zeichenklassen, die ihre Bedeutung ändern (wie \d), und die Unicode-Eigenschafts- / Skript-Kurzzeichen, die verschwinden (wie \p{N}).
Tim Pietzcker
9
Dies ist keine Antwort auf das "Warum". Es ist eine Antwort "Beheben Sie die Symptome". Immer noch wertvolle Informationen.
usr
Im Allgemeinen unterstützt Regrex den Unicode-Abgleich. ECMAScript jedoch nicht. Wenn Sie also RegexOptions.ECMAScript verwenden, stimmt es nur mit den ASCII überein, dh 0-9.
lzlstyle
119

Von Bedeutet "\ d" in Regex eine Ziffer? ::

[0-9]ist nicht gleichbedeutend mit \d. [0-9]stimmt nur mit 0123456789Zeichen überein , während \dÜbereinstimmungen [0-9]und andere Ziffern, z. B. ostarabische Ziffern٠١٢٣٤٥٦٧٨٩

İsmet Alkan
quelle
49
Laut: msdn.microsoft.com/en-us/library/20bw873z.aspx If ECMAScript-compliant behavior is specified, \d is equivalent to [0-9].
Benutzer 12345678
2
huh, irre ich mich oder dieser satz aus dem link sagt das gegenteil. "\ d entspricht einer beliebigen Dezimalstelle. Es entspricht dem regulären Ausdrucksmuster \ p {Nd}, das die Standard-Dezimalstellen 0-9 sowie die Dezimalstellen einer Reihe anderer Zeichensätze enthält."
İsmet Alkan
3
@ByteBlast danke, mit dem Konstruktor: var rex = new Regex(regex, RegexOptions.ECMAScript);macht sie alle in Bezug auf die Leistung so gut wie nicht zu unterscheiden.
Weston
2
oh trotzdem, danke an alle. Diese Frage stellte sich für mich als großartiges Lernen heraus.
İsmet Alkan
3
Bitte kopieren Sie nicht nur Antworten von anderen Fragen. Wenn es sich bei der Frage um ein Duplikat handelt, kennzeichnen Sie sie als solche.
BoltClock
20

Als Ergänzung zur Top-Antwort von Sina Iravianian finden Sie hier eine .NET 4.5-Version (da nur diese Version die UTF16-Ausgabe (siehe die ersten drei Zeilen) unterstützt) seines Codes unter Verwendung des gesamten Bereichs der Unicode-Codepunkte. Aufgrund des Mangels an angemessener Unterstützung für höhere Unicode-Flugzeuge ist es vielen Menschen nicht bewusst, immer nach den oberen Unicode-Flugzeugen zu suchen und diese einzuschließen. Trotzdem enthalten sie manchmal einige wichtige Zeichen.

Aktualisieren

Da \dNicht-BMP-Zeichen in Regex nicht unterstützt werden (danke xanatos ), hier eine Version, die die Unicode-Zeichendatenbank verwendet

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

Die folgende Ausgabe ergibt:

DecimalDigitNumber 0123456789٠١٢٣٤٥٦٧٨٩۰۱۲۳۴۵۶۷۸۹߀߁߂߃߄߅߆߇߈߉०१२३४५६७८

Briefnummer

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

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

Sebastian
quelle
Das Traurige ist, dass die Win32-Konsole keine
Sebastian
4
Wenn ich mich recht erinnere, werden in .NET leider Regexkeine Nicht-BMP-Zeichen unterstützt. Am Ende ist es also nutzlos, mit einem regulären Ausdruck nach Zeichen> 0xffff zu suchen.
Xanatos
-1

\ d überprüft alle Unicodes, während [0-9] auf diese 10 Zeichen beschränkt ist. Wenn nur 10 Ziffern, sollten Sie verwenden. Andere empfehle ich die Verwendung von \ d , Weil weniger geschrieben wird.

Dengkai
quelle