Najszybszy sposób sprawdzenia, czy ciąg zawiera tylko cyfry


178

Znam kilka sposobów, jak to sprawdzić. regex, int.parse, tryparse, zapętlenie.

czy ktoś może mi powiedzieć, jaki jest najszybszy sposób sprawdzenia?

wystarczy tylko SPRAWDZIĆ, a nie trzeba faktycznie analizować.

to nie jest to samo pytanie co: Jak rozpoznać, czy ciąg jest liczbą?

nie chodzi tylko o to, jak zidentyfikować. ale o tym, jaka jest najszybsza metoda.


2
bez pomiaru zgaduję, że int.tryparse
kenny

Prawdopodobnie pętla napisana w asemblerze, która odczytuje fragmenty danych o rozmiarze rodzimego słowa do rejestru, a następnie wykonuje sprawdzenie zakresu na każdym bajcie w rejestrze.
aroth

35
po prostureturn str.All(Char.IsDigit);
Mohsen

2
int.TryParse nie sprawdza, czy string zawiera tylko cyfry! Ciągi takie jak „-13” (ze znakiem minus i spacjami) zostaną pomyślnie przeanalizowane.
aleyush

Odpowiedzi:


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

    return true;
}

Prawdopodobnie będzie to najszybszy sposób na zrobienie tego.


16
Jest teżchar.IsDigit()
Keith,

30
@Keith IsDigitwraca truepo około trzysta kolejnych znaków. W tym cyfry dziesiętne o pełnej szerokości 0123... (powszechne w Chinach i Japonii) oraz cyfry z innych kultur, np. ০১২௧௨௩௪꘤꘥꘦꘧꘨I wiele innych.
CodesInChaos

62
jeśli kogoś to obchodzi, z pewnością można to zredukować do jednej linii ->return str.All(c => c >= '0' && c <= '9');
Jonesopolis

18
Może po prostu to zrobić za: return str.All(char.IsDigit);. Brawo dla grup metodycznych!
Icemanind

11
Zwróć uwagę, że pusty ciąg nie jest liczbą ważną.
Danon,

64

Oto kilka testów porównawczych opartych na analizach 1000000 tego samego ciągu:

Zaktualizowano dla releasestatystyk:

IsDigitsOnly: 384588
TryParse:     639583
Regex:        1329571

Oto kod, wygląda na to, że IsDigitsOnly jest szybsze:

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

Oczywiście warto zauważyć, że TryParse dopuszcza początkowe / końcowe białe znaki, a także symbole specyficzne dla kultury. Jest również ograniczona długością sznurka.


Analiza liczby zdecydowanie zajmuje więcej czasu niż tylko sprawdzanie każdej cyfry, ponieważ wykonujesz konwersję podstawową.

1
Nawiasem mówiąc, 1000 analiz tej samej struny nie powinno zająć prawie wcale czasu, znacznie poniżej czasu, w którym naturalny szum sprawia, że ​​wyniki są nieistotne. Spodziewałbym się, że będę musiał przeanalizować to milion razy, aby uzyskać przydatne czasy.
Jon Skeet,

Downvoted ponieważ benchmark jest sposób zbyt krótkie, aby być użyteczna i że nie zauważyć, że metoda daje złą odpowiedź nawet na próbce testujesz. Próbka ciąg jest składają się wyłącznie z cyfr, ale dlatego, że jest zbyt długi dla int, TryParse wraca fałszywe.
Jon Skeet,

Jest dużo bliżej z 1m. Ach dobra uwaga co do długości, przegapiłem to.
TheCodeKing,

3
Ooh, z kompilacją / o +, jest teraz ponad 5 razy szybsza niż int.TryParse. Żeby sprawdzić, czy nie korzystasz z debuggera, prawda?
Jon Skeet

59

Możesz to po prostu zrobić za pomocą LINQ

return str.All(char.IsDigit);

  1. .All zwraca true dla pustych ciągów i wyjątek dla ciągów pustych.
  2. char.IsDigit jest prawdziwe dla wszystkich znaków Unicode.

3
char.IsDigit odpowiada licznym cyfrom Unicode z różnych lokalizacji (zobacz fileformat.info/info/unicode/category/Nd/list.htm ). Ponadto Twoja odpowiedź używa LINQ, więc jest mało prawdopodobne, aby była to najszybsza metoda. Może to być jednak wystarczające w większości zastosowań.
Stephen Holt

1
@StephenHolt Tak, masz rację, zdaję sobie sprawę, że niekoniecznie jest to najszybsze, ale prawdopodobnie najłatwiejsze do napisania.
Uday

Tak, słuszna uwaga. Napisałem również podobną odpowiedź (patrz poniżej) kilka lat temu, chociaż moja wersja właśnie przetestowała, czy znak był między „0” a „9”, aby wyeliminować znaki z innych lokalizacji. Będzie to zależeć od dokładnych wymagań.
Stephen Holt

34

Char ma już IsDigit (char c), który robi to:

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

Możesz to po prostu zrobić:

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

Jeśli chcesz sprawdzić cyfry Unicode, nie powinieneś rzucać znaku na int tylko dlatego, że jest to zły kod, nawet dla szybszego kodu.
user823959

1
@ user823959: Nie jestem pewien, co masz na myśli. Char.IsDigit jest częścią mscorelib: msdn.microsoft.com/en-us/library/0t641e58.aspx
flayn

Gerhard przepraszam, mój błąd.
user823959

Jest to bardziej zwięzłe niż zapętlanie, ale na moim komputerze ponad milion iteracji, pętla for jest zawsze szybsza ~ 1,5 raza
Sudhanshu Mishra

23

Może być około 20% szybsze, używając tylko jednego porównania na chari forzamiast 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; 
}

Kod używany do testowania (zawsze profilowany, ponieważ wyniki zależą od sprzętu, wersji, kolejności itp.):

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

Wyniki dla Intel i5-3470 @ 3,2GHz, VS 2015 .NET 4.6.1 Tryb wydania i włączone optymalizacje:

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 

Każdy, kto ma ochotę skorzystać z krótszych metod, powinien to zauważyć


14

Jeśli obawiasz się wydajności, nie używaj int.TryParseani Regex- napisz własną (prostą) funkcję ( DigitsOnlylub DigitsOnly2poniżej, ale nie DigitsOnly3 - wydaje się, że LINQ wiąże się ze znacznym narzutem).

Należy również pamiętać, że int.TryParsenie powiedzie się, jeśli ciąg jest zbyt długi, aby „zmieścić się” int.

Ten prosty punkt odniesienia ...

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

    }

}

... daje następujący wynik ...

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

11

Funkcja z pustą walidacją:

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

10

Podoba mi się Linq i aby wyjść z pierwszego niedopasowania, możesz to zrobić

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

8

Prawdopodobnie najszybszym sposobem jest:

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

Uwaga: zwróci True w przypadku, gdy ciąg jest pusty, co jest niepoprawne (jeśli nie uważasz, że pusty jest prawidłowy numer / cyfra)


7

To powinno działać:

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

int.Parse lub int.TryParse nie zawsze będzie działać, ponieważ ciąg może zawierać więcej cyfr, które może pomieścić int.

Jeśli zamierzasz to zrobić, sprawdź więcej niż raz, przydatne jest użycie skompilowanego wyrażenia regularnego - za pierwszym razem zajmuje to więcej czasu, ale później jest znacznie szybsze.


3
to źle, zwraca prawdę, jeśli jest choćby jedna cyfra. chociaż spełniony pomysł jest niesamowity.
Nahum,

1
Jest to zdecydowanie najwolniejsza metoda, ale jest to najlepsze rozwiązanie oparte na nieznanej wielkości ciągu. Jak już wspomniano, wyrażenie regularne również wymaga poprawek.
TheCodeKing,

6

Możesz to zrobić w jednowierszowej instrukcji LINQ. OK, zdaję sobie sprawę, że to niekoniecznie jest najszybsze, więc technicznie nie odpowiadam na pytanie, ale prawdopodobnie najłatwiej jest napisać:

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

4
str.All(char.IsDigit)jest jeszcze łatwiejszy do napisania, ale oczywiście nie jest odpowiednikiem twojego kodu.
CodesInChaos

Próbowałem to przetestować: pastebin.com/PuWBp9n1 w wersji bez debuggera oczywiście ... i wydaje się, że jest to DUŻO szybciej. @Jon Skeet czy możesz podać jakieś informacje? str.All (c => c> = '0' && c <= '9') wydaje się DUŻO szybszy niż IsDigit
Nahum

1
@NahumLitvin IsDigitobsługuje Unicode. Więc w zależności od tego, jakie kompromisy czasowe wybrał Microsoft podczas implementacji, sprawdzenie może być dość kosztowne. Zakładam, że przekazuje do kodu natywnego, to przejście również może być dość kosztowne.
CodesInChaos

@CodesInChaos, kiedy powiedziałeś, że to „nie jest odpowiednikiem mojego kodu”, poszedłem sprawdzić, co jeszcze może pasować i okazało się, że cyfry w innych lokalizacjach (np. Arabskim) będą się zgadzać w Twojej wersji. Myślę, że jest to coś, co OP musiałby rozważyć, czy takie cyfry są prawidłowe, czy nie. Podczas wykonywania int.TryParse myślę, że nie zaakceptowałoby to ciągów zawierających takie znaki.
Stephen Holt,

LINQ to najwolniejszy sposób osiągnięcia czegokolwiek. Jeśli chcesz zastosować ogólną regułę do kodowania, załóż, że im wyższy poziom i funkcjonalność oferuje coś, tym wolniej.
TravisO

3

To może nadejść bardzo późno !, ale jestem pewien, że to komuś pomoże, tak jak pomogło mi.

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

1

Możesz spróbować użyć wyrażeń regularnych, testując ciąg wejściowy, aby zawierał tylko cyfry (0-9) przy użyciu .IsMatch(string input, string pattern)metody w języku C #.

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

pozdrowienia


3
Cześć Jason i witaj w Stackoverflow. Dziękuję za odpowiedź, ale zauważ, że pytanie dotyczyło najszybszej drogi. Wyrażenia regularne są stosunkowo wolne, co zostało omówione w innych odpowiedziach.
Nahum

1

to zadziała idealnie, jest wiele innych sposobów, ale to zadziała

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

Wypróbuj ten kod:

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

Czy możesz wyjaśnić, dlaczego Twoje rozwiązanie jest lepsze niż te już dostarczone?
Noel Widmer

Ponieważ kolejność uruchamiania tego kodu [o (1)] jest mniejsza niż innych [o (n)]
H. Borsipour

Byłbym bardzo zaskoczony, gdyby Convert.ToInt32działał szybciej niż o (n). Czy masz jakieś dowody na poparcie tego założenia?
BDL

1
może być szybsze, jeśli str jest faktycznie liczbą, ale prawdopodobnie byłoby wolniejsze w przypadku wyjątku. Nie odpowiada również na pytanie, ponieważ nie zadziała, jeśli str jest liczbą większą niż int.MaxValue.
Tomer Wolberg

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

Chociaż ten kod może rozwiązać problem, powinieneś dodać wyjaśnienie, dlaczego / jak to działa. I proszę wyjaśnij, dlaczego uważasz, że ten kod jest lepszy niż te już dostarczone.
BDL

1
Ponadto: Twój kod zwraca True dla pustych ciągów.
BDL


-3

Bardzo sprytny i łatwy sposób na wykrycie, że ciąg zawiera tylko cyfry lub nie, jest następujący:

string s = "12fg";

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

Warunek if jest niepotrzebny, więc są dwie instrukcje powrotu, możesz po prostu zwrócić s.All ... Ale są też inne problemy, takie jak puste ciągi.
alvarlagerlof
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.