-Fred
--
Science answers questions; philosophy questions answers.
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient with your first post.
> To unsubscribe from this group, send email to
> clojure+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en
I would think about the problem this way: to compute the value at index i in the output list, you multiply together each pair of values in the input lists whose indices add up to i, then add them all together. So I would first write a small helper function to return a list of such index pairs for a given output index, then use it with map to compute each output value in one go.
(defn convolve-indices [i max-m max-n]
"Lists all index pairs adding up to i, where the first index is less than max-m and the second is less than max-n."
(filter #(< (first %) max-m)
(filter #(< (second %) max-n)
(map #(vector % (- i %))
(range (inc i))))))
(defn convolve-1 [i ms ns]
"Returns the ith value of the convolution of ms with ns."
(reduce +
(map #(* (ms (first %)) (ns (second %)))
(convolve-indices i (count ms) (count ns)))))
(defn convolve [ms ns]
"Convolves ms with ns."
(map #(convolve-1 % ms ns)
(range (dec (+ (count ms) (count ns))))))
I would expect this to be much slower than the imperative approach, due to all the temporary objects and the lack of type hints. There should be a much less wasteful way to write convolve-indices, for instance. On the plus side, you may be able to use pmap to parallelize the work with little effort.
Perhaps something like this?
(defn convolve [ns ms]
(loop [nums (for [[i n] (indexed ns)
[j m] (indexed ms)] [(+ i j) (+ n m)])
y (transient (vec (repeat (+ (count ns) (count ms)) 0)))]
(if-let [[i s] (first nums)]
(recur (next nums) (assoc! y i (+ (y i) s)))
(persistent! y))))
- James
-Fred
--
Science answers questions; philosophy questions answers.
On Jul 16, 2010, at 2:57 PM, Isaac Hodes wrote:
What do I need to pull in to pick up the "indexed" function?
http://richhickey.github.com/clojure-contrib/seq-utils-api.html#clojure.contrib.seq-utils/indexed
Sorry, I should have mentioned you need Clojure 1.2.0 beta,
Clojure-Contrib 1.2.0 beta, and you need to include "indexed" from the
clojure.contrib.seq namespace:
(use '[clojure.contrib.seq :only (indexed)])
For version 1.1.0, it needs a little adjusting. The following code
should work under 1.1.0 (but I haven't tested this):
(use '[clojure.contrib.seq-utils :only (indexed)])
(defn convolve [ns ms]
(loop [nums (for [[i n] (indexed ns)
[j m] (indexed ms)] [(+ i j) (+ n m)])
y (vec (repeat (+ (count ns) (count ms)) 0))]
(if-let [[i s] (first nums)]
(recur (next nums) (assoc y i (+ (y i) s)))
y)))
This code will be slower, however, as it isn't using transients.
Transients are a way of speeding up manipulating data structures by
assuming you don't need to keep around the old copies.
- James
http://erl.nfshost.com/2010/07/17/discrete-convolution-of-finite-vectors/
Here's the originally posted Python code:
from itertools import repeat
def convolve(ns, ms):
y = [i for i in repeat(0, len(ns)+len(ms)-1)]
for n in range(len(ns)):
for m in range(len(ms)):
y[n+m] = y[n+m] + ns[n]*ms[m]
return y
Now, if you want to write this functionally in Clojure, you've got two
main obstacles in your way. First, you can't "bang a vector in place"
-- you want to use Clojure's built-in persistent vectors. For the
best speed, you can call transient at the beginning of the operations
and persistent! at the end, but you still need to pass the vector
around as an accumulator. This leads to the second obstacle --
because you're not "banging a vector in place" you can't really use
for loops or doseq loops to conveniently iterate through the indices.
You need to unroll these for loops into loop/recur constructs. It's a
bit of a nuisance to do all this, but it's actually a fairly
straightforward translation process. Here's what you get:
(defn convolve [ns ms]
(let [ns (vec ns), ms (vec ms),
count-ns (count ns), count-ms (count ms)]
(loop [n 0,
y (transient (vec (repeat (dec (+ count-ns count-ms)) 0)))]
(if (= n count-ns) (persistent! y)
(recur (inc n)
(loop [m 0, y y]
(if (= m count-ms) y
(let [n+m (+ n m)]
(recur (inc m) (assoc! y n+m
(+ (y n+m) (* (ns n) (ms m)))))))))))))
By my measurements, this Clojure code runs (under java -server) twice
as fast as the comparable Python code. Considering that Clojure's
math is inherently slower than Python's, and Clojure is using more
expensive persistent data structures, it's impressive that Clojure is
faster. Clojure also offers the opportunity to go faster (with Java
arrays), and even to deliver the results lazily if that's useful to
you (but unsurprisingly, the laziness comes at a bit of a cost).
Others have described those solutions, so I won't rehash those here.
But there are two strikes against Clojure here:
1. This Clojure code is definitely harder to read than the Python
equivalent. The transformation obfuscates what's going on.
2. Running the Python code under the psyco JIT compiler speeds the
Python code up by a factor of 10, making it about 5 times faster than
this Clojure transformation. So Python still gets the final word in
speed here (comparing the exact same algorithm on their built-in data
structures).