\ d kém hiệu quả hơn [0-9]


1249

Tôi đã đưa ra một nhận xét ngày hôm qua về một câu trả lời mà ai đó đã sử dụng [0123456789]trong một biểu thức thông thường hơn là [0-9]hoặc \d. Tôi đã nói rằng có thể hiệu quả hơn khi sử dụng một công cụ xác định phạm vi hoặc chữ số so với một bộ ký tự.

Tôi đã quyết định kiểm tra điều đó hôm nay và phát hiện ra điều ngạc nhiên là (ít nhất là trong công cụ regex C #) có \dvẻ kém hiệu quả hơn so với hai trong số hai cái còn lại dường như không khác nhau nhiều. Đây là đầu ra thử nghiệm của tôi hơn 10000 chuỗi ngẫu nhiên gồm 1000 ký tự ngẫu nhiên với 5077 thực sự có chứa một chữ số:

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

Đó là một bất ngờ đối với tôi vì hai lý do:

  1. Tôi đã nghĩ rằng phạm vi sẽ được thực hiện hiệu quả hơn nhiều so với thiết lập.
  2. Tôi không thể hiểu tại sao \dtồi tệ hơn [0-9]. Có nhiều \dhơn chỉ đơn giản là tốc ký cho [0-9]?

Đây là mã kiểm tra:

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
Có thể \dgiao dịch với các địa phương. Ví dụ: tiếng Do Thái sử dụng các chữ cái cho các chữ số.
Barmar


37
Đây là một câu hỏi thú vị chính xác bởi vì \dkhông có nghĩa giống nhau trong các ngôn ngữ khác nhau. Trong Java, ví dụ \d, thực sự chỉ khớp 0-9
Ray Toal

17
@Barmar tiếng Do Thái không sử dụng các chữ cái cho các chữ số thông thường, thay vào đó là các chữ số Latin giống nhau [0-9]. Chữ cái có thể được thay thế cho chữ số, nhưng đây là một cách sử dụng hiếm hoi và dành riêng cho các điều khoản đặc biệt. Tôi sẽ không mong đợi một phân tích cú pháp regex để phù hợp với כ"ג יורדי סירה (với כ"ג là một substitue cho 23). Ngoài ra, như có thể thấy trong câu trả lời của Sina Iravanian, các chữ cái tiếng Do Thái không xuất hiện dưới dạng khớp hợp lệ cho \ d.
Yuval Adam

7
Chuyển mã Weston sang năng suất Java: - Regex \ d lấy kết quả 00: 00: 00.043922: 4912/10000 - Regex [0-9] lấy kết quả 00: 00: 00.073658: 4912/10000 167% của lần đầu tiên - Regex [ 0123456789] mất 00: 00: 00.085799 kết quả: 4912/10000 195% số đầu tiên
Lunchbox

Câu trả lời:


1566

\dkiểm tra tất cả các chữ số Unicode, trong khi [0-9]giới hạn ở 10 ký tự này. Ví dụ, các chữ số Ba Tư۱۲۳۴۵۶۷۸۹ , là một ví dụ về các chữ số Unicode được khớp với \d, nhưng không [0-9].

Bạn có thể tạo danh sách tất cả các ký tự như vậy bằng mã sau:

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

Tạo ra:

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


121
Dưới đây là danh sách đầy đủ hơn các chữ số không 0-9: fileformat.info/info/unicode/carget/Nd/list.htmlm
Robert McKee

8
@weston Unicode có 17 mặt phẳng với 16 bit mỗi mặt phẳng. Hầu hết các nhân vật quan trọng đều ở trong mặt phẳng cơ bản, nhưng một số nhân vật đặc biệt, chủ yếu là người Trung Quốc, nằm trong các mặt phẳng bổ sung. Đối phó với những người trong C # là một chút khó chịu.
CodeInChaos

9
@RobertMcKee: Nitpick: Bộ ký tự unicode đầy đủ thực sự là 21 bit (17 mặt phẳng mỗi bit 16 bit). Nhưng tất nhiên, kiểu dữ liệu 21 bit là không thực tế, vì vậy nếu bạn sử dụng kiểu dữ liệu có sức mạnh 2, thì đúng là bạn cần 32 bit.
sleske

3
Theo bài viết trên Wikipedia này , Hiệp hội Unicode đã tuyên bố rằng giới hạn của 1.114.112 điểm mã (0 đến 0x010FFFF) sẽ không bao giờ được thay đổi. Nó liên kết đến unicode.org, nhưng tôi không tìm thấy tuyên bố ở đó (có lẽ tôi đã bỏ lỡ nó).
Keith Thompson

14
Nó sẽ không bao giờ được thay đổi - cho đến khi họ cần thay đổi nó.
Robert McKee

271

Tín dụng cho ByteBlast để nhận thấy điều này trong các tài liệu. Chỉ cần thay đổi hàm tạo regex:

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

Cung cấp thời gian mới:

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
Làm gì RegexOptions.ECMAScript?
nguyệt quế

7
Từ các tùy chọn biểu thức chính quy : "Kích hoạt hành vi tuân thủ ECMAScript cho biểu thức."
chrisaycock

28
@ 0xFE: Không hoàn toàn. Lối thoát Unicode vẫn còn hiệu lực trong ECMAScript( \u1234). Đó là "chỉ" các lớp ký tự tốc ký thay đổi ý nghĩa (như \d) và các ký tự / thuộc tính Unicode sẽ biến mất (như \p{N}).
Tim Pietzcker

9
Đây không phải là một câu trả lời cho phần "tại sao". Đó là một câu trả lời "sửa chữa các triệu chứng". Vẫn còn thông tin giá trị.
usr

Nói chung, Regrex hỗ trợ kết hợp unicode. Nhưng ECMAScript thì không. Do đó, khi sử dụng RegexOptions.ECMAScript, nó chỉ khớp với ascii, tức là 0-9.
lzlstyle

119

Từ Liệu có phải là một chữ số trong regex có nghĩa là một chữ số không? :

[0-9]không tương đương với \d. [0-9]chỉ khớp các 0123456789ký tự, trong khi \dkhớp [0-9]và các ký tự chữ số khác, ví dụ chữ số Đông Ả Rập٠١٢٣٤٥٦٧٨٩


49
Theo: msdn.microsoft.com/en-us/l Library / 20bw873z.aspx If ECMAScript-compliant behavior is specified, \d is equivalent to [0-9].
Người dùng 12345678

2
huh, tôi sai hoặc câu này từ liên kết đang nói ngược lại. "\ d khớp với bất kỳ chữ số thập phân nào. Nó tương đương với mẫu biểu thức chính quy \ p {Nd}, bao gồm các chữ số thập phân chuẩn 0-9 cũng như các chữ số thập phân của một số bộ ký tự khác."
İsmet Alkan

3
@ByteBlast cảm ơn, sử dụng hàm tạo: var rex = new Regex(regex, RegexOptions.ECMAScript);làm cho tất cả chúng không thể phân biệt được khá nhiều về mặt hiệu suất.
weston

2
oh dù sao cũng cảm ơn mọi người Câu hỏi này hóa ra là một bài học tuyệt vời cho tôi.
İsmet Alkan

3
Xin đừng "chỉ sao chép" câu trả lời từ các câu hỏi khác. Nếu câu hỏi là một bản sao, đánh dấu nó như vậy.
BoltClock

20

Một bổ sung cho câu trả lời hàng đầu từ Sina Iravianian , đây là phiên bản .NET 4.5 (vì chỉ có phiên bản đó hỗ trợ đầu ra UTF16, cf ba dòng đầu tiên) của mã của anh ấy, sử dụng toàn bộ các điểm mã Unicode. Do thiếu sự hỗ trợ thích hợp cho các mặt phẳng Unicode cao hơn, nhiều người không biết luôn luôn kiểm tra và bao gồm các mặt phẳng Unicode phía trên. Tuy nhiên, đôi khi chúng có chứa một số nhân vật quan trọng.

Cập nhật

\dkhông hỗ trợ các ký tự không phải BMP trong regex (cảm ơn xanatos ), nên đây là phiên bản sử dụng cơ sở dữ liệu ký tự 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();
}

Mang lại đầu ra sau:

Số thập phân số 0123456789٠١٢٣٤٥٦٧٨٩۰۱۲۳۴۵۶۷۸۹߀߁߂߃߄߅߆߇߈߉०१२३४५६७८ ০১২৩৪৫৬৭৮৯੦੧੨੩੪੫੬੭੮੯૦૧૨૩૪૫૬૭૮૯ ୦୧୨୩୪୫୬୭୮୯ ௦௧௨௩௪௫௬௭௮௯౦౧౨౩౪౫౬౭౮౯೦೧೨೩೪೫೬೭೮೯൦൧൨൩൪൫൬൭൮൯ ෦෧෨෩෪෫෬෭෮෯ ᧐᧑᧒᧓᧔᧕᧖᧗᧘᧙᪀᪁᪂᪃᪄᪅᪆᪇᪈᪉᪐᪑᪒᪓᪔᪕᪖᪗᪘᪙ 𑁦𑁧𑁨𑁩𑁪𑁫𑁬𑁭𑁮𑁯 𑃰𑃱𑃲𑃳𑃴𑃵𑃶𑃷𑃸𑃹

Số thư

𐅀𐅁𐅂𐅃𐅄𐅅𐅆𐅇𐅈𐅉𐅊𐅋𐅌𐅍𐅎𐅏𐅐𐅑𐅒𐅓𐅔𐅕𐅖𐅗𐅘𐅙𐅚𐅛𐅜𐅝𐅞𐅟𐅠𐅡𐅢𐅣𐅤𐅥𐅦𐅧𐅨𐅩𐅪𐅫𐅬𐅭𐅮𐅯𐅰𐅱𐅲𐅳𐅴 𐍁𐍊 𐏑𐏒𐏓𐏔𐏕

Số khác³¹¼½¾৴৵৶৷৸৹ ³¹¼½¾৴৵৶৷৸৹ ³¹¼½¾৴৵৶৷৸৹ ³¹¼½¾৴৵৶৷৸৹𑜺𑜻 𑣪𑣫𑣬𑣭𑣮𑣯𑣰𑣱𑣲 𖭛𖭜𖭝𖭞𖭟𖭠𖭡𝍠𝍡𝍢𝍣𝍤𝍥𝍦𝍧𝍨𝍩𝍪𝍫𝍬𝍭𝍮𝍯𝍰𝍱


Điều đáng buồn là Bảng điều khiển Win32 không hiển thị các ký tự thiên văn
Sebastian

4
Nếu tôi nhớ chính xác, thật đáng buồn trong .NET Regexkhông hỗ trợ các ký tự không phải BMP. Vì vậy, cuối cùng việc kiểm tra các ký tự> 0xffff với regex là vô ích.
xanatos

-1

\ d kiểm tra tất cả Unicode, trong khi [0-9] bị giới hạn ở 10 ký tự này. Nếu chỉ có 10 chữ số, bạn nên sử dụng. Những người khác tôi khuyên bạn nên sử dụng \ d , Vì viết ít hơn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.