使用可变参数

时间:2014-09-06 06:34:13

标签: clojure

我正在玩并尝试创建自己的简化实现,到目前为止,我已经使用了这个测试数据:

((fn [func & args]
  (reduce (fn [acc item]
            (conj acc (func (last acc) item))
            )[(first args)] (first (rest args)))) * 2 [3 4 5]

我不喜欢的是我如何分离args。

(first args)是我所期望的,即2,但(rest args)([3 4 5]),所以我得到了余下的(first (rest args)),我不喜欢。

我是否遗漏了一些技巧,可以更容易地使用可变参数?

2 个答案:

答案 0 :(得分:3)

Variadic参数只是在列表中获取未指定数量的参数,因此可以在此处应用所有列表/解构操作。

例如:

(let [[fst & rst] a-list]
  ; fst is the first element
  ; rst is the rest
 )

这比以下内容更具可读性:

(let [fst (first a-list)
      rst (rest a-list)]
  ; ...
 )

您可以在一行中进一步获取列表的第一个和第二个元素(假设它有> 1个元素):

(let [fst snd & rst]
  ; ...
 )

我最初误读了您的问题,并认为您正在尝试重新实现reduce功能。以下是我为此答案编写的示例实现,它不使用firstrest

(defn myreduce
  ;; here we accept the form with no initial value
  ;; like in (myreduce * [2 3 4 5]), which is equivalent
  ;; to (myreduce * 2 [3 4 5]). Notice how we use destructuring
  ;; to get the first/rest of the list passed as a second
  ;; argument
  ([op [fst & rst]] (myreduce op fst rst))
  ;; we take an operator (function), accumulator and list of elements
  ([op acc els]
    ;; no elements? give the accumulator back
    (if (empty? els)
      acc
      ;; all the function's logic is in here
      ;; we're destructuring els to get its first (el) and rest (els)
      (let [[el & els] els]
        ;; then apply again the function on the same operator,
        ;; using (op acc el) as the new accumulator, and the
        ;; rest of the previous elements list as the new
        ;; elements list
        (recur op (op acc el) els)))))

我希望它可以帮助您了解如何使用列表解构,这可能是您在函数中想要的。这是关于此主题的relevant blog post

答案 1 :(得分:1)

整理你的功能。

如@bfontaine所述,您可以使用(second args)代替(first (rest args))

(defn reductions [func & args]
  (reduce
    (fn [acc item] (conj acc (func (last acc) item)))
    [(first args)]
    (second args)))

这使用

  • func
  • (first args)
  • (second args)

...但忽略args的其余部分。

因此我们可以使用解构来命名args的第一和第二个元素 - initcoll似乎合适 - 给予

(defn reductions [func & [init coll & _]]
  (reduce
    (fn [acc item] (conj acc (func (last acc) item)))
    [init]
    coll))

...其中_是被忽略参数的常规名称,在本例中是一个序列。

我们可以摆脱它,简化为

(defn reductions [func & [init coll]] ... )

...然后到

(defn reductions [func init coll] ... )

... - 三个参数的直接功能。

处理潜在问题。

您的功能有两个问题:

  • 慢度
  • 缺乏懒惰。

慢度

此功能中闪烁的红灯是在

中使用last
(fn [acc item] (conj acc (func (last acc) item)))

即使acc是向量,每次调用时都会扫描整个acc。因此,此reductions需要的时间与coll长度的平方成正比:对于长序列来说,它的速度很慢。

一个简单的解决方法是将(last acc)替换为(acc (dec (count acc))),这需要有效的恒定时间。

缺乏懒惰

我们仍然不能懒惰地使用该功能产生的东西。例如,将这样的因子序列封装起来会很好:

(def factorials (reductions * 1N (next (range)))))

使用reductions,此定义永不返回。

你必须完全重塑你的功能才能使它变得懒惰。让我们修改标准-lazy - reductions来使用解构:

(defn reductions [f init coll]
  (cons
    init
    (lazy-seq
      (when-let [[x & xs] (seq coll)]
        (reductions f (f init x) xs)))))

现在我们可以定义

(def factorials (reductions * 1N (next (range))))

然后,例如,

(take 10 factorials)
;(1N 1N 2N 6N 24N 120N 720N 5040N 40320N 362880N)

另一种方法是从自身推导出序列,就像铁路机车铺设它所经过的轨道一样:

(defn reductions [f init coll]
  (let [answer (lazy-seq (reductions f init coll))]
    (cons init (map f answer coll))))

但这包含一个隐藏的递归(至少对我隐藏):

(nth (reductions * 1N (next (range))) 10000)
;StackOverflowError ...