我想编写一个连接向量或矩阵的函数,它可以接受任意输入。为了结合两个向量,我编写了以下代码。它还可以组合矩阵以延长列。
(defn concats
([x y] (vec (concat x y))))
我被卡住的地方是将输入扩展到 n 个向量或矩阵,并将矩阵组合成更长的行。
例如)(somefunction [[:a :b] [:c :d]] [[1 2] [3 4]] 2]
[[:a :b 1 2] [:c :d 3 4]]
输入中的 2 指定要连接的级别。
我想编写一个连接向量或矩阵的函数,它可以接受任意输入。为了结合两个向量,我编写了以下代码。它还可以组合矩阵以延长列。
(defn concats
([x y] (vec (concat x y))))
我被卡住的地方是将输入扩展到 n 个向量或矩阵,并将矩阵组合成更长的行。
例如)(somefunction [[:a :b] [:c :d]] [[1 2] [3 4]] 2]
[[:a :b 1 2] [:c :d 3 4]]
输入中的 2 指定要连接的级别。
如果你对“它是如何工作的”不感兴趣,这里是前面的解决方案(注意它level
是零索引的,所以你称之为第 1 级,我称之为第 0 级):
(defn into* [to & froms]
(reduce into to froms))
(defn deep-into*
[level & matrices]
(-> (partial partial mapv)
(iterate into*)
(nth level)
(apply matrices)))
它如何工作的简短答案是:它迭代地构建一个函数,将调用嵌套into*
在正确的级别,然后将其应用于提供的矩阵。
给定向量第一个参数的常规 oldinto
会将第二个参数的元素连接到向量的末尾。这里的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*
向下映射某个集合的函数。这有点像说:将第 1 级的想法映射(mapv into* ...)
到矩阵中。为了将其推广到函数,您必须在此处识别模式。我要把它们都放在一起:
(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
现在迭代模式变得更加清晰。我们正在调用一些函数 on ...
(这只是我们给定的矩阵),并且该函数是调用(partial mapv ...)
on的迭代构建into*
,迭代级别数。该(partial mapv ...)
部分可以功能化为(partial partial mapv)
. 这是一个偏函数,它返回mapv
一些提供的参数的偏函数。这个外部partial
不是很必要,因为我们知道...
这里永远是一回事。所以我们可以像写它一样容易#(partial mapv %)
,但我很少有机会使用(partial partial ...)
它,而且我认为它看起来很漂亮。至于迭代,我使用 pattern (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]]]
然后,使用->
宏我可以分解出一些嵌套的括号。这个宏接受一个表达式列表,并递归地将每个表达式嵌套到连续表达式的第二个位置。它没有添加任何功能,但肯定可以使内容更具可读性:
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)))
Here are two different solutions for a function which will return a vector that's the concatenation of an arbitrary number of input collections:
(defn concats [& colls]
(reduce (fn [result coll]
(into result coll))
[]
colls))
(defn concats [& colls]
(vec (apply concat colls)))
The [& arg-name]
notation in the argument lists is how you specify that the function is "variadic" - meaning it can accept a variable number of arguments. The result is that colls
(or whatever name you pick) will be a sequence of all the arguments in excess of the positional arguments.
Functions can have multiple arities in Clojure, so you can also do things like this:
(defn concats
([x]
(vec x))
([x y]
(vec (concat x y)))
([x y & colls]
(vec (apply concat (list* x y colls)))))
However, only one of the overloads can be variadic, and its variadic part must come last (i.e. you can't do [& more n]
, only [n & more]
.
The Clojure.org page on special forms has more useful information on argument lists in Clojure (in the section on fn
).
The function below correctly handles the example input/output you provided. Unfortunately I don't think I understand how you want the levels (and associated numeric input) to work well enough to generalize it as far as you're looking for.
(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]]
But maybe it will give you some ideas anyway, or if you can add more information (especially examples of how different levels should work) I'll see if I can be more help.
使用您在问题中列出的 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"))