Sprawdzanie, czy ciąg jest numeryczny, czy nie

93

Jak możemy sprawdzić, czy ciąg składa się tylko z liczb. Pobieram podciąg z ciągu i chcę sprawdzić, czy jest to podciąg numeryczny, czy nie.

NSString *newString = [myString substringWithRange:NSMakeRange(2,3)];
Abhinav
źródło

Odpowiedzi:

241

Oto jeden sposób, który nie polega na ograniczonej precyzji próby przeanalizowania ciągu jako liczby:

NSCharacterSet* notDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
if ([newString rangeOfCharacterFromSet:notDigits].location == NSNotFound)
{
    // newString consists only of the digits 0 through 9
}

Zobacz +[NSCharacterSet decimalDigitCharacterSet]i -[NSString rangeOfCharacterFromSet:].

John Calsbeek
źródło
6
tak jest w przypadku @ "231.123", więc: // newString składa się tylko z cyfr od 0 do 9 i .znaku
Nicolas Tyler
5
NSMutableCharacterSet * digitsAndDots = [NSMutableCharacterSet decimalDigitCharacterSet]; [digitsAndDots addCharactersInString: @ "."]; NSCharacterSet * notDigitsNorDots = [digitsAndDots invertedSet]; // także dzięki za wprowadzenie "invertedSet". Nie wiedziałem o jego istnieniu
kodrut
5
uwaga: ta metoda traktuje @ "" jako liczbę; jeśli ma to zastosowanie, może być konieczne sprawdzenie, czy [newString lenght]> 0. Daj mi znać, jeśli się mylę.
markckim
2
@Supertecnoboff Wygląda na to, że to się zmieniło w IOS. Zamiast tego można to wykorzystać, aby mieć pewność:[[NSCharacterSet characterSetWithCharactersInString:@"0123456789."] invertedSet]]
Nicolas Tyler
2
@KMGorbunov Nie sądzę, aby zestaw NSCharacterSet faktycznie przechował zestaw kopii zapasowych, który zawiera każdy pojedynczy znak w zestawie. Podejrzewam, że invertedSetzwraca klasę, która zawija zestaw, w którym została utworzona, i zwraca przeciwną wartość dla wszystkich zapytań. Ale nie wiem na pewno.
John Calsbeek
33

Sugerowałbym użycie numberFromString:metody z klasy NSNumberFormatter , tak jakby numer był niepoprawny, zwróci nil; w przeciwnym razie zwróci Ci NSNumber.

NSNumberFormatter *nf = [[[NSNumberFormatter alloc] init] autorelease];
BOOL isDecimal = [nf numberFromString:newString] != nil;
Sam Symons
źródło
Czy na pewno nie zwróci prawidłowej liczby, np. Dla @ "124sbd"?
Tommy
Nie, zarówno NSNumberFormatter, jak i NSScanner zwróciłyby NOTwój ciąg. Warto również wiedzieć: powracają również YESpo liczbach wypełnionych spacjami. Przy okazji, właśnie dodałem fragment kodu do Twojej odpowiedzi, mam nadzieję, że nie masz nic przeciwko.
Regexident
NSScanner powinien zwrócić „TAK” dla tego ciągu, zgodnie z dokumentacją, po znalezieniu „prawidłowej reprezentacji liczby całkowitej”. Co więcej, zrobił to dokładnie w teście na iOS 4.3.2. Jednak NSNumberFormatter zwrócił nil.
Tommy
To się nie sprawdza ”. co jest dla mnie wymagane. Odpowiedź użytkownika @John Calsbeek działała ładnie.
Damian
Co dzieje się z łańcuchem zawierającym setki znaków, wszystkie cyfry? Czy istnieje limit utworzonego numeru NSNumber?
Biniou
12

Sprawdź poprawność za pomocą wyrażenia regularnego, według wzorca "^[0-9]+$", przy użyciu następującej metody -validateString:withPattern:.

[self validateString:"12345" withPattern:"^[0-9]+$"];
  1. Jeśli brana jest pod uwagę wartość „123.123”
    • Z wzorem "^[0-9]+(.{1}[0-9]+)?$"
  2. Jeśli dokładnie 4 cyfry, bez ".".
    • Z wzorem "^[0-9]{4}$".
  3. Jeśli cyfry są bez ".", a długość wynosi od 2 do 5.
    • Z wzorem "^[0-9]{2,5}$".
  4. Ze znakiem minus: "^-?\d+$"

Wyrażenie regularne można sprawdzić w witrynie internetowej online .

Funkcja pomocnicza jest następująca.

// Validate the input string with the given pattern and
// return the result as a boolean
- (BOOL)validateString:(NSString *)string withPattern:(NSString *)pattern
{
    NSError *error = nil;
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:&error];

    NSAssert(regex, @"Unable to create regular expression");

    NSRange textRange = NSMakeRange(0, string.length);
    NSRange matchRange = [regex rangeOfFirstMatchInString:string options:NSMatchingReportProgress range:textRange];

    BOOL didValidate = NO;

    // Did we find a matching range
    if (matchRange.location != NSNotFound)
        didValidate = YES;

    return didValidate;
}

Wersja Swift 3:

Przetestuj na placu zabaw.

import UIKit
import Foundation

func validate(_ str: String, pattern: String) -> Bool {
    if let range = str.range(of: pattern, options: .regularExpression) {
        let result = str.substring(with: range)
        print(result)
        return true
    }
    return false
}

let a = validate("123", pattern: "^-?[0-9]+")
print(a)
AechoLiu
źródło
Próbowałem w ten sposób dla Swift 3. func numberOnly(string: String) -> Int { let expression = "" let regex = NSRegularExpression.init(pattern: expression, options: .caseInsensitive) let numberOfMatches = regex.numberOfMatches(in: string, options: .reportProgress, range: NSRange.init(location: 0, length: string.characters.count)) if numberOfMatches == 0 { return Int(string)! } return 0 }I otrzymałem błądPlayground execution aborted: error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).
Mathi Arasan
Nie znam tego właściwego dla szybkiego 3. Popraw mnie, jeśli się mylę.
Mathi Arasan
Co powiesz na toreturn matchRange.location != NSNotFound;
LimeRed
a co ze znakiem minus?
Thomas
@Thomas With ^-?\d+$, zweryfikowałem to na stronie: regex101.com
AechoLiu
9

Możesz utworzyć NSScanner i po prostu zeskanować ciąg:

NSDecimal decimalValue;
NSScanner *sc = [NSScanner scannerWithString:newString];
[sc scanDecimal:&decimalValue];
BOOL isDecimal = [sc isAtEnd];

Sprawdź dokumentację NSScanner, aby uzyskać więcej metod do wyboru.

Regexident
źródło
Czy to nie mówi ci tylko, czy przynajmniej pierwszy znak jest liczbą?
Tommy
Mój błąd. Zapomniałem o ostatnim telefonie do isAtEnd.
Regexident
6

Myślę, że najłatwiejszym sposobem sprawdzenia, czy każdy znak w danym ciągu jest numeryczny, jest prawdopodobnie:

NSString *trimmedString = [newString stringByTrimmingCharactersInSet:[NSCharacterSet decimalDigitCharacterSet]];

if([trimmedString length])
{
    NSLog(@"some characters outside of the decimal character set found");
}
else
{
    NSLog(@"all characters were in the decimal character set");
}

Użyj jednej z innych metod fabryki NSCharacterSet, jeśli chcesz mieć pełną kontrolę nad dopuszczalnymi znakami.

Tommy
źródło
Podoba mi się ten sposób. Chociaż nie wydaje się to zbyt przejrzyste, jest to bardzo łatwy i przyjazny dla fundamentów sposób sprawdzania, czy w NSString znajdują się „niecyfrowe”. + Myślę, że jest znacznie szybszy niż jakikolwiek inny sposób oparty na robotach (chociaż prawdopodobnie nie patrzymy tutaj zbytnio na wydajność).
nembleton
Uważam, że stringByTrimmingCharactersInSet przycina tylko początek i koniec ciągu
Brody Robertson
@BrodyRobertson: tak. Nie ma to najmniejszego znaczenia dla tej odpowiedzi. Jak myślisz, na jakim przykładzie ten test się nie powiedzie?
Tommy
@Tommy, Po ponownej ocenie, rozumiem twoją odpowiedź, jest ona prawidłowa i zagłosuję za nią, ale musisz ją edytować, aby umożliwić mi cofnięcie
Brody Robertson
6

To pierwotne pytanie dotyczyło Objective-C, ale zostało również opublikowane wiele lat przed ogłoszeniem Swift. Jeśli więc przyjeżdżasz tutaj z Google i szukasz rozwiązania wykorzystującego Swift, proszę bardzo:

let testString = "12345"
let badCharacters = NSCharacterSet.decimalDigitCharacterSet().invertedSet

if testString.rangeOfCharacterFromSet(badCharacters) == nil {
    print("Test string was a number")
} else {
    print("Test string contained non-digit characters.")
}
TwoStraws
źródło
6

Rozwiązanie Swift 3, jeśli chcesz sprawdzić, czy ciąg ma tylko cyfry:

CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: myString))
plamkata__
źródło
1
Czysto i przyjemnie, najlepsze rozwiązanie. Podoba mi się, że to nie robi zbędnych .invertedi innych działań.
Dannie P,
4

aby było jasne, to działa dla liczb całkowitych w łańcuchach.

Oto mała kategoria pomocnicza oparta na odpowiedzi Johna powyżej:

w pliku h

@interface NSString (NumberChecking)

+(bool)isNumber:(NSString *)string;

@end

w pliku m

#import "NSString+NumberChecking.h"

@implementation NSString (NumberChecking)

+(bool)isNumber {
    if([self rangeOfCharacterFromSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet]].location == NSNotFound) {
        return YES;
    }else {
        return NO;
    }
}

@end

stosowanie:

#import "NSString+NumberChecking.h"

if([someString isNumber]) {
    NSLog(@"is a number");
}else {
    NSLog(@"not a number");
}
MrTristan
źródło
4

Rozwiązanie Swift 3 mogłoby wyglądać następująco:

extension String {

    var doubleValue:Double? {
        return NumberFormatter().number(from:self)?.doubleValue
    }

    var integerValue:Int? {
        return NumberFormatter().number(from:self)?.intValue
    }

    var isNumber:Bool {
        get {
            let badCharacters = NSCharacterSet.decimalDigits.inverted
            return (self.rangeOfCharacter(from: badCharacters) == nil)
        }
    }
}
hhamm
źródło
2

Rozszerzenie odpowiedzi @John Calsbeek i wyjaśnienie komentarzy @Jeff i @gyratory circus .

+ (BOOL)doesContainDigitsOnly:(NSString *)string
{
    NSCharacterSet *nonDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];

    BOOL containsDigitsOnly = [string rangeOfCharacterFromSet:nonDigits].location == NSNotFound;

    return containsDigitsOnly;
}

+ (BOOL)doesContainNonDigitsOnly:(NSString *)string
{
    NSCharacterSet *digits = [NSCharacterSet decimalDigitCharacterSet];

    BOOL containsNonDigitsOnly = [string rangeOfCharacterFromSet:digits].location == NSNotFound;

    return containsNonDigitsOnly;
}

Poniższe można dodać jako metody kategorii dla NSString

- (BOOL)doesContainDigitsOnly
{
    NSCharacterSet *nonDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];

    BOOL containsDigitsOnly = [self rangeOfCharacterFromSet:nonDigits].location == NSNotFound;

    return containsDigitsOnly;
}

- (BOOL)doesContainNonDigitsOnly
{
    NSCharacterSet *digits = [NSCharacterSet decimalDigitCharacterSet];

    BOOL containsNonDigitsOnly = [self rangeOfCharacterFromSet:digits].location == NSNotFound;

    return containsNonDigitsOnly;
}
Abdurrahman Mubeen Ali
źródło
2

Odpowiedź Johna Calsbeeka jest prawie poprawna, ale pomija niektóre przypadki Unicode.

Zgodnie z dokumentacjądecimalDigitCharacterSet , ten zestaw zawiera wszystkie znaki sklasyfikowane przez Unicode jako Nd. W ten sposób ich odpowiedź przyjmie m.in .:

  • (U + 0967 DEVANAGARI DIGIT ONE)
  • (U + 1811 MONGOLIAN DIGIT ONE)
  • 𝟙 (U + 1D7D9 MATEMATYCZNA PODWÓJNA CYFROWA CYFRA)

Chociaż w pewnym sensie jest to poprawne - każdy znak Ndodwzorowuje cyfrę dziesiętną - prawie na pewno nie jest to to, czego oczekiwał pytający. W chwili pisania tego tekstu istnieje 610 punktów kodowych sklasyfikowanych jakoNd , z których tylko dziesięć to oczekiwane znaki 0(od U ​​+ 0030) do9 (U + 0039).

Aby rozwiązać problem, po prostu określ dokładnie te znaki, które są dopuszczalne:

NSCharacterSet* notDigits = 
    [[NSCharacterSet characterSetWithCharactersInString:@"0123456789"] invertedSet];
if ([newString rangeOfCharacterFromSet:notDigits].location == NSNotFound)
{
    // newString consists only of the digits 0 through 9
}
ravron
źródło
1

Jeszcze inna opcja:

- (BOOL)isValidNumber:(NSString*)text regex:(NSString*)regex {
    @try {
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
        return [predicate evaluateWithObject:text];
    }
    @catch (NSException *exception) {
        assert(false); 
        return NO;
    }
}

Przykład użycia:

BOOL isValid = [self isValidNumber:@"1234" regex:@"^[0-9]+$"];
LuisCien
źródło
0

Sprawdź, czy ciąg jest liczbą. Może być pomocny

int i = [@"12.3" rangeOfCharacterFromSet: [ [NSCharacterSet characterSetWithCharactersInString:@"0123456789."] invertedSet] ].location;

if (i == NSNotFound) {
     //is a number
}
Ashok Kumar
źródło
0

Szybkie rozszerzenie:

extension NSString {
func isNumString() -> Bool {
    let numbers = NSCharacterSet(charactersInString: "0123456789.").invertedSet
    let range = self.rangeOfCharacterFromSet(numbers).location
    if range == NSNotFound {
        return true
    }
    return false
}  }
Bhavesh Patel
źródło
0

Dla Swift 3

var onlyDigits: CharacterSet = CharacterSet.decimalDigits.inverted
if testString.rangeOfCharacter(from: onlyDigits) == nil {
  // String only consist digits 0-9
}
Mert Celik
źródło
0

Jeśli masz cyfry z języków mieszanych , które używają (lub nie) formatów cyfr 0-9, będziesz musiał uruchomić wyrażenie regularne, które będzie szukało dowolnej liczby, a następnie przekonwertuj wszystkie cyfry na 0- 9 format (jeśli potrzebujesz rzeczywistej wartości):

// Will look for any language digits
let regex = try NSRegularExpression(pattern: "[^[:digit:]]", options: .caseInsensitive)
let digitsString = regex.stringByReplacingMatches(in: string,
                                                         options: NSRegularExpression.MatchingOptions(rawValue: 0),
                                                         range: NSMakeRange(0, string.count), withTemplate: "")
// Converting the digits to be 0-9 format
let numberFormatter = NumberFormatter()
numberFormatter.locale = Locale(identifier: "EN")
let finalValue = numberFormatter.number(from: digitsString)

if let finalValue = finalValue {
  let actualValue = finalValue.doubleValue
}
OhadM
źródło
0

Najłatwiejszym i najbardziej niezawodnym sposobem jest próba rzucenia, ponieważ Doublejeśli wynik jest nil- nie można go uformować w legalną liczbę.

let strings = ["test", "123", "123.2", "-123", "123-3", "123..22", ".02"]
let validNumbers = strings.compactMap(Double.init)

print(validNumbers)
// prints [123.0, 123.2, -123.0, 0.02]

Więcej informacji w dokumentacji: https://developer.apple.com/documentation/swift/double/2926277-init

Ariel
źródło
0

Simple napisał rozszerzenie ciągu:

extension String {
    var isNumeric: Bool {
        return self.rangeOfCharacter(from: CharacterSet.decimalDigits.inverted) == nil
    }
}
Umair Ali
źródło