问题
In Clojure the function for
can be used for iterating nested sequences. Imagine a 3D space with a x-, y- and z-axis:
(for [x (range 10)
y (range 5)
z (range 2)]
[x y z])
The code above would produce a sequence of vectors which represents all possible positions inside a cuboid. (limited to the positions which indices are natural numbers of course)
Does anybody know a good way to make this more generic? Meaning, to make it work if you have not a number of 3 but of n dimensions.
回答1:
Most approaches seem to use for
, as you do, assuming a known number of dimensions. What you seem to be looking for is the cartesian product. There is a function for calculating cartesian products in clojure.math.combinatorics.
(cartesian-product (range 10) (range 5) (range 2))
(apply cartesian-product (map range [10 5 2]))
(apply cartesian-product (repeatedly n #(range 3)))
If you don't want to include another library, this question has some interesting answers which you can use and/or learn from.
As of March 2016, this was the source for clojure.math.combinatorics/cartesian-product
:
(defn cartesian-product
"All the ways to take one item from each sequence"
[& seqs]
(let [v-original-seqs (vec seqs)
step
(fn step [v-seqs]
(let [increment
(fn [v-seqs]
(loop [i (dec (count v-seqs)), v-seqs v-seqs]
(if (= i -1) nil
(if-let [rst (next (v-seqs i))]
(assoc v-seqs i rst)
(recur (dec i) (assoc v-seqs i (v-original-seqs i)))))))]
(when v-seqs
(cons (map first v-seqs)
(lazy-seq (step (increment v-seqs)))))))]
(when (every? seq seqs)
(lazy-seq (step v-original-seqs)))))
回答2:
another way to do it (probably worse then a cartesian-product
, but still nice to show the power of clojure's macros):
(defmacro product [& colls]
(let [names (repeatedly (count colls) #(gensym "var"))]
`(for ~(vec (interleave names colls))
~(vec names))))
it just generates this for
list comprehension for any number of colls. for example:
(product (range 3) [:a :b :c] (range 2))
would expand into the following:
(for [var19715 (range 3) var19716 [:a :b :c] var19717 (range 2)]
[var19715 var19716 var19717])
回答3:
for
is a macro and its body expression can contain arbitrary code, such as a do
block or IO calls:
(for [x (range 3)]
(do
(prn x)
x))
Assuming the desired body expression is always in the form of [x y z ... n]
and the inputs are positive ranges
How to create a sequence of positions of an n dimensional matrix:
(defn matrix [h & t]
(if (some? t)
(for [d (range h)
ds (apply matrix t)]
(into [d] ds))
(map vector (range h))))
It is somewhat naive, but seems to do the job:
(matrix 3) ;; => (map vector (range 3))
;; => ([0] [1] [2])
(matrix 3 2) ;; => (for [d (range 3) ds (apply matrix '(2))] (into [d] ds))
;; => ([0 0] [0 1] [1 0] [1 1] [2 0] [2 1])
(matrix 3 2 4)
;; => ([0 0 0] [0 0 1] [0 0 2] [0 0 3] [0 1 0] [0 1 1] [0 1 2] [0 1 3]
;; [1 0 0] [1 0 1] [1 0 2] [1 0 3] [1 1 0] [1 1 1] [1 1 2] [1 1 3]
;; [2 0 0] [2 0 1] [2 0 2] [2 0 3] [2 1 0] [2 1 1] [2 1 2] [2 1 3])
I said naive because for
is lazy but into
is eager. Wrapping for
with doall
would force evaluation but a loop
using transients can probably perform better.
来源:https://stackoverflow.com/questions/35874072/clojure-for-for-n-dimensions