Good analysis, Danny.<br>It's mainly due to the linear time required by string-append and maybe string-length. If there had been a constant-time string-cons, we would have had linear time.<br>This can still be done by converting to lists:<br>
(define (string->mformat-3 str n filler)<br> (let loop ([l (string->list str)] [len (string-length str)])<br> (if (>= len n)<br> (list->string l)<br> (loop (cons filler l) (add1 len)))))<br>
<br>
There is still an overhead though, probably from the conversion from a big list to a big string:<br>trial 10000000<br>2:<br>cpu time: 348 real time: 347 gc time: 36<br>3:<br>cpu time: 4957 real time: 4959 gc time: 3740<br>
<br>Actually, version 3 is in fact in O(n log n) because of the add1 (maybe also because of len, depending on how it is treated on the loop call).<br>Version 2 is also probably in O(n log n), but maybe there can be some nice compilation trick to prevent that.<br>
Although we are of course bounded by the addressable memory space (but Racket programs are supposed to be independent of that, I think).<br><br>Laurent<br><br><div class="gmail_quote">On Fri, Sep 28, 2012 at 10:38 PM, Danny Yoo <span dir="ltr"><<a href="mailto:dyoo@hashcollision.org" target="_blank">dyoo@hashcollision.org</a>></span> wrote:<br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div class="im">> It is also possible to use build-string, without using explicit recursion:<br>
> (define (string->mformat str n filler)<br>
> (string-append<br>
> (build-string (max 0 (- n (string-length str)))<br>
> (lambda (x) filler))<br>
> str))<br>
<br>
<br>
</div>Hi Laurent,<br>
<br>
I like this one much more because it won't have the quadratic behavior<br>
that the first solution will exhibit on sufficiently large n.<br>
<br>
<br>
It's neat to see how the two compare in terms of time. Here's a small<br>
test program:<br>
<br>
###########################<br>
#lang racket<br>
<br>
;; string->mformat-1: string number char -> string<br>
(define (string->mformat-1 str n filler)<br>
<div class="im"> (if (equal? (string-length str) n)<br>
str<br>
</div> (string->mformat-1 (string-append (string filler) str)<br>
n<br>
filler)))<br>
<br>
;; string->mformat-2: string number char -> string<br>
(define (string->mformat-2 str n filler)<br>
<div class="im"> (string-append<br>
(build-string (max 0 (- n (string-length str)))<br>
(lambda (x) filler))<br>
str))<br>
<br>
<br>
</div>;; test: number -> void<br>
;; Just show how the two functions compare.<br>
(define (test n)<br>
(printf "trial ~a\n" n)<br>
(displayln "1:")<br>
(time (void (string->mformat-1 "foo" n #\0)))<br>
(displayln "2:")<br>
(time (void (string->mformat-2 "foo" n #\0)))<br>
(newline))<br>
<br>
<br>
(for ([n '(10 100 1000 10000 100000)])<br>
(test n))<br>
###########################<br>
<br>
<br>
<br>
We'll compare the two versions to see how they perform. Here's what I<br>
see on my machine:<br>
<br>
<br>
###########################<br>
trial 10<br>
1:<br>
cpu time: 0 real time: 0 gc time: 0<br>
2:<br>
cpu time: 0 real time: 0 gc time: 0<br>
<br>
trial 100<br>
1:<br>
cpu time: 0 real time: 0 gc time: 0<br>
2:<br>
cpu time: 0 real time: 0 gc time: 0<br>
<br>
trial 1000<br>
1:<br>
cpu time: 0 real time: 0 gc time: 0<br>
2:<br>
cpu time: 0 real time: 0 gc time: 0<br>
<br>
trial 10000<br>
1:<br>
cpu time: 141 real time: 152 gc time: 78<br>
2:<br>
cpu time: 1 real time: 0 gc time: 0<br>
<br>
trial 100000<br>
1:<br>
cpu time: 20866 real time: 22001 gc time: 8670<br>
2:<br>
cpu time: 2 real time: 2 gc time: 0<br>
###########################<br>
<br>
<br>
We can see that the performance of string->mformat-1 starts to falter<br>
as n grows. The string->mformat-2 function is better because it does<br>
not repeatedly use string-append on the accumulated result.<br>
</blockquote></div><br>