How to convert comma separated char* to uint32_t[] array in C

3.3k Views Asked by At

I want to convert a comma separated char* to an uint32_array[] in C. Is there an easy method/routine to do that?

I already spend a lot of time on SO and found many solutions on C++, but not an C like that : Parsing a comma-delimited std::string But I think it is not a good solution to cast from char* to string to string stream to vector and work with the vector.

char input[] = "1 , 2 , 34, 12, 46, 100";

to

uint32_t output[] = { 1 , 2 , 34, 12, 46, 100 };

I would appreciate any kind of help. Thanks a lot.

7

There are 7 best solutions below

2
On BEST ANSWER

Here's a recursive algorithm that only makes a single pass. It allocates at the deepest level and fills in on the way out:

int *cvt(char *input, int *level)
{
    char *cp = strtok(input, ", ");
    if (cp == NULL) {
        /* No more separators */
        return (int *) malloc(sizeof(int) * *level);
    }

    int my_index = -1;
    int n;
    if (sscanf(cp, "%d", &n) == 1) {
        my_index = *level;
        *level += 1;
    } else {
        printf("Invalid integer token '%s'\n", cp);
    }
    int *array = cvt(NULL, level);
    if (my_index >= 0) {
        array[my_index] = n;
    }
    return array;
}

Call with:

int main(int ac, char **av)
{
    char input[] = "1, 2, bogus, 4, 8, 22, 33, 55";
    int n_array = 0;
    int *array = cvt(input, &n_array);

    int i;
    printf("Got %d members:\n", n_array);
    for (i = 0; i < n_array; ++i)
        printf("%d ", array[i]);
    printf("\n");

    return 0;
}
1
On

One method (of many):

int converted = 0 ;
char* tok = input ;
int i = 0 ;
do
{
    converted = sscanf( tok, "%d", &output[i] ) ;
    tok = strchr( tok, ',' ) + 1 ;
    i++ ;

} while( tok != NULL && converted != 0 ) ;

You could use strtok() instead of sscanf() and strchr(), but that modifies input which may be undesirable.

If the input is a stream rather than a string, then it is simpler:

int converted = 0 ;
int i = 0 ;
do
{
    converted = fscanf( fd, "%d,", &output[i] ) ;
    i++ ;

} while( !feof( fd ) && converted != 0 ) ;

I have not included any means to prevent output[i] from exceeding the bounds - you may need to consider that too.

3
On

Read through the string once to figure out how to size your array:

uint32_t n = 1;
for (uint32_t idx = 0; idx < strlen(input); idx++) {
    if (input[idx] == ',') {
        n++;
    }
}

There's a different way to do this that doesn't require reading through the string, but it requires resizing the destination array as new elements come in, which makes the code more complex. It's easy enough to read through the string once for small strings.

Make your destination array:

uint32_t* output = NULL;
output = malloc(sizeof(*output) * n);
if (!output) {
    fprintf(stderr, "Error: Could not allocate space for output array!\n");
    exit(EXIT_FAILURE);
}

Populate your array. One way to do this without clobbering the string is to keep a couple pointers to the start and end of a substring that contains the desired numeric element in the comma-separated string, and just loop over all the characters in the string:

#define MAX_LEN 13
char* start = &input[0];
char* end = &input[0];
char entry[MAX_LEN];
uint32_t entry_idx = 0;
int finished = 0; // false
do {
    end = strchr(start, ',');
    if (!end) {
        end = input + strlen(input);
        finished = 1;
    }
    memcpy(entry, start, end - start);
    entry[end - start] = '\0';
    sscanf(entry, "%u", &output[entry_idx++]);
    start = end + 1;
} while (!finished);

MAX_LEN is 13 because it is unlikely that a uint32_t will be longer than 13 digits. You can make this longer to future-proof this for computers made in the year 2100.

Be sure to free the array when you're done with it:

free(output); 
output = NULL;
0
On
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

int getsize(char* str, char* delimiters) //give the size of the final uint32_t array[]
{
  int count = 0;
  char* st = strdup(str), *t = strtok(st, delimiters);
  while(t)
  {
    count++;
    t = strtok(NULL, delimiters);
  }
  free(st);
  return count;
}

uint32_t* Char_to_Array(char *data, char* delimiters, int *siz) //siz is a pointer to get the size of the array
{
  char* st = strdup(data), *t = NULL; //dup string, strtok mage change on the passed string
  *siz = getsize(data, delimiters);
  uint32_t* buf=(uint32_t *)malloc((*siz)*4);
  t = strtok(st, delimiters); //split string by " "
  int i = 0;
  while(t)
  {
    buf[i] = atoi(t);
    t = strtok(NULL, delimiters);
    i++;
  }
  free(st);
  return buf;
}

here a test with a main function

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

int main()
{
  int getsize(char* str, char* delimiters), siz = 0, i = 0;
  uint32_t* Char_to_Array(char *data, char* delimiters, int *x);
  uint32_t* t = Char_to_Array("123, 156, 4658,7878", " ,", &siz);
  while(i<siz)
  {
    printf("array[%d] = %d\n", i, t[i]);
    i++;
  }
  free(t);
  return 0;
}
2
On

Here is one way to do it:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct
{
    int* values;
    int  count;
}
output_t;

output_t Read(char input[])
{
    int*  values = NULL;
    int   count  = 0;
    char* pch    = strtok(input,", ");

    while (pch != NULL)
    {
        values = realloc(values,(count+1)*sizeof(*values));
        values[count++] = atoi(pch);
        pch = strtok(NULL,", ");
    }

    return (output_t){values,count};
}

And here is a usage example:

void Example()
{
    char input[] = "1 , 2 , 34, 12, 46, 100";
    output_t output = Read(input);
    for (int i=0; i<output.count; i++)
        printf("%d\n",output.values[i]);
    free(output.values);
}
0
On

2 pass approach:

1) Count the number of commas and allocate an array.

2) Parse the string - look for errors.

[Late to the uint32 comma party]

#include <errno.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <inttypes.h>

typedef struct {
  uint32_t *u32;
  size_t count;
  bool error;
} CSV_32_T;

CSV_32_T CSV_to_int32_list(const char *csv) {
  CSV_32_T list = { NULL, 1, false };

  // 1st pass: Quickly go through list counting commas
  const char *p = csv;
  for (p = csv; *p; p++) {
    if (*p == ',') {
      list.count++;
    }
  }
  size_t i = 0;
  list.u32 = malloc(list.count * sizeof *list.u32);
  if (list.u32) {

    // 2nd pass: scan
    p = csv;
    for (i = 0; i < list.count; i++) {
      if (i > 0 && *p++ != ',') {
        break;
      }
      int n = 0;
      if (1 != sscanf(p, "%" SCNu32 " %n", &list.u32[i], &n)) {
        break;
      }
      p += n;
    }
  }
  if (i != list.count || *p) {
    free(list.u32);
    return (CSV_32_T ) { NULL, 0, true } ;
  }
  return list;
}

void testCSV(const char *csv) {
  CSV_32_T y = CSV_to_int32_list(csv);
  printf("%d %zu \"%s\"\n", y.error, y.count, csv);
}

int main(void) {
  testCSV("1 , 2 , 34, 12, 46, 100");
  testCSV("1  2 , 34, 12, 46, 100");
  return 0;
}
3
On

I'll throw my hat into the ring and do a single pass of the data. I estimate the required array size to be the worst case where every data is of the form "n," so two bytes per number, and resize it afterwards.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef unsigned int uint32_t;

int main (void) {
    char input[] = "1 , 2 , 34, 12, 46, 100";
    uint32_t *output, *temp;
    char *tok;
    int elements = 0;
    int len = 1 + strlen(input) / 2;            // estimate max num of elements
    output = malloc(len * sizeof(*output));
    if (output == NULL)
        exit(-1);                               // memory alloc error

    tok = strtok(input, ", ");                  // parse the string
    while (tok != NULL) {
        if (elements >= len)
            exit(-2);                           // error in length assumption
        if (1 != sscanf(tok, "%u", output + elements))
            exit(-3);                           // error in string format
        elements++;
        tok = strtok(NULL, ", ");
    }

    temp = realloc(output, elements * sizeof(*output)); // resize the array
    if (temp == NULL)
        exit(-4);                               // error in reallocating memory
    output = temp;

    for (len=0; len<elements; len++)
        printf("%u ", output[len]);
    printf("\n");
    free(output);
    return 0;
}

Program output:

1 2 34 12 46 100