This morning, I was catching up on the RSS feeds I follow. I noticed an interesting snippet of code in the Abstract Heresies journal for the Discrete-time Fourier Transform. Here is his snippet of code:
(define (dtft samples)
(lambda (omega)
(sum 0 (vector-length samples)
(lambda (n)
(* (vector-ref samples n)
(make-polar 1.0 (* omega n)))))))
My first thought was That’s way too short.
Then, I started reading through it. My next thought was, maybe I don’t understand scheme at all. Then, my next thought was, I do understand this code, it just didn’t do things the way I expected.
Here, I believe is a decent translation of the above into Common Lisp:
(defun dtft (samples)
#'(lambda (omega)
(loop for nn from 0 below (length samples)
summing (let ((angle (* omega nn)))
(* (aref samples nn)
(complex (cos angle) (sin angle)))))))
Now, what I find most interesting here is that most implementations you’ll find for the DTFT (Discrete-Time Fourier Transform) take an array of samples and a wavelength, and returns a result. This, instead, returns a function which you can call with a wavelength omega. It returns an evaluator. This is an interesting pattern that I will have to try to keep in mind. I have used it in some places before. But, I am sure there are other places that I should have used it, and missed. This is one where I would have missed.
Usage for the above would go something like:
(let ((dd (dtft samples)))
(dd angle1)
(dd angle2)
...)
For those not into Lisp either (who are you?), here is a rough translation into C++.
#include <cmath>
#include <complex>
class DTFT {
unsigned int len;
double* samples;
public:
DTFT( double* _samples, unsigned int _len ) {
this->len = _len;
this->samples = new double[ this->len ];
for ( unsigned int ii=0; ii < this->len; ++ii ) {
this->samples[ ii ] = _samples[ ii ];
}
}
~DTFT( void ) {
delete[] this->samples;
}
std::complex< double > operator () ( double omega ) const {
std::complex< double > sum( 0.0, 0.0 );
for ( unsigned int ii=0; ii < this->len; ++ii ) {
sum += this->samples[ ii ] * std::polar( 1.0, omega );
}
return sum;
}
};
With usage like:
DTFT dd( samples, 1024 );
dd( angle1 );
dd( angle2 );
...
So, six lines of Scheme or Lisp. Twenty-five lines of C++ including explicit definition of a class to act as a pseudo-closure, explicit copying and management of the samples buffer, etc. I suppose, a more direct translation would have used a std::vector to hold the samples and would have just kept a pointer to the buffer. That would have shaved off six or seven lines and the whole len and _len variables.