Search code examples
cstringstrtok

How can I split a C-string twice with strtok in C?


Assume that we have a C string

text = "0.4,0.1,-4.1#100,200,300#-32.13,23.41,100#<...>#20,25,30"

The goal is to split that string first with # and then , because I'm after each values between # and there are three separate values between ,.

The string text contains 17 elements of 3 numbers with separator , and 16 elements of #

I did try to solve this with this code.

char *min_max_bias_char;
float min_max_bias_float[3*17]; /* 3 values per each analog input channel */
for(uint8_t i = 0; i <= 16; i++) {
    if(i == 0)
        min_max_bias_char = strtok(text, DELIMITER);
    else
        min_max_bias_char = strtok(NULL, DELIMITER);
    min_max_bias_float[0 + i*3] = atoff(strtok(min_max_bias_char, ",")); /* Min value */
    min_max_bias_float[1 + i*3] = atoff(strtok(NULL, ","));              /* Max value */
    min_max_bias_float[2 + i*3] = atoff(strtok(NULL, ","));              /* Bias value */
}

Where I first split the text string text depending on # and then I take the first index of min_max_bias_char and split that on the delimiter ,.

This did not work out very well because as soon I do strtok(min_max_bias_char) then strtok forget about the min_max_bias_char = strtok(NULL, DELIMITER); statement.

Now I got the array min_max_bias_float that holds the values inside of an array {0.4,0.1,-4.1,100,200,300,-32.13,23.41,100,<...>,20,25,30}

This is the output. So how can I solve this issue? I'm trying to split string twice.


Solution

  • strtok accepts multiple delimiters, and since your data structure seems to not care whether the current element is a ',' or a '#' character (in other words, you're not building a 2d structure requiring nested looping), you can just provide a delimiter string and make one call to strtok in the loop.

    Here's a minimal example you can adapt to your environment:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(void) {
        char delimiters[] = "#,";
        char text[] = "0.4,0.1,-4.1#100,200,300#-32.13,23.41,100#20,25,30";
        int size = 3 * 4; // or 3 * 17;
        float res[size];
        res[0] = atof(strtok(text, delimiters));
    
        for (int i = 1; i < size; i++) {
            res[i] = atof(strtok(NULL, delimiters));
        }
    
        for (int i = 0; i < size; i++) {
            printf("%.2f ", res[i]);
        }
    
        puts("");
        return 0;
    }
    

    Output:

    0.40 0.10 -4.10 100.00 200.00 300.00 -32.13 23.41 100.00 20.00 25.00 30.00
    

    It's a good idea to check the return value of strtok in the above code.

    If you want to avoid strtok (there are good reasons to), there's strtok_r or write it by hand with a loop:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(void) {
        char delimiters[] = "#,";
        char text[] = "0.4,0.1,-4.1#100,200,300#-32.13,23.41,100#20,25,30";
        int size = 3 * 4; // or 3 * 17;
        float res[size];
        int res_size = 0;
        int last_index = 0;
    
        for (int i = 0, len = strlen(text); i < len; i++) {
            if (!strchr(delimiters, text[i])) {
                continue;
            }
            else if (i - last_index >= 32 || res_size >= size) {
                fprintf(stderr, "buffer size exceeded\n");
                return 1;
            }
            
            char buf[32] = {0};
            strncpy(buf, text + last_index, i - last_index);
            res[res_size++] = atof(buf);
            last_index = i + 1;
        }
    
        for (int i = 0; i < res_size; i++) {
            printf("%.2f ", res[i]);
        }
    
        puts("");
        return 0;
    }