frank schmidt
frank schmidt

Reputation: 121

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

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.

Upvotes: 0

Views: 3374

Answers (7)

chux
chux

Reputation: 154080

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

Upvotes: 0

Narcisse Doudieu Siewe
Narcisse Doudieu Siewe

Reputation: 649

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

Upvotes: 0

Weather Vane
Weather Vane

Reputation: 34585

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

Upvotes: 1

Gil Hamilton
Gil Hamilton

Reputation: 12357

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

Upvotes: 1

barak manos
barak manos

Reputation: 30136

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

Upvotes: 1

Alex Reynolds
Alex Reynolds

Reputation: 96967

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;

Upvotes: 0

Clifford
Clifford

Reputation: 93514

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.

Upvotes: 1

Related Questions