Der schnellste Weg, um zu überprüfen, ob die Zeichenfolge nur Ziffern enthält


177

Ich weiß ein paar Möglichkeiten, wie ich das überprüfen kann. regex, int.parse,tryparse , Looping.

Kann mir jemand sagen, was der schnellste Weg ist, um zu überprüfen?

die Notwendigkeit ist nur zu überprüfen ohne dass eine tatsächliche Analyse .

Dies ist nicht die gleiche Frage wie: Wie identifiziere ich, ob eine Zeichenfolge eine Zahl ist?

Die Frage ist nicht nur, wie man sich identifiziert. aber was ist die schnellste Methode.


2
ohne nur zu messen würde ich int.tryparse
kenny

Wahrscheinlich eine in Assembly geschriebene Schleife, die Datenblöcke in nativer Wortgröße aus der Zeichenfolge in ein Register liest und dann eine Bereichsprüfung für jedes Byte im Register durchführt.
aroth

35
einfachreturn str.All(Char.IsDigit);
Mohsen

2
int.TryParse prüft nicht, ob der String nur Ziffern enthält! Zeichenfolgen wie "-13" (mit Minuszeichen und Leerzeichen) werden erfolgreich analysiert.
Aleyush

Antworten:


258
bool IsDigitsOnly(string str)
{
    foreach (char c in str)
    {
        if (c < '0' || c > '9')
            return false;
    }

    return true;
}

Wird wahrscheinlich der schnellste Weg sein, dies zu tun.


16
Es gibt auchchar.IsDigit()
Keith

30
@Keith IsDigitgibt truefür ungefähr dreihundert weitere Zeichen zurück. Einschließlich Dezimalstellen voller Breite 0123... (in China und Japan üblich) und Ziffern aus anderen Kulturen, z ০১২௧௨௩௪꘤꘥꘦꘧꘨. B. und vieles mehr.
CodesInChaos

62
Wenn es jemanden interessiert, kann dies sicherlich auf einen return str.All(c => c >= '0' && c <= '9');
Einzeiler

17
Sie können dies auch einfach tun : return str.All(char.IsDigit);. Hurra für Methodengruppen!
Icemanind

11
Bitte beachten Sie, dass eine leere Zeichenfolge keine gültige Nummer ist.
Danon

63

Hier sind einige Benchmarks, die auf 1000000 Parses derselben Zeichenfolge basieren:

Für releaseStatistiken aktualisiert :

IsDigitsOnly: 384588
TryParse:     639583
Regex:        1329571

Hier ist der Code, sieht aus wie IsDigitsOnly ist schneller:

class Program
{
    private static Regex regex = new Regex("^[0-9]+$", RegexOptions.Compiled);

    static void Main(string[] args)
    {
        Stopwatch watch = new Stopwatch();
        string test = int.MaxValue.ToString();
        int value;

        watch.Start();
        for(int i=0; i< 1000000; i++)
        {
            int.TryParse(test, out value);
        }
        watch.Stop();
        Console.WriteLine("TryParse: "+watch.ElapsedTicks);

        watch.Reset();
        watch.Start();
        for (int i = 0; i < 1000000; i++)
        {
            IsDigitsOnly(test);
        }
        watch.Stop();
        Console.WriteLine("IsDigitsOnly: " + watch.ElapsedTicks);

        watch.Reset();
        watch.Start();
        for (int i = 0; i < 1000000; i++)
        {
            regex.IsMatch(test);
        }
        watch.Stop();
        Console.WriteLine("Regex: " + watch.ElapsedTicks);

        Console.ReadLine();
    }

    static bool IsDigitsOnly(string str)
    {
        foreach (char c in str)
        {
            if (c < '0' || c > '9')
                return false;
        }

        return true;
    }
}

Natürlich ist es erwähnenswert, dass TryParse führende / nachfolgende Leerzeichen sowie kulturspezifische Symbole zulässt. Es ist auch auf die Länge der Saite begrenzt.


Das Parsen einer Zahl dauert definitiv länger als nur das Überprüfen jeder Ziffer, da Sie eine Basiskonvertierung durchführen.

1
1000 Parses derselben Saite sollten übrigens fast keine Zeit in Anspruch nehmen , auch in der Zeit, in der natürliches Rauschen die Ergebnisse unbedeutend macht. Ich würde erwarten, dass ich es millionenfach analysieren muss , um nützliche Timings zu erhalten.
Jon Skeet

Herabgestuft, weil der Benchmark viel zu kurz ist, um nützlich zu sein, und Sie nicht bemerkt haben, dass Ihre Methode selbst für die Probe, die Sie testen, die falsche Antwort liefert . Die Probe Zeichenfolge ist , die nur aus Ziffern, sondern weil es für eine zu lange ist int, TryParse zurückkehrt falsch.
Jon Skeet

Mit 1m ist es viel näher. Ah guter Punkt über die Länge, das habe ich verpasst.
TheCodeKing

3
Ooh, mit / o + beim Kompilieren ist es jetzt mehr als fünfmal schneller als int.TryParse. Nur um zu überprüfen, ob Sie nicht im Debugger ausgeführt werden, oder?
Jon Skeet

57

Sie können dies einfach mit LINQ tun

return str.All(char.IsDigit);

  1. .All Gibt true für leere Zeichenfolgen und eine Ausnahme für Nullzeichenfolgen zurück.
  2. char.IsDigit gilt für alle Unicode-Zeichen.

3
char.IsDigit stimmt mit zahlreichen Unicode-Ziffern aus verschiedenen Ländereinstellungen überein (siehe fileformat.info/info/unicode/category/Nd/list.htm ). Außerdem verwendet Ihre Antwort LINQ, sodass es unwahrscheinlich ist, dass dies der schnellste Weg ist. Für die meisten Fälle kann dies jedoch ausreichend sein.
Stephen Holt

1
@StephenHolt Ja, Sie haben Recht, mir ist klar, dass dies nicht unbedingt der schnellste ist, aber wahrscheinlich der am einfachsten zu schreibende.
Uday

Ja, fairer Punkt. Ich habe vor einigen Jahren auch eine ähnliche Antwort geschrieben (siehe unten), obwohl meine Version gerade getestet hat, ob das Zeichen zwischen '0' und '9' liegt, um Zeichen aus anderen Gebietsschemas zu entfernen. Das hängt von den genauen Anforderungen ab.
Stephen Holt

34

Das Zeichen hat bereits eine IsDigit (Zeichen c), die dies tut:

 public static bool IsDigit(char c)
    {
      if (!char.IsLatin1(c))
        return CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.DecimalDigitNumber;
      if ((int) c >= 48)
        return (int) c <= 57;
      else
        return false;
    }

Sie können dies einfach tun:

var theString = "839278";
bool digitsOnly = theString.All(char.IsDigit);

Wenn Sie nach Unicode-Ziffern suchen möchten, sollten Sie ein Zeichen nicht in ein int umwandeln, nur weil es ein schlechter Code ist, selbst für schnelleren Code.
user823959

1
@ user823959: Ich bin nicht sicher, was du meinst. Char.IsDigit ist Teil der mscorelib: msdn.microsoft.com/en-us/library/0t641e58.aspx
flayn

Gerhard Entschuldigung, mein Fehler.
user823959

Dies ist prägnanter als eine Schleife, aber auf meinem Computer ist die Schleife über eine Million Iterationen immer um das 1,5-fache schneller
Sudhanshu Mishra,

22

Kann etwa 20% schneller sein, wenn nur ein Vergleich pro charund foranstelle von foreach:

bool isDigits(string s) 
{ 
    if (s == null || s == "") return false; 

    for (int i = 0; i < s.Length; i++) 
        if ((s[i] ^ '0') > 9) 
            return false; 

    return true; 
}

Zum Testen verwendeter Code (immer Profil, da die Ergebnisse von Hardware, Versionen, Reihenfolge usw. abhängen):

static bool isDigitsFr(string s) { if (s == null || s == "") return false; for (int i = 0; i < s.Length; i++) if (s[i] < '0' || s[i] > '9') return false; return true; }
static bool isDigitsFu(string s) { if (s == null || s == "") return false; for (int i = 0; i < s.Length; i++) if ((uint)(s[i] - '0') > 9) return false; return true; }
static bool isDigitsFx(string s) { if (s == null || s == "") return false; for (int i = 0; i < s.Length; i++) if ((s[i] ^ '0') > 9) return false; return true; }
static bool isDigitsEr(string s) { if (s == null || s == "") return false; foreach (char c in s) if (c < '0' || c > '9') return false; return true; }
static bool isDigitsEu(string s) { if (s == null || s == "") return false; foreach (char c in s) if ((uint)(c - '0') > 9) return false; return true; }
static bool isDigitsEx(string s) { if (s == null || s == "") return false; foreach (char c in s) if ((c ^ '0') > 9) return false; return true; }
static void test()
{
    var w = new Stopwatch(); bool b; var s = int.MaxValue + ""; int r = 12345678*2; var ss = new SortedSet<string>(); //s = string.Concat(Enumerable.Range(0, 127).Select(i => ((char)i ^ '0') < 10 ? 1 : 0));
    w.Restart(); for (int i = 0; i < r; i++) b = s.All(char.IsDigit); w.Stop(); ss.Add(w.Elapsed + ".All .IsDigit"); 
    w.Restart(); for (int i = 0; i < r; i++) b = s.All(c => c >= '0' && c <= '9'); w.Stop(); ss.Add(w.Elapsed + ".All <>"); 
    w.Restart(); for (int i = 0; i < r; i++) b = s.All(c => (c ^ '0') < 10); w.Stop(); ss.Add(w.Elapsed + " .All ^"); 
    w.Restart(); for (int i = 0; i < r; i++) b = isDigitsFr(s); w.Stop(); ss.Add(w.Elapsed + " for     <>");
    w.Restart(); for (int i = 0; i < r; i++) b = isDigitsFu(s); w.Stop(); ss.Add(w.Elapsed + " for     -");
    w.Restart(); for (int i = 0; i < r; i++) b = isDigitsFx(s); w.Stop(); ss.Add(w.Elapsed + " for     ^");
    w.Restart(); for (int i = 0; i < r; i++) b = isDigitsEr(s); w.Stop(); ss.Add(w.Elapsed + " foreach <>");
    w.Restart(); for (int i = 0; i < r; i++) b = isDigitsEu(s); w.Stop(); ss.Add(w.Elapsed + " foreach -");
    w.Restart(); for (int i = 0; i < r; i++) b = isDigitsEx(s); w.Stop(); ss.Add(w.Elapsed + " foreach ^");
    MessageBox.Show(string.Join("\n", ss)); return;
}

Ergebnisse für Intel i5-3470 bei 3,2 GHz, VS 2015 .NET 4.6.1 Freigabemodus und aktivierte Optimierungen:

time    method          ratio
0.7776  for     ^       1.0000 
0.7984  foreach -       1.0268 
0.8066  foreach ^       1.0372 
0.8940  for     -       1.1497 
0.8976  for     <>      1.1543 
0.9456  foreach <>      1.2160 
4.4559  .All <>         5.7303 
4.7791  .All ^          6.1458 
4.8539  .All. IsDigit   6.2421 

Beachten Sie Folgendes, wenn Sie versucht sind, die kürzeren Methoden zu verwenden


14

Wenn Sie sich Gedanken über die Leistung machen, verwenden Sie weder int.TryParsenoch Regex- schreiben Sie Ihre eigene (einfache) Funktion ( DigitsOnlyoder DigitsOnly2darunter, aber nicht DigitsOnly3 - LINQ scheint einen erheblichen Overhead zu verursachen).

Beachten Sie auch, dass dies int.TryParsefehlschlägt, wenn die Zeichenfolge zu lang ist, um "hinein zu passen" int.

Dieser einfache Maßstab ...

class Program {

    static bool DigitsOnly(string s) {
        int len = s.Length;
        for (int i = 0; i < len; ++i) {
            char c = s[i];
            if (c < '0' || c > '9')
                return false;
        }
        return true;
    }

    static bool DigitsOnly2(string s) {
        foreach (char c in s) {
            if (c < '0' || c > '9')
                return false;
        }
        return true;
    }

    static bool DigitsOnly3(string s) {
        return s.All(c => c >= '0' && c <= '9');
    }

    static void Main(string[] args) {

        const string s1 = "916734184";
        const string s2 = "916734a84";

        const int iterations = 1000000;
        var sw = new Stopwatch();

        sw.Restart();
        for (int i = 0 ; i < iterations; ++i) {
            bool success = DigitsOnly(s1);
            bool failure = DigitsOnly(s2);
        }
        sw.Stop();
        Console.WriteLine(string.Format("DigitsOnly: {0}", sw.Elapsed));

        sw.Restart();
        for (int i = 0; i < iterations; ++i) {
            bool success = DigitsOnly2(s1);
            bool failure = DigitsOnly2(s2);
        }
        sw.Stop();
        Console.WriteLine(string.Format("DigitsOnly2: {0}", sw.Elapsed));

        sw.Restart();
        for (int i = 0; i < iterations; ++i) {
            bool success = DigitsOnly3(s1);
            bool failure = DigitsOnly3(s2);
        }
        sw.Stop();
        Console.WriteLine(string.Format("DigitsOnly3: {0}", sw.Elapsed));

        sw.Restart();
        for (int i = 0; i < iterations; ++i) {
            int dummy;
            bool success = int.TryParse(s1, out dummy);
            bool failure = int.TryParse(s2, out dummy);
        }
        sw.Stop();
        Console.WriteLine(string.Format("int.TryParse: {0}", sw.Elapsed));

        sw.Restart();
        var regex = new Regex("^[0-9]+$", RegexOptions.Compiled);
        for (int i = 0; i < iterations; ++i) {
            bool success = regex.IsMatch(s1);
            bool failure = regex.IsMatch(s2);
        }
        sw.Stop();
        Console.WriteLine(string.Format("Regex.IsMatch: {0}", sw.Elapsed));

    }

}

... ergibt folgendes Ergebnis ...

DigitsOnly: 00:00:00.0346094
DigitsOnly2: 00:00:00.0365220
DigitsOnly3: 00:00:00.2669425
int.TryParse: 00:00:00.3405548
Regex.IsMatch: 00:00:00.7017648

10

Ich mag Linq und um es bei der ersten Nichtübereinstimmung zu beenden, können Sie dies tun

string str = '0129834X33';
bool isAllDigits = !str.Any( ch=> ch < '0' || ch > '9' );

8

Der wahrscheinlich schnellste Weg ist:

myString.All(c => char.IsDigit(c))

Hinweis: Es wird True zurückgegeben, falls Ihre Zeichenfolge leer ist, was falsch ist (wenn Sie leer nicht als gültige Zahl / Ziffer betrachten).


8

Funktion mit leerer Validierung:

public static bool IsDigitsOnly(string str)
  {             
        return !string.IsNullOrEmpty(str) && str.All(char.IsDigit);
  }

7

Das sollte funktionieren:

Regex.IsMatch("124", "^[0-9]+$", RegexOptions.Compiled)

int.Parseoder int.TryParsefunktioniert nicht immer, da die Zeichenfolge möglicherweise mehr Ziffern enthält, die ein Int enthalten kann.

Wenn Sie diese Überprüfung mehr als einmal durchführen, ist es nützlich, einen kompilierten regulären Ausdruck zu verwenden. Beim ersten Mal dauert es länger, aber danach ist es viel schneller.


3
Dies ist falsch. Es gibt true zurück, wenn es nur eine Ziffer gibt. obwohl die konforme Idee fantastisch ist.
Nahum

1
Dies ist bei weitem die langsamste Methode, aber die beste Lösung basierend auf unbekannter Stringgröße. Wie bereits erwähnt, muss der Regex auch angepasst werden.
TheCodeKing

6

Sie können dies in einer einzeiligen LINQ-Anweisung tun. OK, mir ist klar, dass dies nicht unbedingt der schnellste ist, also beantworte ich die Frage technisch nicht, aber es ist wahrscheinlich am einfachsten zu schreiben:

str.All(c => c >= '0' && c <= '9')

4
str.All(char.IsDigit)ist noch einfacher zu schreiben, entspricht aber natürlich nicht Ihrem Code.
CodesInChaos

Ich habe versucht, dies zu testen: pastebin.com/PuWBp9n1 bei Veröffentlichung natürlich kein Debugger ... und es scheint WAYYYY schneller. @ Jon Skeet können Sie einen Einblick geben? str.All (c => c> = '0' && c <= '9') scheint viel schneller als IsDigit
Nahum

1
@NahumLitvin IsDigitunterstützt Unicode. Abhängig davon, welche Zeit-Speicher-Kompromisse Microsoft bei der Implementierung gewählt hat, kann die Überprüfung recht teuer sein. Ich gehe davon aus, dass es an nativen Code weitergeleitet wird, dass der Übergang auch ziemlich teuer sein kann.
CodesInChaos

@CodesInChaos Als Sie sagten, es sei "nicht gleichbedeutend mit meinem Code", habe ich überprüft, was sonst noch übereinstimmen könnte, und es stellte sich heraus, dass Ziffern in anderen Gebietsschemas (z. B. Arabisch) in Ihrer Version übereinstimmen würden. Ich denke, es ist etwas, das OP berücksichtigen müsste, ob solche Ziffern gültig sind oder nicht. Wenn Sie int.TryParse ausführen, werden meiner Meinung nach keine Zeichenfolgen akzeptiert, die solche Zeichen enthalten.
Stephen Holt

LINQ ist der langsamste Weg, um etwas zu erreichen. Wenn Sie eine Pauschalregel auf die Codierung anwenden möchten, gehen Sie davon aus, dass die höhere Ebene und Funktionalität, die etwas bietet, umso langsamer ist.
TravisO

2

Das könnte sehr spät kommen!, Aber ich bin sicher, es wird jemandem helfen, wie es mir geholfen hat.

        private static bool IsDigitsOnly(string str)
        {
            return str.All(c => c >= '0' && c <= '9');
        }

1

Sie können versuchen, reguläre Ausdrücke zu verwenden, indem Sie die Eingabezeichenfolge mithilfe der .IsMatch(string input, string pattern)Methode in C # so testen, dass sie nur Ziffern (0-9) enthält .

using System;
using System.Text.RegularExpression;

public namespace MyNS
{
    public class MyClass
    {
        public void static Main(string[] args)
        {
             string input = Console.ReadLine();
             bool containsNumber = ContainsOnlyDigits(input);
        }

        private bool ContainOnlyDigits (string input)
        {
            bool containsNumbers = true;
            if (!Regex.IsMatch(input, @"/d"))
            {
                containsNumbers = false;
            }
            return containsNumbers;
        }
    }
}

Grüße


3
Hallo Jason und willkommen bei Stackoverflow. Vielen Dank für Ihre Antwort. Beachten Sie jedoch, dass es sich bei der Frage um den schnellsten Weg handelt. Reguläre Ausdrücke sind relativ langsam. Dies wurde in anderen Antworten diskutiert.
Nahum

1

Dies wird perfekt funktionieren, es gibt viele andere Möglichkeiten, aber dies würde funktionieren

bool IsDigitsOnly(string str)
    {
        if (str.Length > 0)//if contains characters
        {
            foreach (char c in str)//assign character to c
            {
                if (c < '0' || c > '9')//check if its outside digit range
                    return false;
            }
        }else//empty string
        {
            return false;//empty string 
        }

        return true;//only digits
    }

0

Versuchen Sie diesen Code:

bool isDigitsOnly(string str)
{
   try
   {
      int number = Convert.ToInt32(str);
      return true;
   }
   catch (Exception)
   {
      return false;
   }
}

Können Sie erklären, warum Ihre Lösung besser ist als die bereits bereitgestellten?
Noel Widmer

Weil die zeitliche Reihenfolge der Ausführung dieses Codes [o (1)] geringer ist als bei anderen [o (n)]
H. Borsipour

Ich wäre sehr überrascht, wenn ich Convert.ToInt32schneller als o (n) laufen würde. Haben Sie Beweise, die diese Annahme stützen?
BDL

1
Es könnte schneller sein, wenn str tatsächlich eine Zahl ist, aber im Falle von Exeption wäre es wahrscheinlich langsamer. Außerdem wird die Frage nicht beantwortet, da sie nicht funktioniert, wenn str eine Nummer größer als int.MaxValue ist.
Tomer Wolberg

-2
public bool CheckforDigits(string x)
{    
    int tr;  
    return x.All(r=> int.TryParse(r.ToString(), out tr));
}

Obwohl dieser Code das Problem möglicherweise löst, sollten Sie eine Erklärung hinzufügen, warum / wie es funktioniert. Und erklären Sie bitte, warum Sie der Meinung sind, dass dieser Code besser ist als die bereits bereitgestellten.
BDL

1
Zusätzlich: Ihr Code gibt True für leere Zeichenfolgen zurück.
BDL


-3

Sehr clevere und einfache Möglichkeit, Ihre Zeichenfolge zu erkennen, enthält nur Ziffern oder nicht:

string s = "12fg";

if(s.All(char.IsDigit))
{
   return true; // contains only digits
}
else
{
   return false; // contains not only digits
}

Die if-Bedingung ist nicht erforderlich, ebenso wie zwei return-Anweisungen. Sie können einfach die s.All ... zurückgeben. Es gibt jedoch auch andere Probleme, z. B. bei leeren Zeichenfolgen.
alvarlagerlof
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.