Clojure: for for n Dimensions

强颜欢笑 提交于 2019-12-10 16:03:42

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!