find all numbers in a string with a NSScanner
Asked Answered
M

2

7

I used the below code to extract numbers from inputString using NSScanner

NSString *inputString = @"Dhoni7 notout at183*runs in 145andhehit15four's and10sixers100";
NSString *numberString;
NSArray *elements = [inputString componentsSeparatedByString:@" "];
for (int i=0; i<[elements count];i++)
{
    NSScanner *scanner = [NSScanner scannerWithString:[elements objectAtIndex:i]];
    NSCharacterSet *numbers = [NSCharacterSet characterSetWithCharactersInString:@"1234567890"];

    // Throw away characters before the first number.
    [scanner scanUpToCharactersFromSet:numbers intoString:NULL];

    // Collect numbers.
    [scanner scanCharactersFromSet:numbers intoString:&numberString];

    // Result.
    int number = [numberString integerValue];
    if (number != 0)
    {
        NSLog(@"%d\n",number);
        numberString = nil;
    }
}

My expected output is 7 183 145 15 10 100

but the output I am getting is 7 183 145 10

It just extracts first occurrence of a number from each word. Eg: if its Dho7ni89 it just detects the 7 and doesn't detect 89. I would be really happy if someone helps me figure out a way to fix this.

Markitamarkka answered 24/5, 2015 at 15:3 Comment(1)
@matt Absolutely correct. Why would someone down vote this question? It's got a reasonable attempt at the answer at least it's not just another "write this for me" question.Edmanda
S
8

Your mistake is simple: you have started by making a false assumption. You artificially separate the string into words:

NSArray *elements = [inputString componentsSeparatedByString:@" "];
for (int i=0; i<[elements count];i++) {
    // scan for one number
}

Thus, that is exactly what happens. Each word is scanned for one number, once. If a word contains two numbers, the second number is never scanned - because that is not what you said to do.

The solution is to stop making that false assumption. Don't separate the string into words at all! Just keep repeating the process:

* scan up to a number
* scan the number

...until you reach the end of the scanner's string (isAtEnd).

Spotty answered 24/5, 2015 at 15:13 Comment(2)
Of course I could actually write out the code for you, but that would not leave you with any challenge! I prefer to teach you how to think about the problem and let you solve it. You are so close to having all the code you need already...!Spotty
@Spotty : That worked perfectly for me. Thanks a bunch. It was very thoughtful of you to help me figure it out myself :) All I did was, removing the string separation code and replacing for loop with while ([scanner isAtEnd] == NO)Markitamarkka
H
10

Matt was a little bit faster. Why do you split your string in the beginning?
A little code rearrangement will yield the desired result:

NSScanner *scanner = [NSScanner scannerWithString:@"Dhoni7 notout at183*runs in 145andhehit15four's and10sixers100"];
NSCharacterSet *numbers = [NSCharacterSet characterSetWithCharactersInString:@"1234567890"];

while (true) {
    // Throw away characters before a number
    [scanner scanUpToCharactersFromSet:numbers intoString:NULL];

    // Read the number
    NSString *numberString;
    if ([scanner scanCharactersFromSet:numbers intoString:&numberString]) {
        long number = [numberString integerValue];
        NSLog(@"%ld\n",number);
    } else {
        break;
    }
}

Outputting

7 183 145 15 10 100

NOTE
You could also use

NSCharacterSet *numbers = [NSCharacterSet decimalDigitCharacterSet];

to filter out the numbers. I find this a bit neater than listing the chars. It will however include, for example, the decimal digits of the Indic scripts and Arabic as well.

Hylozoism answered 24/5, 2015 at 15:15 Comment(5)
Actually this is even better than what I said, because instead of testing isAtEnd, you use the fact that scan... returns a BOOL. - It is scary writing what looks like an endless loop with while(true), but that in fact is the correct approach.Spotty
Yes, i know that while (true) is not the best but I do not think there is a way of putting both scanning operations in the condition. You could add a bool-value indicating that it has not found a number any more though...Hylozoism
Isn't zero a number too? You should use strtol() to detect the number as that allows for error-detection; something [NSString integerValue] does not.Edmanda
well, i need to clean the code a bit, that is true, however a 0 will be detected.Hylozoism
That's much better and my suggestion of using strtol() doesn't really help in this situation, given you know there can only be numbers in numberString.Edmanda
S
8

Your mistake is simple: you have started by making a false assumption. You artificially separate the string into words:

NSArray *elements = [inputString componentsSeparatedByString:@" "];
for (int i=0; i<[elements count];i++) {
    // scan for one number
}

Thus, that is exactly what happens. Each word is scanned for one number, once. If a word contains two numbers, the second number is never scanned - because that is not what you said to do.

The solution is to stop making that false assumption. Don't separate the string into words at all! Just keep repeating the process:

* scan up to a number
* scan the number

...until you reach the end of the scanner's string (isAtEnd).

Spotty answered 24/5, 2015 at 15:13 Comment(2)
Of course I could actually write out the code for you, but that would not leave you with any challenge! I prefer to teach you how to think about the problem and let you solve it. You are so close to having all the code you need already...!Spotty
@Spotty : That worked perfectly for me. Thanks a bunch. It was very thoughtful of you to help me figure it out myself :) All I did was, removing the string separation code and replacing for loop with while ([scanner isAtEnd] == NO)Markitamarkka

© 2022 - 2024 — McMap. All rights reserved.