How to add a number to the middle of a string in a time-efficient way?

118 Views Asked by At

I have this:

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

int main(void){
    const char* pFilename = NULL;

    pFilename = "Something.png"

    functionX(pFilename, argX);
}

But then, I'd like to call that function inside a loop, with different filenames like "Something0.png", "Something1.png", etc

After a bit of digging, I came up with this:

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

int main(void){
    const char* pFilename = NULL;
    char buffer[4];
    char nameStd[] = "Something";
    char namePng[] = ".png";
    char nameAll[17];

    pFilename = "Something.png"

    for (i = 0; i < 100; i++) {
        snprintf(buffer, sizeof(buffer), "%d", i);
        strcat(nameAll, pFilename);
        strcat(nameAll, buffer);
        strcat(nameAll, namePng);
        functionX(nameAll, argX);
        memset(nameAll,0,strlen(nameAll));
    }
}

Well, I'm not sure this will work. And I cannot execute the code at the moment (as functionX needs specific peripherals). But even if it does work, is it really the most time-efficient way to do this?

2

There are 2 best solutions below

3
Allan Wind On BEST ANSWER

It's usually much easier to assemble parts. Either via snprintf() as suggested above, or by building the string yourself. The prefix doesn't change and you only need to add the suffix when another digit is added. It's about 60% faster than snprintf() for 1e6 strings but probably not worth the complexity:

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

// A constant expression of log10(INT_MAX) + 1
#define str_uint32_t_len sizeof(uint32_t) * CHAR_BIT * 21306 / 70777 + 2

uint32_t uint32_t_len(uint32_t i) {
    uint32_t n = !i;
    for(uint32_t j = i; j; j /= 10, n++);
    return n;
}

// s is not '\0' terminated
char *uint32_t_to_str(uint32_t u, size_t u_len, char *s) {
    for(char *p = s + u_len; s <= --p; u /= 10)
        *p = '0' + u % 10;
    return s;
}

int main() {
    char prefix[] = "Something";
    char suffix[] = ".png";

    // builds the path template "Something\0.png"
    char path[sizeof prefix + str_uint32_t_len + sizeof suffix - 2] = {0};
    strcpy(path, prefix);
    strcpy(path + sizeof prefix, suffix);

    size_t u_len[] = {1, 1};
    for(uint32_t i = 0; i < 1000000; i++) {
        u_len[1] = uint32_t_len(i);
        uint32_t_to_str(i, u_len[1], path + sizeof prefix - 1);
        if(u_len[0] < u_len[1]) {
            u_len[0] = u_len[1];
            strcpy(path + sizeof prefix + u_len[1] - 1, suffix);
        }
        printf("%s\n", path);
    }
}
4
chqrlie On

There is no point in assembling parts for performance reasons on such cases: composing the filename with snprintf is simple and efficient and far less expensive than fopen() anyway.

Here is a simplified version:

#include <stdio.h>

int functionX(const char *filename, ...) {
    //printf("%s\n", filename);
}

int main(void) {
    int argX = 1;
    for (int i = 0; i < 100; i++) {
        char filename[32];
        snprintf(filename, sizeof filename, "Something%d.png", i);
        functionX(filename, argX);
    }
    return 0;
}

The above code is the recommended approach, fast enough for most purposes. The rest of the answer is for educational purposes only.


If you absolutely want to squeeze the overhead, you could try and update the string directly:

Here is an example with a benchmark:

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

void functionX(const char *filename, ...) {
    //printf("%s\n", filename);
}

int s_update(char *s, int len) {
    for (char *p = s + len - 1;;) {
        if (++*p <= '9')
            return len;
        *p = '0';
        if (p == s) {
            memmove(s + 1, s, strlen(s) + 1);
            *s = '1';
            return len + 1;
        }
    }
}

int main(void) {
    char filename[32];
    int argX = 1;
    int repeat = 1000000;
    clock_t c0 = clock();
    for (int i = 0; i < repeat; i++) {
        snprintf(filename, sizeof filename, "Something%d.png", i);
        functionX(filename, argX);
    }
    clock_t c1 = clock();
    strcpy(filename, "Something0.png");
    char *p = strchr(filename, '0');
    int len = 1;
    for (int i = 0; i < repeat; i++) {
        functionX(filename, argX);
        len = s_update(p, len);
    }
    clock_t c2 = clock();

    printf("snprintf: %.3fms\n", (c1 - c0) * 1000.0 / CLOCKS_PER_SEC);
    printf("s_update: %.3fms\n", (c2 - c1) * 1000.0 / CLOCKS_PER_SEC);
    return 0;
}

Output:

snprintf: 81.443ms
s_update: 3.085ms

Beware that this notable performance improvement (27x faster) does not justify such clever code in most cases: for your example (100 iterations), the difference is only 30 microseconds, 0,00003 seconds, barely measurable.