Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>A much more tidy and elegant solution (which I've called <strong>Basic Solution</strong>) is as follows:</p> <p><strong>Basic Solution</strong></p> <pre><code>char *internalRepeat(char *s, int n, size_t total) { return (n &gt; 0) ? strcat(internalRepeat(s, n - 1, total + strlen(s)), s) : strcpy(malloc(total + 1), ""); } char *repeat(char *s, int n) { return internalRepeat(s, n, 0); } </code></pre> <p>This is the beauty of recursion. The key to this solution uses recursion to incrementally build the length of the result. Parameter <code>total</code> does this (not including the NUL-terminator). When the recursion terminates, the result buffer is allocated once (including the NUL-terminator) and then we use the recursion unwinding to append each copy of <code>s</code>to the result. Basic Solution behaves as follows:</p> <ol> <li>Returns a zero-length string for any number of repetitions of an empty string.</li> <li>Returns a zero-length string for zero or negative iterations of a non-empty string.</li> <li>Returns a non-zero-length string for a non-zero-positive number of repetitions on a non-empty string.</li> </ol> <p>If you create a program based on the above functions, the following statements:</p> <pre><code>printf("Repeat \"\" 0 times: [%s]\n", repeat("", 0)); printf("Repeat \"\" 3 times: [%s]\n", repeat("", 3)); printf("Repeat \"abcde\" 0 times: [%s]\n", repeat("abcde", 0)); printf("Repeat \"abcde\" 1 times: [%s]\n", repeat("abcde", 1)); printf("Repeat \"abcde\" 4 times: [%s]\n", repeat("abcde", 4)); </code></pre> <p>will produce the following output:</p> <pre><code>Repeat "" 0 times: [] Repeat "" 3 times: [] Repeat "abcde" 0 times: [] Repeat "abcde" 1 times: [abcde] Repeat "abcde" 4 times: [abcdeabcdeabcdeabcde] </code></pre> <p><br/><br/> <strong>EDIT : Optimised Solution follows. Read on if you're interested in optimisation techniques.</strong></p> <hr> <p><br/> All the other proposals here principally run in <strong>O(<em>n^2</em>)</strong> and allocate memory at every iteration. Even though Basic Solution is elegant, uses only a single <code>malloc()</code>, and takes only two statements, surprisingly <strong>Basic Solution also has a running time of O(<em>n^2</em>)</strong>. This makes it very inefficient if string <code>s</code> is long and means that Basic Solution is no more efficient than any other proposal here.</p> <p><strong>Optimised Solution</strong></p> <p>The following is an <em>optimal</em> solution to this problem that actually runs in <strong>O(<em>n</em>)</strong>:</p> <pre><code>char *internalRepeat(char *s, int n, size_t total, size_t len) { return (n &gt; 0) ? strcpy(internalRepeat(s, n - 1, total, len), s) + len : strcpy(malloc(total + 1), ""); } char *repeat(char *s, int n) { int len = strlen(s); return internalRepeat(s, n, n * len, len) - (n * len); } </code></pre> <p>As you can see, it now has three statements and uses one more parameter, <code>len</code>, to cache the length of <code>s</code>. It recursively uses <code>len</code> to compute the position within the result buffer where the <code>n</code>'th copy of <code>s</code> will be positioned, so allowing us to replace <code>strcat()</code> with <code>strcpy()</code> for each time <code>s</code> is added to the result. This gives an <strong>actual running time</strong> of O(<em>n</em>), not O(<em>n^2</em>).</p> <p><strong>What's the difference between the Basic and Optimised solutions?</strong></p> <p>All other solutions have used <code>strcat()</code> at least <code>n</code> times on string <code>s</code> to append <code>n</code> copies of <code>s</code> to the result. This is where the problem lies, because the implementation of <code>strcat()</code> hides an inefficiency. Internally, <code>strcat()</code> can be thought of as:</p> <pre><code>strcat = strlen + strcpy </code></pre> <p>i.e., when appending, you first have to find the end of the string you're appending to <em>before</em> you can do the append itself. This hidden overhead means that, in fact, creating <code>n</code> copies of a string requires <code>n</code> length checks and <code>n</code> physical copying operations. However, the real problem lies in that for each copy of <code>s</code> we append, our result gets longer. This means that each successive length check within <code>strcat()</code> on the <em>result</em> is also getting longer. If we now compare the two solutions using "<em>number of times we have to scan or copy <code>s</code></em>" as our basis for comparison, we can see where the difference in the two solutions lies.</p> <p>For <code>n</code> copies of the string <code>s</code>, the Basic Solution performs as follows:</p> <pre><code>strlen's/iteration: 2 strcpy's/iteration: 1 Iteration | Init | 1 | 2 | 3 | 4 | ... | n | Total | ----------+------+---+---+---+---+-----+---+------------+ Scan "s" | 0 | 1 | 2 | 3 | 4 | ... | n | (n+1)(n/2) | Copy "s" | 0 | 1 | 1 | 1 | 1 | ... | 1 | n | </code></pre> <p>whereas the Optimised Solution performs like this:</p> <pre><code>strlen's/iteration: 0 strcpy's/iteration: 1 Iteration | Init | 1 | 2 | 3 | 4 | ... | n | Total | ----------+------+---+---+---+---+-----+---+------------+ Scan "s" | 1 | 0 | 0 | 0 | 0 | ... | 0 | 1 | Copy "s" | 0 | 1 | 1 | 1 | 1 | ... | 1 | n | </code></pre> <p>As you can see from the table, the Basic Solution performs (<em>n^2 + n)/2</em> scans of our string due to the built-in length check in <code>strcat()</code>, whereas the Optimised Solution always does <em>(n + 1)</em> scans. This is why the Basic Solution (and every other solution that relies on <code>strcat()</code>) performs in <em>O(n^2)</em>, whereas the Optimised Solution performs in <em>O(n)</em>.</p> <p><strong>How does O(<em>n</em>) compare to O(<em>n^2</em>) in real terms?</strong></p> <p>Running times make a <strong>huge</strong> difference when large strings are being used. As an example, let's take a string <code>s</code> of 1MB that we wish to create 1,000 copies of (== 1GB). If we have a 1GHz CPU that can scan or copy 1 byte/clock cycle, then 1,000 copies of <code>s</code> will be generated as follows:<br/><br/> <em>Note: <strong>n</strong> is taken from performance tables above, and represents a single scan of <strong>s</strong>.</em></p> <pre><code>Basic: (n + 1) * (n / 2) + n = (n ^ 2) / 2 + (3n / 2) = (10^3 ^ 2) / 2 + (3 * 10^3) / 2 = (5 * 10^5) + (1.5 * 10^2) = ~(5 * 10^5) (scans of "s") = ~(5 * 10^5 * 10^6) (bytes scanned/copied) = ~500 seconds (@1GHz, 8 mins 20 secs). Optimised: (n + 1) = 10^3 + 1 = ~10^3 (scans of "s") = ~10^3 * 10^6 (bytes scanned/copied) = 1 second (@1Ghz) </code></pre> <p>As you can see, the Optimised Solution, which completes nearly instantly, demolishes the Basic Solution which takes nearly 10 minutes to complete. However, if you think making string <code>s</code> smaller will help, this next result will horrify you. Again, on a 1GHz machine that processes 1 byte/clock cycle, we take <code>s</code> as 1KB (1 thousand times smaller), and make 1,000,000 copies (total == 1GB, same as before). This gives:</p> <pre><code>Basic: (n + 1) * (n / 2) + n = (n ^ 2) / 2 + (3n / 2) = (10^6 ^ 2) / 2 + (3 * 10^6) / 2 = (5 * 10^11) + (1.5 * 10^5) = ~(5 * 10^11) (scans of "s") = ~(5 * 10^11 * 10^3) (bytes scanned/copied) = ~50,000 seconds (@1GHz, 833 mins) = 13hrs, 53mins, 20 secs Optimised: (n + 1) = 10^6 + 1 = ~10^6 (scans of "s") = ~10^6 * 10^3 (bytes scanned/copied) = 1 second (@1Ghz) </code></pre> <p>This is a truly shocking difference. Optimised Solution performs in the same time as before as the total amount of data written is the same. However, Basic Solution stalls for over <strong>half a day</strong> building the result. This is the difference in running times between O(<em>n</em>) and O(<em>n^2</em>). </p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload