简体   繁体   中英

Time optimization for string formatting: pad a line to length K by adding spaces between words

I have written code to extend string to length K using spacebar symbol. My code works, but is not fast enough for lengthy strings. My tries at optimizing the code were insufficient, so any help would be appreciated.

Example:

Input:
16
i love apples
Output:
i   love  apples

The goal is to make any string process in 1 second, if K = BUFSIZE.

Here is the code:

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

#define BUFSIZE 1000005

int main(void) {
    char s = ' ';
    char str[BUFSIZE], res[BUFSIZE] = "";
    int K;
    scanf("%d\n", &K);
    fgets(str, BUFSIZE, stdin);
    int i, j = 0, len = strlen(str) - 1, maxP = 0, p = 0, c = 0, sum = 0;
    if (K == len) {
        printf("%s", str);
        return 0;
    }
    for (i = 0; i < len; i++) {
        if (str[i] == s) {
            p++;
            sum++;
        }
        else if (str[i] != s && i - 1 >= 0 && str[i - 1] == s) {
            if (p > maxP) {
                maxP = p;
                p = 0;
            }
            else {
                p = 0;
            }
            c++;
        }
    }
    i = 0;
    K -= len;
    do {
        if (str[i] == s) {
            res[j] = s;
            p++;
            j++;
            i++;
        }
        else if (str[i] != s && i - 1 >= 0 && str[i - 1] == s) {
            if (sum / c == maxP && K > 0) {
                res[j] = s;
                K--;
                j++;
            }
            if (p < maxP && K > 0) {
                sum++;
                res[j] = s;
                K--;
                j++;
                p = 0;
            }
            res[j] = str[i];
            i++;
            j++;
        }
        else {
            res[j] = str[i];
            i++;
            j++;
        }
        if (i == len) {
            i = 0;
            j = 0;
            strcpy(str, res);
            len = strlen(str);
            strcpy(res, "");
        }
    } while (K > 0);
    if (i < len) {
        while (i < len) {
            res[j] = str[i];
            i++;
            j++;
        }
    }
    printf("%s", res);
    return 0;
}

I'm running on an Android phone so I'm not posting code here, but I'll give you some hint.

First, you don't need to insert spaces one-by-one. You can create an array of strings, separate the sentence into an array of words and store them.

Now you have the number of words. You can use a very simple integer division to determine how many spaces should every "word separation" have, as well as how many remaining spaces to be added from left to right.

You've done all of them. You can now concatenate the words and add the spaces during concatenation. The whole procedure can be done in a time of O(N), where N is the length of the original string.

Most linear algorithms should be fast enough in this case. The performance is likely limited by I/O.

Here's a straightforward O(n) algorithm to pad a line to width with spaces:

  1. find number of words in the input line described by /([ ]*[^ ]*)*/ regex:

     size_t count_words(const char* str) { size_t count = 0; for ( ; ; ) { // ([ ]*[^ ]*)* for ( ; *str && *str == ' '; ++str) ; // skip separator if (!*str) break; ++count; // found word for ( ; *str && *str != ' '; ++str) ; // skip word chars } return count; } 
  2. find total number of spaces required knowing the desired line width and the number of word (nonspace) characters in the input:

     size_t count_nonspaces(const char* str) { size_t count = 0; for ( ; *str; ++str) if (*str != ' ') ++count; return count; } 
  3. it allows to find the desired number of spaces between words. If spaces can't be evenly distributed between words then the remainder is inserted from left to right by adjusting the space between words if necessary

  4. the main loop copies words from the source to destination inserting the computed number of spaces between words:

     int main(void) { const size_t max_width = 1000005; assert (max_width <= (SIZE_MAX - 2)); // read desired width, line size_t width, len; char *line; if (!(line = malloc(max_width + 2)) // '\\n' + '\\0' || !fgets(line, max_width + 2, stdin) // read 1st line || sscanf(line, "%zu", &width) != 1 || width > max_width || !fgets(line, max_width + 2, stdin) // read 2nd line || !(len = strlen(line)) // last char is always '\\n' || line[len-1] != '\\n') { exit(EXIT_FAILURE); //NOTE: let OS free memory hereafter } if (len > width) { // input line is wide enough fputs(line, stdout); // output as is exit(EXIT_SUCCESS); } // pad *line* to *width* with spaces, normalize space line[--len] = '\\0'; // chop newline const size_t word_count = count_words(line); // total number of spaces const size_t m = width - count_nonspaces(line); // number of spaces between words const size_t space_count = word_count > 1 ? m / (word_count - 1) : m; // the rest of spaces size_t rest = word_count > 1 ? m % (word_count - 1) : 0; // insert spaces between words char *str = line, *dest = malloc(width + 1); if (!dest) exit(EXIT_FAILURE); char *start = dest, *end = dest + width; for ( ; ; ) { // ([ ]*[^ ]*)* for ( ; *str == ' '; ++str) ; // skip separator // copy found word for ( ; *str && *str != ' '; ++str) *dest++ = *str; if (dest == end) { *dest = '\\0'; break; } // append spaces size_t nspaces = space_count; if (rest) { // distribute the rest of spaces: one for each word from // left to right --rest; ++nspaces; } memset(dest, ' ', nspaces); dest += nspaces; } puts(start); } 

Example :

$ cc *.c && ./a.out <<<$'16\n i love apples'
i   love  apples

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM