Reputation: 121
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
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
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
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
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
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
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
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