Clojure:实现comp函数

时间:2014-01-27 02:01:42

标签: clojure

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))...)

5 个答案:

答案 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;

匿名函数:

  • 需要5&#34;你好世界&#34;成为(\ h \ e \ l \ l \ o)
  • 是否应用str成为&#34;你好&#34;
  • 是否要成为&#34; HELLO&#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))))

此函数chaincomp类似,只是它只接受两个参数。

((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..)