这是其中之一"是否有内置/更好/惯用/聪明的方法来做到这一点?"的问题。
我想要一个函数 - 称之为fn-pow
- 它将函数f应用于将f应用于参数的结果,然后将其应用于将其应用于其结果等的结果, n
次。(fn-pow inc 0 3)
次。例如,
(inc (inc (inc 0)))
等同于
iterate
使用(defn fn-pow-0
[f x n]
(nth (iterate f x) n))
:
(defn fn-pow-1
[f x n]
(if (> n 0)
(recur f (f x) (dec n))
x))
但是这会产生并抛弃不必要的懒惰序列。
从头开始编写函数并不难。这是一个版本:
fn-pow-0
在(fn-pow inc 0 10000000)
上使用Criterium,我发现这几乎是fn-pow-1
的两倍。
我不认为fn-pow
的定义是单一的,但{{1}}似乎可能是标准的内置函数,或者可能有一些简单的方法来定义它有一些巧妙安排的高阶函数。我也没有成功发现任何一个。我错过了什么吗?
答案 0 :(得分:7)
您正在寻找的内置版可能是dotimes
。我会以圆润的方式告诉你为什么。
您在基准测试中测试的主要是间接级别的开销。那个(nth (iterate ...) n)
仅只是的两倍,当身体是一个非常快的函数时编译成循环的速度是相当令人惊讶/鼓舞的。如果f
是一个代价更高的函数,那么开销的重要性会降低。 (当然如果你的f
是低级别且速度很快,那么你应使用低级循环结构。)
假设您的功能需要~1 ms而不是
(defn my-inc [x] (Thread/sleep 1) (inc x))
然后这两个大约需要1秒钟 - 差异大约是2%而不是100%。
(bench (fn-pow-0 my-inc 0 1000))
(bench (fn-pow-1 my-inc 0 1000))
另一个问题是iterate
正在创建一个不必要的序列。但是,如果您没有抓住头部,只需执行nth
,那么您 ,并丢弃LazySeq
个对象。换句话说,您使用的是一定数量的空间,尽管与n
成比例地生成垃圾。但是,除非您的f
是原始的或变异其参数,否则已经在生成自己的中间结果时与n
成比例地生成垃圾。
fn-pow-0
和fn-pow-1
之间的有趣折衷方案是
(defn fn-pow-2 [f x n] (reduce (fn [x _] (f x)) x (range n)))
由于range
个对象知道如何智能地减少自身,因此不会与n
成比例地创建附加垃圾。它归结为一个循环。这是范围的reduce
方法:
public Object reduce(IFn f, Object start) {
Object ret = f.invoke(start,n);
for(int x = n+1;x < end;x++)
ret = f.invoke(ret, x);
return ret;
}
这实际上是三个中最快的(在n
版本的recur
上添加原始类型提示之前)my-inc
放慢了速度。
如果你正在迭代一个在时间或空间上可能很昂贵的函数,比如矩阵运算,那么你很可能想要(以一种包含的方式)使用f
来改变它的参数以消除垃圾高架。由于突变是副作用,并且您希望副作用n
次,dotimes
是自然的选择。
为了举例,我将使用atom
作为替身,但想象一下在可变矩阵上进行抨击。
(def my-x (atom 0))
(defn my-inc! [x] (Thread/sleep 1) (swap! x inc))
(defn fn-pow-3! [f! x n] (dotimes [i n] (f! x)))
答案 1 :(得分:3)
这听起来就像编写函数n
次一样。
(defn fn-pow [f p t]
((apply comp (repeat t f)) p))
答案 2 :(得分:2)
嗯。我注意到Ankur的版本比原版快了大约10倍 - 可能不是意图,无论多么惯用? : - )
仅仅针对计数器输入提示fn-pow-1对我来说产生了明显更快的结果 - 大约快了3倍。
(defn fn-pow-3 [f x ^long n]
(if (> n 0)
(recur f (f x) (dec n))
x))
这大约是直接使用inc的版本的两倍,失去了可变性(不暗示x以保持测试精神)......
(defn inc-pow [x ^long n]
(if (> n 0)
(recur (inc x) (dec n))
x))
我认为fn-pow-3可能是最好的解决方案。
我没有找到一种特别“惯用”的方式,因为它不像微基准之外的常见用例(尽管会喜欢被反驳)。
如果你有一个现实世界的例子,会很感兴趣吗?
答案 3 :(得分:1)
对于我们愚蠢的命令式程序员,更通用的模式称为 while 语句。我们可以在宏中捕获它:
(defmacro while [bv ; binding vector
tf ; test form
recf ; recur form
retf ; return form
]
`(loop ~bv (if ~tf (recur ~@recf) ~retf)))
...在你的情况下
(while [x 0, n 3] (pos? n)
[(inc x) (dec n)]
x)
; 3
n
,但这是非法的。也许是的
推断。while
。 我们可以在重复之前调整宏来做事情:
(defmacro while [bv ; binding vector
tf ; test form
bodyf ; body form
retf ; return form
]
(let [bodyf (vec bodyf)
recf (peek bodyf)
bodyf (seq (conj (pop bodyf) (cons `recur recf)))]
`(loop ~bv (if ~tf ~bodyf ~retf))))
例如
(while [x 0, n 3] (pos? n)
(let [x- (inc x) n- (dec n)] [x- n-])
x)
; 3
我觉得这很有表现力。因人而异。