tags:

views:

735

answers:

4

I have the following character string:

"..1....10..20....30...40....50...80..."

and I need to extract all numbers from it into array.

What is the best way to do it in C?

+10  A: 

Perhaps the easiest way is to use the strtok() function (or strtok_r() if reentrancy is a concern):

char str[] = "..1...10...20";
char *p = strtok(str, ".");
while (p != NULL) {
    printf("%d\n", atoi(p));
    p = strtok(NULL, ".");
}

Once you have the results of calling atoi(), it should be a simple matter to save those integers into an array.

Greg Hewgill
Great I'll try that!
+4  A: 

You can use a sscanf code with suppressed assignment (%*[.]) to skip over the dots (or any other character you want), and a scanned character count code %n to advance the string pointer.

const char *s = "..1....10..20....30...40....50...80...";
int num, nc;

while (sscanf(s, "%*[.]%d%n", &num, &nc) == 1) {
 printf("%d\n", num);
 s += nc;
}
Diomidis Spinellis
ephemient
A: 

I prefer the use of strtok in a for loop. Makes it feel more natural, though the syntax looks a little weird.

char str[] = "..1....10..20....30...40....50...80..."
for ( char* p = strtok( strtok, "." ); p != NULL; p = strtok( NULL, "." ) )
{
    printf( "%d\n", atoi( p ) );
}
graham.reeds
Indeed, it looks quite weird. I would have expected something like for (p=strtok(str,","); p=strtok(NULL,"."); ), a semicolon after the first line and a declaration of p.
mweerden
Yeah - the code is written off the top of my head. Corrected.
graham.reeds
I don't think it will work to have the initialization in the test position of the for loop. This would get executed each time at loop start. I don't think that is what you want. mweerden's version is better, but I use an explicit test in the test position and move the next token fetch to the end.
tvanfosson
To clarify: (p=strtok(str,"."); p != NULL; p=strtok(NULL,"."))
tvanfosson
Like I said it was written off the top of my head - stop marking it down!
graham.reeds
titanae
A: 

Here is the correct way to do it, it is a little longer than the simplest way but it doesn't suffer from undefined behavior if the value read is out of range, works properly if the first character is not a dot, etc. You didn't specify whether the numbers could be negative so I used a signed type but only allow positive values, you can easily change this by allowing the negative sign at the top of the inner while loop. This version allows any non-digit characters to delimit integers, if you only want dots to be allowed you can modify the inner loop to skip only dots and then check for a digit.

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>

#define ARRAY_SIZE 10

size_t store_numbers (const char *s, long *array, size_t elems)
{
  /* Scan string s, returning the number of integers found, delimited by
   * non-digit characters.  If array is not null, store the first elems
   * numbers into the provided array */

  long value;
  char *endptr;
  size_t index = 0;

  while (*s)
  {
    /* Skip any non-digits, add '-' to support negative numbers */
    while (!isdigit(*s) && *s != '\0')
      s++;

    /* Try to read a number with strtol, set errno to 0 first as
     * we need it to detect a range error. */
    errno = 0;
    value = strtol(s, &endptr, 10);

    if (s == endptr) break; /* Conversion failed, end of input */
    if (errno != 0) { /* Error handling for out of range values here */ }

    /* Store value if array is not null and index is within array bounds */
    if (array && index < elems) array[index] = value;
    index++;

    /* Update s to point to the first character not processed by strtol */
    s = endptr;
  }

  /* Return the number of numbers found which may be more than were stored */
  return index;
}

void print_numbers (const long *a, size_t elems)
{
  size_t idx;
  for (idx = 0; idx < elems; idx++) printf("%ld\n", a[idx]);
  return;
}

int main (void)
{
  size_t found, stored;
  long numbers[ARRAY_SIZE];
  found = store_numbers("..1....10..20....30...40....50...80...", numbers, ARRAY_SIZE);

  if (found > ARRAY_SIZE)
    stored = ARRAY_SIZE;
  else
    stored = found;

  printf("Found %zu numbers, stored %zu numbers:\n", found, stored);
  print_numbers(numbers, stored);

  return 0;
}
Robert Gamble
First, the simple answer does not break if there is no leading dots and by definition since this is parsing a progress indicator, the input is well bounded in terms of number range. Using strtol() or strtoul() is probably an improvement over atoi(), but your solution is unnecessarily complex.
Tall Jeff
The strtok version does not work on string literals, doesn't store the numbers in an array, and uses the problematic atoi. The sscanf solution doesn't work without leading dots and has issues similar to atoi. My solution is longer but it is also a complete program with error checking and comments.
Robert Gamble