Вчера я сделал комментарий, где кто-то использовал [0123456789]
в регулярном выражении, а не [0-9]
или \d
. Я сказал, что, вероятно, более эффективно использовать спецификатор диапазона или цифры, чем набор символов.
Я решил проверить это сегодня и, с удивлением обнаружил, что (по крайней мере, в двигателе с регулярным выражением С#) \d
оказывается менее эффективным, чем любой из двух других, которые, похоже, не сильно отличаются друг от друга. Вот мой тестовый вывод более 10000 случайных строк из 1000 случайных символов с 5077, фактически содержащим цифру:
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
Это сюрприз для меня по двум причинам:
\d
хуже, чем [0-9]
. Есть ли больше \d
, чем просто сокращение для [0-9]
?Вот тестовый код:
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;
}
}
}
\d
проверяет все цифры в Unicode, а [0-9]
ограничивается этими 10 символами. Например, цифры Persian, ۱۲۳۴۵۶۷۸۹
, являются примером цифр Unicode, которые сопоставляются с \d
, но не [0-9]
.
Вы можете создать список всех таких символов, используя следующий код:
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());
Что генерирует:
+012345678901234567890123456789 ߀߁߂߃߄߅߆߇߈߉012345678 9 01২345678901234567890123456789 ୦୧୨୩୪୫୬୭୮୯ 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 ᠐᠑᠒᠓᠔᠕᠖᠗᠘᠙ ᥆᥇᥈᥉᥊᥋᥌᥍᥎᥏ ᧐᧑᧒᧓᧔᧕᧖᧗᧘᧙ ᭐᭑᭒᭓᭔᭕᭖᭗᭘᭙᮰᮱᮲᮳᮴᮵᮶᮷᮸᮹᱀᱁᱂᱃᱄᱅᱆᱇᱈᱉᱐᱑᱒᱓᱔᱕᱖᱗᱘᱙꘠꘡꘢꘣꘤꘥꘦꘧꘨꘩꣐꣑꣒꣓꣔꣕꣖꣗꣘꣙꤀꤁꤂꤃꤄꤅꤆꤇꤈꤉꩐꩑꩒꩓꩔꩕꩖꩗꩘꩙0123456789
UInt16
? Кроме того, эта ссылка от Роберта показывает символы выше \uFFFF
которые меня удивили, я думал, что это всего лишь 16 бит. Так что ваш код не найдет их, например, \ u104A0.
Кредит ByteBlast для уведомления об этом в документах. Просто изменив конструктор регулярных выражений:
var rex = new Regex(regex, RegexOptions.ECMAScript);
Дает новые тайминги:
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
RegexOptions.ECMAScript
?
Из Имеет ли значение "\d" в регулярном выражении цифра?:
[0-9]
не эквивалентен\d
.[0-9]
соответствует только символам0123456789
, а\d
соответствует[0-9]
и другим цифровым символам, например восточным арабским цифрам٠١٢٣٤٥٦٧٨٩
If ECMAScript-compliant behavior is specified, \d is equivalent to [0-9].
В дополнение к верхнему ответу от Сина Иревианян, вот версия .NET 4.5 (так как только эта версия поддерживает выход UTF16, cf первые три строки) его кода, используя полный диапазон кодовых точек Unicode. Из-за отсутствия надлежащей поддержки для более высоких плоскостей Unicode многие люди не знают, что нужно всегда проверять и включать верхние юникодные плоскости. Тем не менее они иногда содержат некоторые важные символы.
Обновление
Так как \d
не поддерживает символы без BMP в regex (спасибо xanatos), здесь версия, использующая базу данных символов 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();
}
Выход следующего выхода:
DecimalDigitNumber 012345678901234567890123456789߀߁߂߃߄߅߆߇߈߉012345678 9 01২345678901234567890123456789 ୦୧୨୩୪୫୬୭୮୯ 0123456789012345678901234567890123456789 ෦෧෨෩෪෫෬෭෮෯ 012345678901234567890123456789012345678901234567890123456789 ᠐᠑᠒᠓᠔᠕᠖᠗᠘᠙ ᥆᥇᥈᥉᥊᥋᥌᥍᥎᥏ ᧐᧑᧒᧓᧔᧕᧖᧗᧘᧙᪀᪁᪂᪃᪄᪅᪆᪇᪈᪉᪐᪑᪒᪓᪔᪕᪖᪗᪘᪙ ᭐᭑᭒᭓᭔᭕᭖᭗᭘᭙᮰᮱᮲᮳᮴᮵᮶᮷᮸᮹᱀᱁᱂᱃᱄᱅᱆᱇᱈᱉᱐᱑᱒᱓᱔᱕᱖᱗᱘᱙꘠꘡꘢꘣꘤꘥꘦꘧꘨꘩꣐꣑꣒꣓꣔꣕꣖꣗꣘꣙꤀꤁꤂꤃꤄꤅꤆꤇꤈꤉꧐꧑꧒꧓꧔꧕꧖꧗꧘꧙꧰꧱꧲꧳꧴꧵꧶꧷꧸꧹꩐꩑꩒꩓꩔꩕꩖꩗꩘꩙ ꯰꯱꯲꯳꯴꯵꯶꯷꯸꯹0123456789?????????? ?????????? ?????????? ?????????? ?????????? ?????????? ?????????? ?????????? ?????????? ?????????? ?????????? ?????????? ????????????????????????????????????????????????????????????
LetterNumber
ᛮᛯᛰⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾⅿↀↁↂↅↆↇↈ〇〡〢〣〤〥〦〧〨〩〸〹〺ꛦꛧꛨꛩꛪꛫꛬꛭꛮꛯ ????????????????????????????????????????????????????? ?? ????? ???????????????????????????????????????????????????????????????????????????????????????????????????????????????
OtherNumber ²³¹¼½¾৴৵৶.৸৹ ୲୳୴୵୶୷ ௰௱௲ ౸౹౺౻౼౽౾ ൰൱൲൳൴൵ ༪ ༫ ༬ ༭ ༮ ༯ ༰ ༱ ༲ ༳ ፩፪፫፬፭፮፯፰፱፲፳፴፵፶፷፸፹፺፻፼ ៰ ៱ ៲ ៳ ៴ ៵ ៶ ៷ ៸ ៹ ᧚⁰⁴⁵⁶⁷⁸⁹₀₁₂₃₄₅₆₇₈₉⅐⅑⅒⅓⅔⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞⅟↉①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇⒈⒉⒊⒋⒌⒍⒎⒏⒐⒑⒒⒓⒔⒕⒖⒗⒘⒙⒚⒛⓪⓫⓬⓭⓮⓯⓰⓱⓲⓳⓴⓵⓶⓷⓸⓹⓺⓻⓼⓽⓾⓿❶❷❸❹❺❻❼❽❾❿➀➁➂➃➄➅➆➇➈➉➊➋➌➍➎➏➐➑➒➓ ⳽ ㆒ ㆓ ㆔ ㆕ ㈠㈡㈢㈣㈤㈥㈦㈧㈨㈩㉈㉉㉊㉋㉌㉍㉎㉏㉑㉒㉓㉔㉕㉖㉗㉘㉙㉚㉛㉜㉝㉞㉟㊀㊁㊂㊃㊄㊅㊆㊇㊈㊉㊱㊲㊳㊴㊵㊶㊷㊸㊹㊺㊻㊼㊽㊾㊿꠰꠱꠲꠳꠴꠵?????????????????????????????????? ???????????????????????????????????????????? ???? ???????? ??????? ????????? ????? ?????? ???????????????????????????????????????????????????????????????? ???????? ?? ??? ????? ???????? ???????? ??????? ?????? ??????????????????????????????? ???????????????????? ????? ??????????????? ?? ????????? ????????????????????????? ??????????????????????
Regex
не поддерживает не-BMP символы. Так что в конце проверка символов> 0xffff с регулярным выражением бесполезна.
\ d проверяет все Unicode, а [0-9] ограничивается этими 10 символами. Если всего 10 цифр, вы должны использовать. Другие рекомендую использовать \d, потому что писать меньше.
\d
будет менее эффективным, потому что он должен быть преобразован для сравнения.
Например, если я хочу, чтобы Regex находил IP-адреса, я предпочел бы \d
, чем [0123456789]
или даже [0-9]
представлять любую цифру.
Вообще говоря, в моем использовании Regex, функция, если важнее скорости.
O(1)
, а не O(n)
, (где n
- длина входной строки.) Короче говоря, влияние в лучшем случае минимально.
\d
имеет дело с локалями. Например, иврит использует буквы для цифр.