04. juli 2005 - 20:39Der er
30 kommentarer og 2 løsninger
Korrekt identificering af et tal i en streng
Jeg er i en situation hvor jeg skal trække et tal ud af en streng. Strengen er f.eks. "next_17", hvor jeg så bruger en lille substring til at trække "17" ud og lave det om til et tal.
Problemet er at der er mange tilfælde hvor der står noget andet efter "next", f.eks. "next_sytten".
Jeg ved ikke på forhånd om det er et ord eller et tal der står i strengen. Jeg bruger lige nu en dum løsning: Jeg trækker ordet ud og parser til en int. Hvis parsingen mislykkes, catcher jeg den exception der kommer, og så ved jeg at det ikke var et tal der stod.
Det ved jeg tilfældigvis er en rigtig dårlig løsning med hensyn til performance.
Og nu til spørgsmålet.
Hvad er den bedste løsning til at identificere om et ord er et tal eller ej?
string pattern = @"\d+"; Regex regex = new Regex(pattern); Match match = regex.Match(strengMedTal);
if (match.Success) { Console.WriteLine("Og tallet er: " + match.Groups[0].Value); } else { Console.WriteLine("Der var ikke noget tal i strengen."); } } } }
Jeg ville lige for sjov sammenligne regex'en med en mere manuel nybegynder-metode:
char[] chars = str.ToCharArray(); foreach (char c in chars) { if (c != '0' && c != '1' && c != '2' && c != '3' && c != '4' && c != '5' && c != '6' && c != '7' && c != '8' && c != '9') { return false; } }
return true;
Regex er godt nok 5 gange hurtigere end trycatch. Men dumme-metoden er 10-15 gange hurtigeree end regex!
Det er rigtigt. Der bruges en substring-metode bagefter. Jeg har nu taget højde for det i testen, så regexen ikke bruger substring-metode når det viser sig at være et tal, men det gør de simple metoder i stedet. Det ikke nogen betydelig forskel i performance.
Tak for tippet med IsDigit. Det gør ingen forskel i performance, men det er da lidt pænere. Tror I ikke jeg med rimelig sikkerhed kan antage at der ikke er nogle betydeligt bedre måder at gøre det på?
Jeg venter spændt - er altid interesseret i noget med performance.
Synes dog at spørgsmålet har ændret lidt karakter undervejs. Jeg troede at du gerne ville vide hvad tallet var, og nu viser det sig at du bare vil vide om der er et tal eller ej. I det tilfælde vil et pattern som ”\d” være mere velegnet end ”\d+” - læs ”hurtigere”.
try-catch: 622,5507 char.IsDigit: 32,6413 c != '0': 30,7388 regex: 202,585 erik regex: 108,2931
Det havde en betydelig forskel. Regexen er en hel del hurtigere nu, men stadig langsommere end de ultrasimple. Nu kan man begynde at overveje at bruge regex for den ekstra fleksibilitet, selvom den er lidt langsommere.
Undskyld, det kan godt være mit spørgsmål ledte fokus lidt væk fra den egentlige pointe. Det scenarie hvor jeg skal bruge det, har jeg faktisk allerede hentet 17/sytten ud fra strengen.¨Scenariet ser nogenlunde sådan ud:
string str = variable;
if (IsNumber(variable)) { // do something with variable } else { // do something else with variable }
Du skrev på et tidspunkt at tallet i princippet kunne være uendeligt stort, og då er der faktisk kun begyndermetoden samt regex-metoden tilbage. F.eks. kan try-catxh ikke håndtere tale som er uendeligt store.
Husk dog lige at udvid testen med et tjek som udelukker 0 på aller første plads. I regex kunne det f.eks. se sådan her ud:
public class Try1 : NumberChecker { public bool IsNumber(string s) { try { int.Parse(s); return true; } catch(FormatException) { return false; } } public string Algorithm { get { return "try catch"; } }
}
public class Try2 : NumberChecker { public bool IsNumber(string s) { for(int i = 0; i < s.Length; i++) { if(!Char.IsDigit(s[i])) { return false; } } return true; } public string Algorithm { get { return "for + IsDigit"; } } }
public class Try3 : NumberChecker { private static Regex r = new Regex("^[0-9]+$",RegexOptions.Compiled); public bool IsNumber(string s) { return r.IsMatch(s); } public string Algorithm { get { return "regex ^[0-9]+$"; } } }
public class Try4 : NumberChecker { private static Regex r = new Regex("^[\\d]+$",RegexOptions.Compiled); public bool IsNumber(string s) { return r.IsMatch(s); } public string Algorithm { get { return "regex ^[\\d]+$"; } } }
public class Try5 : NumberChecker { public bool IsNumber(string s) { for(int i = 0; i < s.Length; i++) { if(s[i] < '0' || s[i] > '9') { return false; } } return true; } public string Algorithm { get { return "for + <>"; } } }
for + IsDigit: Short number : 0,21875 Long number : 1,375 Invalid number : 0,1875 regex ^[0-9]+$: Short number : 2,328125 Long number : 4,375 Invalid number : 2,234375 regex ^[\d]+$: Short number : 2,5625 Long number : 5,765625 Invalid number : 2,375 for + <>: Short number : 0,078125 Long number : 0,234375 Invalid number : 0,0625
nielles seneste er marginalt dyrere end eriks. Den skulle se sådan ud de to kombineret, ikke?
^next_[1-9]\d*$
Men jeg forstår egentligt ikke hvorfor jeg kan bruge den til at skippe min substring. Den returnerer vel "next_1234" og ikke kun "1234". Den ved jo ikke hvilken del af regexen jeg er interesseret i.
Lige inden jeg smutter for i dag vil jeg da godt lige påpege noget. Du skrev at du havde fundet de 17. Javel, men det må du jo så have noget kode som klare, og det tager også tid. I mit oprindelige oplæg (04/07-2005 21:13:02) så klares dette faktisk som en integreret ting af løsningen. Det er vel også værd at tage med i betragtningen.
Det er godt opbygget og stærkt fokus på den perfekte "IsNumber(string)"-metode. Resultatet kan jo diskuteres. Jeg får givetvis et andet resultat fordi jeg i min testkode blander en mængde substring ind i det.
Det kan du have ret i nielle, men det lader faktisk til at det i praksis giver dårligere performance end at bruge substring og så bruge en af de andre metoder. Men det kan godt være jeg lige skal kigge min testkode ordentligt efter.
regex er næsten altid langsommere end special kode
men når du begynder at skulle have længere sekvenser som f.eks. et ord + en underscore + et tal så begynder regex at være både nemt at kode og sikkert til at undgå fejl med
Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.