连接任意输入的函数

时间:2013-11-19 21:56:23

标签: clojure clojure-contrib

我想编写一个连接向量或矩阵的函数,它可以接受任意输入。为了组合两个向量,我编写了以下代码。它还可以组合矩阵,以便延长列。

(defn concats 
([x y] (vec (concat x y))))

我遇到的问题是将输入扩展为n个向量或矩阵,并组合矩阵以产生更长的行。

Ex)(某些功能[[:a:b] [:c:d]] [[1 2] [3 4]] 2]

[[:a :b 1 2] [:c :d 3 4]]

输入中的2指定要连接的级别。

3 个答案:

答案 0 :(得分:5)

如果你对“它是如何工作”不感兴趣,那么这就是前面的解决方案(注意level是零索引的,所以你称之为第一级我称之为第0级电平):

(defn into* [to & froms]
  (reduce into to froms))

(defn deep-into*
  [level & matrices]
  (-> (partial partial mapv)
      (iterate into*)
      (nth level)
      (apply matrices)))

它的工作原理的简短答案是:它迭代地构建一个函数,将调用into*嵌套在正确的级别,然后将其应用于提供的矩阵。

给定向量第一个参数的常规旧into将第二个参数的元素连接到向量的末尾。这里的into*函数就是我在可变数量的向量上进行向量连接的方式。它使用reduce在一些累积向量(以into开头)和列表to中的连续向量上迭代调用froms。例如:

user> (into* [1 2] [3 4] [5 6])
> [1 2 3 4 5 6]

现在deep-into*,我必须认识到一种模式。我开始通过手工编写不同的表达式来满足不同的级联级别。对于0级,这很容易(我已经稍微推断了你的例子,所以我可以把它变成2级):

user> (into* [[[:a :b] [:c :d]]] [[[1 2] [3 4]]])
> [[[:a :b] [:c :d]] [[1 2] [3 4]]]

至于第1级,它仍然非常简单。我使用mapv,它的工作方式与map类似,只是它返回一个向量而不是一个惰性序列:

user> (mapv into* [[[:a :b] [:c :d]]] [[[1 2] [3 4]]])
> [[[:a :b] [:c :d] [1 2] [3 4]]]

2级更多参与。这是我开始使用partial的地方。 partial函数接受一个函数和一个可变数量的参数参数(不是拼写错误),并返回一个“假定”给定参数的新函数。如果有帮助,(partial f x)(fn [& args] (apply f x args))相同。这个例子应该更清楚:

user> ((partial + 2) 5)
> 7
user> (map (partial + 2) [5 6 7]) ;why was six afraid of seven?
> (7 8 9)

所以知道这一点,并且知道我想要更深入一级,因此第2级看起来像是这样:

user> (mapv (partial mapv into*) [[[:a :b][:c :d]]] [[[1 2][3 4]]])
> [[[:a :b 1 2] [:c :d 3 4]]]

这里,它映射了一个将into*映射到某个集合的函数。这有点像说:将(mapv into* ...)的1级​​概念映射到矩阵中。为了将其概括为函数,您必须在此处识别模式。我要把它们全部放在一起:

(into* ...) ;level 0
(mapv into* ...) ;level 1
(mapv (partial mapv into*) ...) ;level 2

从这里开始,我记得(partial f)f相同(想一想:你有一个功能而且你没有给它额外的“假设”参数)。通过稍微扩展一点,(map f ...)((partial map f) ...)相同所以我将稍微重写以上内容:

(into* ...) ;level 0
((partial mapv into*) ...) ;level 1
((partial mapv (partial mapv into*)) ...) ;level 2

现在迭代模式变得越来越清晰。我们在...(这只是我们给定的矩阵)上调用了一些函数,该函数是在(partial mapv ...)上调用into*的迭代构建,迭代了级别数。 (partial mapv ...)部分可以功能化为(partial partial mapv)。这是一个部分函数,​​它返回mapv一些提供的参数的部分函数。这个外部partial不是必需的,因为我们知道这里的...总是一件事。所以我们可以像#(partial mapv %)一样轻松地编写它,但我很少有机会使用(partial partial ...),我觉得它看起来很漂亮。至于迭代,我使用模式(nth (iterate f initial) n)。也许另一个例子可以使这种模式清晰:

user> (nth (iterate inc 6) 5)
> 11

如果没有(nth ...)部分,它将永远循环,创建一个大于或等于5的递增整数的无限列表。所以现在,整个事物被抽象并计算为第2级:

user> ((nth (iterate (partial partial mapv) into*) 2)
        [[[:a :b][:c :d]]] [[[1 2][3 4]]])
> [[[:a :b 1 2] [:c :d 3 4]]]

然后,使用->宏我可以分解这些嵌套的parantheses。该宏获取表达式列表,并递归地将每个表达式嵌入到后续表达式的第二个位置。它没有添加任何功能,但肯定可以使事情更具可读性:

user> ((-> (partial partial mapv)
           (iterate into*)
           (nth 2))
        [[[:a :b][:c :d]]] [[[1 2][3 4]]])
> [[[:a :b 1 2] [:c :d 3 4]]]

从这里,推广到函数是非常简单的 - 用参数替换2和矩阵。但是因为这需要不同数量的矩阵,所以我们必须apply迭代构建的函数。 apply宏采用函数或宏,可变数量的参数,最后是集合。本质上,它将函数或宏和提供的参数预先添加到最终列表中,然后评估整个事物。例如:

user> (apply + [1 5 10]) ;same as (+ 1 5 10)
> 16

令人高兴的是,我们可以在apply的末尾贴上所需的(-> ...)。为了对称起见,这是我的解决方案:

(defn deep-into*
  [level & matrices]
  (-> (partial partial mapv)
      (iterate into*)
      (nth level)
      (apply matrices)))

答案 1 :(得分:0)

使用您在问题中列出的concats函数:

user=> (map concats [[:a :b] [:c :d]] [[1 2] [3 4]])
([:a :b 1 2] [:c :d 3 4])

这不会考虑您列出的级别,但会处理给定的输入

任意数量的参数需要替换concats函数

(defn conc [a b & args] 
  (if (nil? (first args)) 
     (concat a b) 
     (recur (concat a b) (first args) (rest args))))

以下是两个例子

user=> (map conc [[:a :b] [:c :d]] [[1 2] [3 4]] [["w" "x"] ["y" "z"]])
((:a :b 1 2 "w" "x") (:c :d 3 4 "y" "z"))
user=> (map conc [[:a :b] [:c :d] [:e :f]] [[1 2] [3 4] [5 6]] [["u" "v"] ["w" "x"] ["y" "z"]])
((:a :b 1 2 "u" "v") (:c :d 3 4 "w" "x") (:e :f 5 6 "y" "z"))

答案 2 :(得分:0)

以下是函数的两种不同解决方案,它将返回一个向量,该向量是任意数量的输入集合的串联:

(defn concats [& colls]
  (reduce (fn [result coll]
            (into result coll))
          []
          colls))

(defn concats [& colls]
  (vec (apply concat colls)))

参数列表中的[& arg-name]表示法是指定函数是“variadic”的方式 - 这意味着它可以接受可变数量的参数。结果是colls(或您选择的任何名称)将是超出位置参数的所有参数的序列。

Clojure中的函数可以有多个arity,所以你也可以这样做:

(defn concats
  ([x]
     (vec x))
  ([x y]
     (vec (concat x y)))
  ([x y & colls]
     (vec (apply concat (list* x y colls)))))

但是,只有一个重载可以是可变参数,并且它的可变参数部分必须是最后一个(即你不能[& more n],只能[n & more]

Clojure.org page on special forms在Clojure中的参数列表中有更多有用的信息(在fn部分)。

以下功能正确处理您提供的示例输入/输出。不幸的是,我不认为我理解你希望水平(和相关的数字输入)如何运作得足以将其概括为你想要的。

(defn concats [x y]
  ;; only works with two inputs
  (vec (map-indexed (fn [i v] (into v (nth y i)))
                    x)))

(concats [[:a :b] [:c :d]] [[1 2] [3 4]]) ;=> [[:a :b 1 2] [:c :d 3 4]]

但也许它会给你一些想法,或者如果你可以添加更多信息(特别是不同级别应该如何工作的例子),我会看看我是否可以提供更多帮助。