问题
在loop-recur中实现多重循环(里:如何造一个轮子)
描述
在loop-recur中使用一个N重循环是很痛苦的(脑细胞在燃烧),这时候可以使用一个生成N重循环下标列表的工具,在loop-recur中实现多重循环
代码
(defn smooth
[cur config]
(let [cur (reverse cur)
config (reverse config)
ps (map #(list % %2) cur config)]
(loop [carry 0
head (first ps)
tail (rest ps)
ret '()]
(if (nil? head)
ret
(let [[x y] head the-carry (quot (+ carry x) y) the-rest (mod (+ carry x) y)] (recur the-carry (first tail) (rest tail) (conj ret the-rest)))))))
; only for test!
(smooth (map + [1 2 3] [3 9 9]) [10 10 10])
(smooth (map + [1 2 3] [1 1 1]) [10 10 10])
(smooth [0 2 2] [2 2 2])
(defn smooth-ex
[numbers limits]
(let [numbers (reverse numbers)
limits (reverse limits)
pairs (map #(list % %2) numbers limits)]
(loop [carry 0
head (first pairs)
tail (rest pairs)
ret '()]
(if (nil? head)
ret
(let [[number limit] head the-carry (quot (+ carry number) limit) the-rest (mod (+ carry number) limit)] (recur the-carry (first tail) (rest tail) (conj ret the-rest)))))))
(defn looper-unit
[looper]
(if (= (count looper) 0)
'()
(let [zero-cnt (dec (count looper))]
(into '(1) (repeat zero-cnt 0)))))
(defn is-looper-end?
[looper looper-end]
(= (map inc looper) looper-end))
(defn looper-next
[looper looper-end]
(smooth (map + looper (looper-unit looper)) looper-end))
(defn looper-demo
[looper-end]
(loop [looper (repeat (count looper-end) 0) ret []]
(let [[i j] looper looper-string (str "row:" i " " "col:" j)]
(if (is-looper-end? looper looper-end)
(ret conj looper-string)
(recur (looper-next looper looper-end) (conj ret looper-string))))))
; just for test!
(map println (looper-demo [3 3]))
说明
smooth
: 类似大数加法中的进位操作,处理循环中下标的进位操作
smooth-ex
: smooth
的容易阅读的版本 …
looper-unit
: 生成循环的递增单位,即一个形如 (0 ... 0 1)
的列表, 可能不含0
其他函数顾名思义,不解释了
核心思想即产生一系列列表,例如一个二重循环:
for(int i = 0; i < 2; ++i)
for(int j = 0; j < 2; ++j)
{ /* excuse me */}
其下标对应的序列为:
[ (0 0) (0 1) (1 0) (1 1) ]
那么对于任意循环,我们只需要生成该列表组就可以进行循环操作, 那么在哪里可以买到呢:
注意 (0 0)
到 (0 1)
的变化过程:
(= '(0 1) (map + '(0 0) '(0 1)))
而 (0 1)
到 (1 0)
的变化过程则较为复杂,但这一变化可以看作二进制下 01 + 01 => 10
的过程
宗上可以看出,生成列表的过程可以看作是一个N进制数字自增的过程,特别的是这里的每一个位可能对应不同的进制(比如上面那个循环把j的限制改成 j < 100
, 就变成了i是2进制,j是100进制的自增过程)
利用这一性质产生了如上的编码(轮子),具体使用方法参考 looper-demo
, 配合解构操作可以顺利获得各循环变量, 然后瞎鸡巴乱搞
最后需要注意的是,对循环的限制是左闭右开区间, (looper-end [5 5])
得到的是一个 0-4 的二重循环
clojure多行注释太麻烦了,最后补上 (map println (looper-demo [3 3]))
的输出结果
; 不是代码 仅供参考
=> (map println (looper-demo [3 3]))
row:0 col:0
row:0 col:1
row:0 col:2
row:1 col:0
row:1 col:1
row:1 col:2
row:2 col:0
row:2 col:1
row:2 col:2
(nil nil nil nil nil nil nil nil nil)
; 最后的一串nil是prinln的返回值被map收集了, 因为这里我们需要的是副作用