4Clojure Problem 58声明为:
编写一个允许您创建函数组合的函数。参数列表应该采用可变数量的函数,并创建一个函数从右到左应用它们。
(= [3 2 1] ((__ rest reverse) [1 2 3 4]))
(= 5 ((__ (partial + 3) second) [1 2 3 4]))
(= true ((__ zero? #(mod % 8) +) 3 5 7 9))
(= "HELLO" ((__ #(.toUpperCase %) #(apply str %) take) 5 "hello world"))
此处__
应替换为解决方案。
在此问题中,不应使用函数comp
。
我找到的解决方案是:
(fn [& xs]
(fn [& ys]
(reduce #(%2 %1)
(apply (last xs) ys) (rest (reverse xs)))))
有效。但我真的不明白reduce
在这里是如何运作的。它如何代表(apply f_1 (apply f_2 ...(apply f_n-1 (apply f_n args))...)
?
答案 0 :(得分:8)
让我们尝试分三个阶段修改该解决方案。保持每个人一段时间,看看你是否得到它。如果你确实如此以及何时停止我会让你更加困惑。
首先,让我们有更多的描述性名称
(defn my-comp [& fns]
(fn [& args]
(reduce (fn [result-so-far next-fn] (next-fn result-so-far))
(apply (last fns) args) (rest (reverse fns)))))
然后考虑一些
(defn my-comp [& fns]
(fn [& args]
(let [ordered-fns (reverse fns)
first-result (apply (first ordered-fns) args)
remaining-fns (rest ordered-fns)]
(reduce
(fn [result-so-far next-fn] (next-fn result-so-far))
first-result
remaining-fns))))
接下来用一个执行相同的循环替换reduce
(defn my-comp [& fns]
(fn [& args]
(let [ordered-fns (reverse fns)
first-result (apply (first ordered-fns) args)]
(loop [result-so-far first-result, remaining-fns (rest ordered-fns)]
(if (empty? remaining-fns)
result-so-far
(let [next-fn (first remaining-fns)]
(recur (next-fn result-so-far), (rest remaining-fns))))))))
答案 1 :(得分:7)
我的解决方案是:
(fn [& fs]
(reduce (fn [f g]
#(f (apply g %&))) fs))
让我们尝试:
((
(fn [& fs]
(reduce (fn [f g]
#(f (apply g %&))) fs))
#(.toUpperCase %)
#(apply str %)
take)
5 "hello world"))
fs是功能列表:
#(.toUpperCase %)
#(apply str %)
take
第一次通过reduce,我们设置
f <--- #(.toUpperCase %)
g <--- #(apply str %)
我们创建一个匿名函数,并将其分配给reduce函数的累加器。
#(f (apply g %&)) <---- uppercase the result of apply str
下次通过reduce,我们设置
f <--- uppercase the result of apply str
g <--- take
我们再次创建一个新的匿名函数,并将其分配给reduce函数的累加器。
#(f (apply g %&)) <---- uppercase composed with apply str composed with take
fs现在为空,因此从reduce返回此匿名函数。
此功能传递5和&#34; hello world&#34;
匿名函数:
答案 2 :(得分:6)
这是comp
的一个优点(在我看来):
(defn comp [& fs]
(reduce (fn [result f]
(fn [& args]
(result (apply f args))))
identity
fs))
嵌套的匿名函数可能会让它一开始难以阅读,所以让我们尝试通过拉出它们并给它们命名来解决这个问题。
(defn chain [f g]
(fn [& args]
(f (apply g args))))
此函数chain
与comp
类似,只是它只接受两个参数。
((chain inc inc) 1) ;=> 3
((chain rest reverse) [1 2 3 4]) ;=> (3 2 1)
((chain inc inc inc) 1) ;=> ArityException
comp
at chain
的定义非常简单,有助于隔离reduce
为节目带来的内容。
(defn comp [& fs]
(reduce chain identity fs))
它将前两个函数链接在一起,其结果是一个函数。然后它将 函数链接到下一个,依此类推。
所以使用你的上一个例子:
((comp #(.toUpperCase %) #(apply str %) take) 5 "hello world") ;=> "HELLO"
仅使用chain
(无reduce
)的等价物是:
((chain identity
(chain (chain #(.toUpperCase %)
#(apply str %))
take))
5 "hello world")
;=> "HELLO"
从根本上讲,reduce
是关于迭代的。这是命令式样式的实现可能是什么样的(忽略了多个arities的可能性,正如Clojure的版本所支持的那样):
def reduce(f, init, seq):
result = init
for item in seq:
result = f(result, item)
return result
它只是捕获迭代序列并累积结果的模式。我认为reduce
有一种神秘感,它实际上可以让它更难理解而不是它需要,但是如果你打破它你就一定会得到它(并且你可能会经常感到惊讶)发现它很有用。)
答案 3 :(得分:2)
这是我的解决方法:
test.sort_values('a', ascending=True, inplace=True) # n log n
data = [{}] # 1
for d in test.itertuples(): # n times
to_append = {'id': d.Index, 'data': {'a': d.a, 'b': d.b}} # 3
data.append(to_append) # 1
我更喜欢A. Webb的解决方案,尽管它的行为不完全像(defn my-comp
([] identity)
([f] f)
([f & r]
(fn [& args]
(f (apply (apply my-comp r) args)))))
,因为在不带任何参数的情况下调用时不会返回comp
。只需添加一个零arar的机构即可解决该问题。
答案 4 :(得分:0)
考虑这个例子:
(def c (comp f1 ... fn-1 fn))
(c p1 p2 ... pm)
调用c
时:
第一个comp
最右边的参数fn
适用于p*
个参数;
然后将fn-1
应用于上一步的结果;
(...)
然后将f1
应用于上一步的结果,并返回其结果
您的样本解决方案完全相同。
首先将最右边的参数(last xs)
应用于ys
参数:
(apply (last xs) ys)
其他参数被反转以输入reduce
:
(rest (reverse xs))
reduce
获取提供的初始结果和函数列表,并迭代地将函数应用于结果:
(reduce #(%2 %1) ..init.. ..functions..)