运行 带传递值的嵌套循环的惯用方法
Idiomatic way to run nested loop with passing value
我想做这样的事情
int n=0
for(int i=xs; i<xe; i++){
for(int j=ys; j<ye; j++){
n++
}
}
return n;
以 Clojure 方式。由于所有值都是不可变的,我认为值 n 应该作为(可能)递归函数的参数传递。最好的方法是什么?
不要over-think这个问题。当你真的需要可变状态时,你总是可以使用 atom
:
(defn calc
[xs ys]
(let [result (atom 0)]
(doseq [x xs]
(doseq [y ys]
(swap! result + (* x y))))
@result))
(let [xs [1 2 3]
ys [2 5 7 9]]
(calc xs ys))
结果
(calc xs ys) => 138
您也可以使用 volatile。它就像一个 non-thread-safe 原子。注意使用 vswap!
:
(defn calc
[xs ys]
(let [result (volatile! 0)]
(doseq [x xs]
(doseq [y ys]
(vswap! result + (* x y))))
@result))
性能
在紧密循环中,使用 volatile
会有所不同。一个例子:
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require [tupelo.profile :as prof]))
(def N 100)
(def vals (vec (range N)))
(prof/defnp summer-atom []
(let [result (atom 0)]
(doseq [i vals]
(doseq [j vals]
(doseq [k vals]
(swap! result + i j k))))
@result))
(prof/defnp summer-volatile []
(let [result (volatile! 0)]
(doseq [i vals]
(doseq [j vals]
(doseq [k vals]
(vswap! result + i j k))))
@result))
(dotest
(prof/timer-stats-reset)
(dotimes [i 10]
(spyx (summer-atom))
(spyx (summer-volatile)))
(prof/print-profile-stats))
结果:
--------------------------------------
Clojure 1.10.2-alpha1 Java 15
--------------------------------------
Testing tst.demo.core
(summer-atom) => 148500000
(summer-volatile) => 148500000
...
---------------------------------------------------------------------------------------------------
Profile Stats:
Samples TOTAL MEAN SIGMA ID
10 2.739 0.273879 0.023240 :tst.demo.core/summer-atom
10 0.383 0.038313 0.041246 :tst.demo.core/summer-volatile
---------------------------------------------------------------------------------------------------
所以它产生了大约 10 倍的差异。可能不值得,除非你像这里一样进行至少一百万次操作 (100^3)。
类似low-level对数据结构的操作,请见transient!和朋友
特别是将 Clojure CheatSheet 添加为书签 from this list
最接近您的代码的是
(defn f [xs xe ys ye]
(let [n (atom 0)]
(doseq [_ (range xs xe)
_ (range ys ye)]
(swap! n inc))
@n))
user> (f 1 10 2 20)
;;=> 162
但是可变原子方法根本不是惯用的。
它可能看起来像这样,更多一点 clojure 方式:
(defn f [xs xe ys ye]
(count (for [_ (range xs xe)
_ (range ys ye)]
nil)))
#'user/f
user> (f 1 10 2 20)
;;=> 162
这真的取决于你想做什么。 (* (- xe xs) (- ye ys))
显然可以更好地计算 n,正如@jas 注意到的那样,与您使用的语言无关)
你提到的递归解决方案怎么样,它可能看起来像这样:
(defn f [xs xe ys ye]
(loop [n 0 i xs j ys]
(cond (== j ye) n
(== i xe) (recur n xs (inc j))
:else (recur (inc n) (inc i) j))))
#'user/f
user> (f 1 10 2 20)
;;=> 162
我认为您可以在 for
上应用 reduce
函数。你在 loop-processing-fn
中做什么取决于你 - 它也可以递归。
(let [n-init 0 ;; your `n` variable
xs 10 xe 20 ys -5 ye 5 ;; loop(s) ranges
loop-processing-fn (fn [current-state [i j :as loop-data]]
(inc current-state) ;; anything here
) ;; processing function operating on state (n) and loop data
]
(reduce loop-processing-fn n-init (for [i (range xs xe)
j (range ys ye)]
[i j])))
;; => 100
想到宏。我定义了一个宏 for-state
,我这样使用:
(def xs 0)
(def xe 9)
(def ys 1)
(def ye 4)
(for-state
i xs (< i xe) (inc i) n 0
(for-state
j ys (< j ye) (inc j) n n
(inc n)))
;; => 27
宏可让您添加仅使用函数难以构建的新结构。因此,如果您有多个此类嵌套循环,可以选择定义一个像 for-state
这样的宏:
(defmacro for-state [iter-var iter-init iter? iter-next
state-var state-init state-next]
`(loop [~iter-var ~iter-init
~state-var ~state-init]
(if ~iter?
(recur ~iter-next
~state-next)
~state-var)))
您可以随意调整它。例如,您可以使用向量对宏参数进行分组,并对这些参数进行解构以提高可读性。
我想做这样的事情
int n=0
for(int i=xs; i<xe; i++){
for(int j=ys; j<ye; j++){
n++
}
}
return n;
以 Clojure 方式。由于所有值都是不可变的,我认为值 n 应该作为(可能)递归函数的参数传递。最好的方法是什么?
不要over-think这个问题。当你真的需要可变状态时,你总是可以使用 atom
:
(defn calc
[xs ys]
(let [result (atom 0)]
(doseq [x xs]
(doseq [y ys]
(swap! result + (* x y))))
@result))
(let [xs [1 2 3]
ys [2 5 7 9]]
(calc xs ys))
结果
(calc xs ys) => 138
您也可以使用 volatile。它就像一个 non-thread-safe 原子。注意使用 vswap!
:
(defn calc
[xs ys]
(let [result (volatile! 0)]
(doseq [x xs]
(doseq [y ys]
(vswap! result + (* x y))))
@result))
性能
在紧密循环中,使用 volatile
会有所不同。一个例子:
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require [tupelo.profile :as prof]))
(def N 100)
(def vals (vec (range N)))
(prof/defnp summer-atom []
(let [result (atom 0)]
(doseq [i vals]
(doseq [j vals]
(doseq [k vals]
(swap! result + i j k))))
@result))
(prof/defnp summer-volatile []
(let [result (volatile! 0)]
(doseq [i vals]
(doseq [j vals]
(doseq [k vals]
(vswap! result + i j k))))
@result))
(dotest
(prof/timer-stats-reset)
(dotimes [i 10]
(spyx (summer-atom))
(spyx (summer-volatile)))
(prof/print-profile-stats))
结果:
--------------------------------------
Clojure 1.10.2-alpha1 Java 15
--------------------------------------
Testing tst.demo.core
(summer-atom) => 148500000
(summer-volatile) => 148500000
...
---------------------------------------------------------------------------------------------------
Profile Stats:
Samples TOTAL MEAN SIGMA ID
10 2.739 0.273879 0.023240 :tst.demo.core/summer-atom
10 0.383 0.038313 0.041246 :tst.demo.core/summer-volatile
---------------------------------------------------------------------------------------------------
所以它产生了大约 10 倍的差异。可能不值得,除非你像这里一样进行至少一百万次操作 (100^3)。
类似low-level对数据结构的操作,请见transient!和朋友
特别是将 Clojure CheatSheet 添加为书签 from this list
最接近您的代码的是
(defn f [xs xe ys ye]
(let [n (atom 0)]
(doseq [_ (range xs xe)
_ (range ys ye)]
(swap! n inc))
@n))
user> (f 1 10 2 20)
;;=> 162
但是可变原子方法根本不是惯用的。
它可能看起来像这样,更多一点 clojure 方式:
(defn f [xs xe ys ye]
(count (for [_ (range xs xe)
_ (range ys ye)]
nil)))
#'user/f
user> (f 1 10 2 20)
;;=> 162
这真的取决于你想做什么。 (* (- xe xs) (- ye ys))
显然可以更好地计算 n,正如@jas 注意到的那样,与您使用的语言无关)
你提到的递归解决方案怎么样,它可能看起来像这样:
(defn f [xs xe ys ye]
(loop [n 0 i xs j ys]
(cond (== j ye) n
(== i xe) (recur n xs (inc j))
:else (recur (inc n) (inc i) j))))
#'user/f
user> (f 1 10 2 20)
;;=> 162
我认为您可以在 for
上应用 reduce
函数。你在 loop-processing-fn
中做什么取决于你 - 它也可以递归。
(let [n-init 0 ;; your `n` variable
xs 10 xe 20 ys -5 ye 5 ;; loop(s) ranges
loop-processing-fn (fn [current-state [i j :as loop-data]]
(inc current-state) ;; anything here
) ;; processing function operating on state (n) and loop data
]
(reduce loop-processing-fn n-init (for [i (range xs xe)
j (range ys ye)]
[i j])))
;; => 100
想到宏。我定义了一个宏 for-state
,我这样使用:
(def xs 0)
(def xe 9)
(def ys 1)
(def ye 4)
(for-state
i xs (< i xe) (inc i) n 0
(for-state
j ys (< j ye) (inc j) n n
(inc n)))
;; => 27
宏可让您添加仅使用函数难以构建的新结构。因此,如果您有多个此类嵌套循环,可以选择定义一个像 for-state
这样的宏:
(defmacro for-state [iter-var iter-init iter? iter-next
state-var state-init state-next]
`(loop [~iter-var ~iter-init
~state-var ~state-init]
(if ~iter?
(recur ~iter-next
~state-next)
~state-var)))
您可以随意调整它。例如,您可以使用向量对宏参数进行分组,并对这些参数进行解构以提高可读性。