Newsgroups: comp.lang.scheme
Path: cantaloupe.srv.cs.cmu.edu!das-news2.harvard.edu!news2.near.net!news.mathworks.com!udel!princeton!news.princeton.edu!blume
From: blume@dynamic.cs.princeton.edu (Matthias Blume)
Subject: Re: factorial algorithm
In-Reply-To: haugha@informatik.uni-tuebingen.de's message of 7 Nov 1994 12:44:11 GMT
Message-ID: <BLUME.94Nov9142708@dynamic.cs.princeton.edu>
Originator: news@hedgehog.Princeton.EDU
Sender: news@Princeton.EDU (USENET News System)
Nntp-Posting-Host: dynamic.cs.princeton.edu
Organization: Princeton University
References: <39l7er$q02@wsiserv.informatik.uni-tuebingen.de>
Date: Wed, 9 Nov 1994 19:27:08 GMT
Lines: 126

In article <39l7er$q02@wsiserv.informatik.uni-tuebingen.de> haugha@informatik.uni-tuebingen.de (Hannes Haug) writes:

   We can do even better with the last one. It is a divide-and-conquer algorithm. 
   It's not the fastest algorithm and may run out of space. But it's simple.

     (define (fact n)
       (letrec ((prod (lambda (left right)
			 (if (= left right)
			     left
			     (let ((pivot (quotient (+ left right) 2)))
				(* (prod left pivot) (prod (+ pivot 1) right)))))))
	  (prod 1 n)))
0
   It computes 10! this way:
     (((1*2)*3)*(4*5))*(((6*7)*8)*(9*10))

Using the same idea (n/2 simple products, n/4 products of simple products,
n/8 products of products of simple products, ...), but without using
any divide operations:

(define (fac n)

  (define (mul first step)
    (let ((second (+ first step)))
      (if (> second n)
	  first
	  (let ((double-step (+ step step)))
	    (* (mul first double-step)
	       (mul second double-step))))))

  (mul 1 1))


10! goes like this:

1*2*3*4*5*6*7*8*9*10 ->
(1*3*5*7*9)*(2*4*6*8*10) ->
((1*5*9)*(3*7))*((2*6*10)*(4*8)) ->
(((1*9)*5)*(3*7))*(((2*10)*6)*(4*8))

Some timing under VSCM (DEC Alpha box, times in seconds, GC time in
parentheses)...

Remarks:
	- my ``division elimination'' doesn't improve timing by a lot ---
	  in fact the numbers I found were close to each other within
	  the margin of error;
	  even running 20000! (not shown) is remarkably close
	  (no-division was .083s faster over a total running time of
	   approx. 39s)

	- doing the iteration in the ``right direction'' improves
	  10000! by 10-15% (not shown)

1. The iterative solution:

---- SESSION TRANSCRIPT ----
Welcome to VSCM [now featuring a module system]
D> (define (fac n)
     (do ((a 1 (* i a))
	  (i n (- i 1)))
	 ((< i 2) a)))
; compile: 0.066 execute: 0.000 TOTAL: 0.066
; result: 
(fac)
D> (begin (fac 100) '())
; compile: 0.017 execute: 0.000 TOTAL: 0.017
; result: 
()
D> (begin (fac 1000) '())
; compile: 0.017 execute: 0.116 TOTAL: 0.133
; result: 
()
D> (begin (fac 5000) '())
; compile: 0.017 execute: 3.033(0.233) TOTAL: 3.050(0.233)
; result: 
()
D> (begin (fac 10000) '())
; compile: 0.017 execute: 12.683(1.066) TOTAL: 12.700(1.066)
; result: 
()
D> (quit)

Process scheme finished
---- END ----

2. The solution outlined above:

---- SESSION TRANSCRIPT ----
Welcome to VSCM [now featuring a module system]
D> (define (fac n)

     (define (mul first step)
       (let ((second (+ first step)))
	 (if (> second n)
	     first
	     (let ((double-step (+ step step)))
	       (* (mul first double-step)
		  (mul second double-step))))))

     (mul 1 1))
; compile: 0.100 execute: 0.000 TOTAL: 0.100
; result: 
(fac)
D> (begin (fac 100) '())
; compile: 0.016 execute: 0.000 TOTAL: 0.016
; result: 
()
D> (begin (fac 1000) '())
; compile: 0.017 execute: 0.100 TOTAL: 0.117
; result: 
()
D> (begin (fac 5000) '())
; compile: 0.017 execute: 1.983 TOTAL: 2.000
; result: 
()
D> (begin (fac 10000) '())
; compile: 0.000 execute: 8.616 TOTAL: 8.616
; result: 
()
D> (quit)

Process scheme finished
---- END ----
--
-Matthias
