Avatar billede nielsbrinch Nybegynder
04. juli 2005 - 20:39 Der 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?
Avatar billede erikjacobsen Ekspert
04. juli 2005 - 20:54 #1
regulære udtryk er sagen. Hvad er den præcise regel du har for dit tal?
Avatar billede nielle Nybegynder
04. juli 2005 - 21:13 #2
Regulære udtryk er nemlig lige sagen, og det lyder ikke som om at regelen er meget andet end "find tallet". Så måske sådan her:

using System;
using System.Text.RegularExpressions;

namespace e630359
{
    class Class1
    {
        [STAThread]
        static void Main(string[] args)
        {
            string strengMedTal = "next_17";

            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.");
            }
        }
    }
}
Avatar billede nielsbrinch Nybegynder
04. juli 2005 - 21:36 #3
Reglen er at tallet er et positivt heltal som kan være uendeligt stort, dog typisk under 1000.

Jeg har prøvet med en regex og det cirka 5 gange hurtigere end try-catch-løsningen.
Avatar billede nielsbrinch Nybegynder
04. juli 2005 - 21:39 #4
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!
Avatar billede nielle Nybegynder
04. juli 2005 - 21:46 #5
I stedet for din begyndermetode:

return char.IsDigit(c);
Avatar billede nielle Nybegynder
04. juli 2005 - 21:48 #6
Regex'en finder hele tallet. Til det, mangler der vist stadig en del kode i begyndermetoden...
Avatar billede nielsbrinch Nybegynder
04. juli 2005 - 21:55 #7
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å?
Avatar billede nielsbrinch Nybegynder
04. juli 2005 - 22:01 #8
Det tror jeg bare jeg antager. Tak for hjælpen. Koden blev lige 20 gange hurtigere.

Her er hele metoden hvis nogle skulle være interesserede:

public static bool IsNumber(string str)
{
    char[] chars = str.ToCharArray();
    foreach (char c in chars)
    {
        if (!char.IsDigit(c))
        {
            return false;
        }
    }

    return true;
}

Læg et svar, nielle
Avatar billede erikjacobsen Ekspert
04. juli 2005 - 22:04 #9
Hvis du skal gøre det mange gang i samme script, skal du kun lave den new RegEx een gang, og bruge den mange gange.
Det vil hjælpe på hastigheden.
Avatar billede nielsbrinch Nybegynder
04. juli 2005 - 22:09 #10
Det prøver jeg lige ...
Avatar billede nielle Nybegynder
04. juli 2005 - 22:12 #11
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”.
Avatar billede nielsbrinch Nybegynder
04. juli 2005 - 22:13 #12
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.
Avatar billede nielle Nybegynder
04. juli 2005 - 22:15 #13
Svar :^)
Avatar billede nielsbrinch Nybegynder
04. juli 2005 - 22:16 #14
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
}
Avatar billede nielsbrinch Nybegynder
04. juli 2005 - 22:19 #15
\d svarer kun til et enkelt tal. Jeg skal tjekke om hele strengen er et tal. Der kan godt stå 101dalmatinere, og så er det ikke et tal.
Avatar billede arne_v Ekspert
04. juli 2005 - 22:21 #16
"^[0-9]+$"

eller

"^[\d]+$"
Avatar billede nielsbrinch Nybegynder
04. juli 2005 - 22:26 #17
Så vandt regex alligevel kapløbet!

try-catch: 597,3842
char.IsDigit: 34,4389
c != '0': 35,3465
regex 1: 230,3692
erik regex: 112,0508
arnev regex: 10,3282
Avatar billede nielsbrinch Nybegynder
04. juli 2005 - 22:35 #18
arne og erik, vil I være med til at lukke spørgsmålet? Med mindre selvfølgelig I kan halvere tiden endnu et par gange :-)
Avatar billede erikjacobsen Ekspert
04. juli 2005 - 22:40 #19
Måske ... du kan jo skippe din substring: ^next_[0-9]+$
(Jeg samler slet ikke på point, tak)
Avatar billede nielsbrinch Nybegynder
04. juli 2005 - 22:42 #20
Det prøver jeg også lige. I mellemtiden kan jeg afsløre at RegexOptions.Compiled også skærer cirka 40% af
Avatar billede nielle Nybegynder
04. juli 2005 - 22:48 #21
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:

"^[1-0]\d*$"
Avatar billede nielle Nybegynder
04. juli 2005 - 22:48 #22
"^[1-9]\d*$"
Avatar billede nielsbrinch Nybegynder
04. juli 2005 - 22:53 #23
Nu kan jeg snart ikke overskue det længere :-)

Jeg havde glemt substring da jeg testede arnev's fulde regex. Jeg tror jeg har det hele med nu:

try-catch: 605,0957
simpel 1: 29,6035
simpel 2: 29,2069
regex 1: 205,116
uden new regex: 114,9171
arnev fuld regex: 36,8726
erik uden substring: 9,7671
Avatar billede arne_v Ekspert
04. juli 2005 - 22:55 #24
jeg kan ikke få regex til at være hurtigst

using System;
using System.Text.RegularExpressions;

public interface NumberChecker
{
    bool IsNumber(string s);
    string Algorithm
    {
        get;
    }
}

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 + <>";
        }
    }
}

class TestClass
{
    private const int N = 10000000;
    private static void Test(string title, string s, NumberChecker chk, bool expect)
    {
        bool res = !expect;
        long t1 = DateTime.Now.Ticks;
        for(int i = 0; i < N; i++)
        {
            res = chk.IsNumber(s);
        }
        long t2 = DateTime.Now.Ticks;
        if(res != expect)
        {
            Console.WriteLine(title + " : Error");
        }
        else
        {
            Console.WriteLine(title + " : " + (t2 - t1)/10000000.0);
        }
    }
    private static void Test(NumberChecker chk)
    {
        Console.WriteLine(chk.Algorithm + ":");
        Test("Short number", "1", chk, true);
        Test("Long number", "123456789", chk, true);
        Test("Invalid number", "ABC", chk, false);
    }
    public static void Main(string[] args)
    {
        //Test(new Try1());
        Test(new Try2());
        Test(new Try3());
        Test(new Try4());
        Test(new Try5());
    }
}

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

(lad være med at teste Try1 på false !)
Avatar billede nielsbrinch Nybegynder
04. juli 2005 - 22:58 #25
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.
Avatar billede nielle Nybegynder
04. juli 2005 - 23:03 #26
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.
Avatar billede nielsbrinch Nybegynder
04. juli 2005 - 23:04 #27
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.
Avatar billede nielsbrinch Nybegynder
04. juli 2005 - 23:06 #28
Det jeg mente var faktisk, at resultatet kan IKKE diskuteres. Der forsvandt bare et ord fra min tanke til mine fingre.
Avatar billede nielsbrinch Nybegynder
04. juli 2005 - 23:08 #29
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.
Avatar billede nielsbrinch Nybegynder
04. juli 2005 - 23:18 #30
Mange tak for interessen allesammen. Godnat.
Avatar billede arne_v Ekspert
04. juli 2005 - 23:29 #31
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
Avatar billede arne_v Ekspert
04. juli 2005 - 23:29 #32
og et svar fra mig
Avatar billede Ny bruger Nybegynder

Din løsning...

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.

Loading billede Opret Preview

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester