\ d kurang efisien daripada [0-9]

1249

Saya membuat komentar kemarin pada jawaban di mana seseorang telah digunakan [0123456789]dalam ekspresi reguler daripada [0-9]atau \d. Saya mengatakan mungkin lebih efisien menggunakan rentang atau digit specifier daripada set karakter.

Saya memutuskan untuk mengujinya hari ini dan mengejutkan saya bahwa (di mesin C # regex setidaknya) \dtampaknya kurang efisien daripada salah satu dari dua lainnya yang tampaknya tidak jauh berbeda. Berikut ini adalah hasil pengujian saya lebih dari 10.000 string acak 1000 karakter acak dengan 5077 sebenarnya mengandung digit:

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

Ini mengejutkan saya karena dua alasan:

  1. Saya akan berpikir jangkauan akan diimplementasikan jauh lebih efisien daripada yang ditetapkan.
  2. Saya tidak mengerti mengapa \dlebih buruk dari itu [0-9]. Apakah ada lebih \ddari sekadar singkatan [0-9]?

Ini adalah kode tes:

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
sumber
178
Mungkin \dberurusan dengan lokal. Misalnya bahasa Ibrani menggunakan huruf untuk angka.
Barmar
37
Ini adalah pertanyaan yang menarik justru karena \dtidak berarti hal yang sama dalam berbagai bahasa. Di Jawa, misalnya \dmemang hanya cocok dengan 0-9
Ray Toal
17
@Barmar Hebrew tidak menggunakan huruf untuk angka secara normal, melainkan angka angka latin yang sama [0-9]. Surat dapat diganti dengan angka, tetapi ini jarang digunakan dan dicadangkan untuk istilah khusus. Saya tidak akan mengharapkan parser regex untuk mencocokkan כ"ג יורדי סירה (dengan כ"ג menjadi pengganti 23). Juga, seperti yang dapat dilihat pada jawaban Sina Iravanian, huruf Ibrani tidak muncul sebagai kecocokan yang valid untuk \ d.
Yuval Adam
7
Porting kode weston ke Jawa menghasilkan: - Regex \ d mengambil hasil 00: 00: 00.043922: 4912/10000 - Regex [0-9] mengambil hasil 00: 00: 00.073658: 4912/10000 167% dari yang pertama - Regex [ 0123456789] mengambil 00: 00: 00.085799 hasil: 4912/10000 195% pertama
Lunchbox

Jawaban:

1566

\dmemeriksa semua digit Unicode, sementara [0-9]terbatas pada 10 karakter ini. Misalnya, digit Persia۱۲۳۴۵۶۷۸۹ ,, adalah contoh digit Unicode yang cocok dengan \d, tetapi tidak [0-9].

Anda dapat membuat daftar semua karakter tersebut menggunakan kode berikut:

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

Yang menghasilkan:

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

Sina Iravanian
sumber
121
Berikut adalah daftar digit yang lebih lengkap yang bukan 0-9: fileformat.info/info/unicode/category/Nd/list.htm
Robert McKee
8
@weston Unicode memiliki 17 pesawat dengan masing-masing 16 bit. Sebagian besar karakter penting ada di bidang dasar, tetapi beberapa karakter khusus, kebanyakan Cina, ada di pesawat tambahan. Berurusan dengan orang-orang di C # agak menjengkelkan.
CodesInChaos
9
@RobertMcKee: Nitpick: Set karakter unicode penuh sebenarnya 21 bit (17 pesawat masing-masing 16 bit). Tapi tentu saja 21-bit-datatype tidak praktis, jadi jika Anda menggunakan tipe-2 power-of-2, memang benar bahwa Anda membutuhkan 32 bit.
sleske
3
Menurut artikel Wikipedia ini , Konsorsium Unicode telah menyatakan bahwa batas 1.114.112 poin kode (0 hingga 0x010FFFF) tidak akan pernah berubah. Tautan ke unicode.org, tetapi saya tidak menemukan pernyataan di sana (saya mungkin hanya melewatkannya).
Keith Thompson
14
Itu tidak akan pernah berubah - sampai mereka perlu mengubahnya.
Robert McKee
271

Penghargaan untuk ByteBlast karena memperhatikan hal ini dalam dokumen. Hanya mengubah konstruktor regex:

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

Memberikan timing baru:

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
sumber
11
Apa yang RegexOptions.ECMAScriptdilakukan?
laurent
7
Dari Opsi Ekspresi Reguler : "Aktifkan perilaku sesuai ECMAScript untuk ekspresi."
chrisaycock
28
@ 0xFE: Tidak cukup. Unicode escapes masih berlaku di ECMAScript( \u1234). "Hanya" kelas karakter steno yang mengubah makna (seperti \d) dan steno properti / skrip Unicode yang hilang (seperti \p{N}).
Tim Pietzcker
9
Ini bukan jawaban untuk bagian "mengapa". Ini adalah jawaban "memperbaiki gejala". Informasi masih berharga.
usr
Secara umum, Regrex mendukung pencocokan unicode. Tetapi ECMAScript tidak. Oleh karena itu, ketika menggunakan skrip RegexOptions.ECMAS, hanya cocok dengan ascii, yaitu 0-9.
lzlstyle
119

Dari Apakah “\ d” dalam regex berarti digit? :

[0-9]tidak setara dengan \d. [0-9]hanya cocok dengan 0123456789karakter, sedangkan \dcocok [0-9]dengan karakter digit lainnya, misalnya angka Arab Timur٠١٢٣٤٥٦٧٨٩

İsmet Alkan
sumber
49
Menurut: msdn.microsoft.com/en-us/library/20bw873z.aspx If ECMAScript-compliant behavior is specified, \d is equivalent to [0-9].
Pengguna 12345678
2
ya, apakah saya salah atau kalimat dari tautan ini mengatakan sebaliknya. "\ d cocok dengan digit desimal apa pun. Ini sama dengan pola ekspresi reguler \ p {Nd}, yang mencakup angka desimal standar 0-9 serta angka desimal dari sejumlah set karakter lainnya."
İsmet Alkan
3
@ByteBlast, terima kasih, menggunakan konstruktor: var rex = new Regex(regex, RegexOptions.ECMAScript);membuat semuanya tidak bisa dibedakan dari segi kinerja.
Weston
2
oh, terima kasih semuanya. pertanyaan ini ternyata menjadi pembelajaran yang bagus bagi saya.
İsmet Alkan
3
Tolong jangan "hanya menyalin" jawaban dari pertanyaan lain. Jika pertanyaannya adalah duplikat, beri tanda seperti itu.
BoltClock
20

Tambahan untuk jawaban teratas dari Sina Iravianian , di sini adalah versi .NET 4.5 (karena hanya versi yang mendukung output UTF16, cf tiga baris pertama) dari kodenya, menggunakan berbagai titik kode Unicode. Karena kurangnya dukungan yang tepat untuk pesawat Unicode yang lebih tinggi, banyak orang tidak menyadari selalu memeriksa dan termasuk pesawat Unicode atas. Meskipun demikian mereka terkadang mengandung beberapa karakter penting.

Memperbarui

Karena \dtidak mendukung karakter non-BMP di regex (terima kasih xanatos ), di sini versi yang menggunakan basis data karakter 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();
}

Menghasilkan output berikut:

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

LetterNumber

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

Nomor Lainnya²³¹¼½¾৴৵৶৷৸৹ ୲୳୴୵୶୷ ௰௱௲ ౸౹౺౻౼౽౾ ൰൱൲൳൴൵ ༪ ༫ ༬ ༭ ༮ ༯ ༰ ༱ ༲ ༲ 𐫫𐫬𐫭𐫮𐫯 ៰ ៱ ៴ ៴ ៴ ៵ ៷ ៷ ៷ ៸ ⳽ ㆔ ㆔ ㆕ ㆕ 𐣻𐣼𐣽𐣾𐣿 𐣻𐣼𐣽𐣾𐣿 𐤖𐤗𐤘𐤙𐤚𐤛 𐤖𐤗𐤘𐤙𐤚𐤛 𐩀𐩁𐩂𐩃𐩄𐩅𐩆𐩇 𐹠𐹡𐹢𐹣𐹤𐹥𐹦𐹧𐹨𐹩𐹪𐹫𐹬𐹭𐹮𐹯𐹰𐹱𐹲𐹳𐹴𐹵𐹶𐹷𐹸𐹹𐹺𐹻𐹼𐹽𐹾 𑁒𑁓𑁔𑁕𑁖𑁗𑁘𑁙𑁚𑁛𑁜𑁝𑁞𑁟𑁠𑁡𑁢𑁣𑁤𑁥 𑁒𑁓𑁔𑁕𑁖𑁗𑁘𑁙𑁚𑁛𑁜𑁝𑁞𑁟𑁠𑁡𑁢𑁣𑁤𑁥 𑁒𑁓𑁔𑁕𑁖𑁗𑁘𑁙𑁚𑁛𑁜𑁝𑁞𑁟𑁠𑁡𑁢𑁣𑁤𑁥 𑁒𑁓𑁔𑁕𑁖𑁗𑁘𑁙𑁚𑁛𑁜𑁝𑁞𑁟𑁠𑁡𑁢𑁣𑁤𑁥 𑁒𑁓𑁔𑁕𑁖𑁗𑁘𑁙𑁚𑁛𑁜𑁝𑁞𑁟𑁠𑁡𑁢𑁣𑁤𑁥𑇧𑇨𑇩𑇪𑇫𑇬𑇭𑇮𑇯𑇰𑇱𑇲𑇳𑇴 𑜺𑜻 𑣪𑣫𑣬𑣭𑣮𑣯𑣰𑣱𑣲 𖭛𖭜𖭝𖭞𖭟𖭠𖭡𝍠𝍡𝍢𝍣𝍤𝍥𝍦𝍧𝍨𝍩𝍪𝍫𝍬𝍭𝍮𝍯𝍰𝍱 𞣇𞣈𞣉𞣊𞣋𞣌𞣍𞣎𞣏🄀🄁🄂🄃🄄🄅🄆🄇🄈🄉🄊🄋🄌

Sebastian
sumber
Yang menyedihkan adalah bahwa Win32 Console tidak menampilkan karakter astral
Sebastian
4
Jika saya ingat dengan benar, sayangnya. NET Regextidak mendukung karakter non-BMP. Jadi pada akhirnya memeriksa karakter> 0xffff dengan regex tidak berguna.
xanatos
-1

Memeriksa semua Unicode, sementara [0-9] terbatas pada 10 karakter ini. Jika hanya 10 digit, Anda harus menggunakan. Yang lainnya saya sarankan menggunakan \ d , Karena menulis lebih sedikit.

dengkai
sumber