Clojure:如何优雅地移动地图中的矢量元素
Clojure: how to move vector elements in a map elegantly
在 clojure 中,我试图完成以下逻辑:
Input:
{:a [11 22 33] :b [10 20 30]}, 2
Output:
{:a [11] :b [10 20 30 22 33]}
即将最后 2 个元素从 :a 移动到 :b
这个操作有clojurish的方法吗?
由于您有效地修改了地图中的两个映射,因此显式解构地图可能是最简单的方法,并且只是 return 通过文字 return 新地图,使用 subvec
and into
进行矢量操作:
(defn move [m n]
(let [{:keys [a b]} m
i (- (count a) n)
left (subvec a 0 i)
right (subvec a i)]
{:a left :b (into b right)}))
(move {:a [11 22 33] :b [10 20 30]} 2)
;;=> {:a [11], :b [10 20 30 22 33]}
作为奖励,这个特定的实现既非常地道又非常快。
或者,使用 here 中的 split-at'
函数,你可以这样写:
(defn split-at' [n v]
[(subvec v 0 n) (subvec v n)])
(defn move [m n]
(let [{:keys [a b]} m
[left right] (split-at' (- (count a) n) a)]
{:a left :b (into b right)}))
(defn f [{:keys [a b]} n]
(let [last-n (take-last n a)]
{:a (into [] (take (- (count a) n) a))
:b (into b last-n)}))
(f {:a [11 22 33] :b [10 20 30]} 2)
=> {:a [11], :b [10 20 30 22 33]}
我会做的与乔什略有不同:
(defn tx-vals [ {:keys [a b]} num-to-move ]
{:a (drop-last num-to-move a)
:b (concat b (take-last num-to-move a)) } )
(tx-vals {:a [11 22 33], :b [10 20 30]} 2)
=> {:a (11), :b (10 20 30 22 33)}
更新
有时使用clojure.core/split-at
函数可能更方便,如下所示:
(defn tx-vals-2 [ {:keys [a b]} num-to-move ]
(let [ num-to-keep (- (count a) num-to-move)
[a-head, a-tail] (split-at num-to-keep a) ]
{ :a a-head
:b (concat b a-tail) } ))
如果输出首选矢量(我的最爱!),只需执行:
(defn tx-vals-3 [ {:keys [a b]} num-to-move ]
(let [ num-to-keep (- (count a) num-to-move)
[a-head, a-tail] (split-at num-to-keep a) ]
{:a (vec a-head)
:b (vec (concat b a-tail))} ))
获取结果:
(tx-vals-2 data 2) => {:a (11), :b (10 20 30 22 33)}
(tx-vals-3 data 2) => {:a [11], :b [10 20 30 22 33]}
如果这些项目的顺序无关紧要,这是我的尝试:
(def m {:a [11 22 33] :b [10 20 30]})
(defn so-42476918 [{:keys [a b]} n]
(zipmap [:a :b] (map vec (split-at (- (count a) n) (concat a b)))))
(so-42476918 m 2)
给出:
{:a [11], :b [22 33 10 20 30]}
首先,当要移动的元素数量大于集合大小时,使用其他答案中的 sub-vec 将抛出 IndexOutOfBoundsException。
其次,解构,大多数人在这里完成的方式,将函数耦合到一个特定的数据结构。这是一个带有键 :a 和 :b 的映射,这些键的值是向量。现在,如果您更改输入中的其中一个键,那么您还需要在移动功能中更改它。
我的解决方案如下:
(defn move [colla collb n]
(let [newb (into (into [] collb) (take-last n colla))
newa (into [] (drop-last n colla))]
[newa newb]))
这应该适用于任何集合,并且 return 2 个向量的向量。我的解决方案的可重用性要高得多。尝试:
(move (range 100000) (range 200000) 10000)
编辑:
现在您可以使用 first 和 second 来访问您在 return 中需要的向量。
我会采用一种方法,它与之前的答案略有不同(好吧,从技术上讲它是相同的,但在应用程序规模级别上有所不同)。
首先,在两个集合之间传输数据是一项非常频繁的任务,因此它至少值得在您的库中为此提供一些特殊的实用函数:
(defn transfer [from to n & {:keys [get-from put-to]
:or {:get-from :start :put-to :end}}]
(let [f (if (= get-from :end)
(partial split-at (- (count from) n))
(comp reverse (partial split-at n)))
[from swap] (f from)]
[from (if (= put-to :start)
(concat swap to)
(concat to swap))]))
好的,它看起来很冗长,但它可以让您将数据从一个集合的 start/end 传输到另一个集合的 start/end:
user> (transfer [1 2 3] [4 5 6] 2)
[(3) (4 5 6 1 2)]
user> (transfer [1 2 3] [4 5 6] 2 :get-from :end)
[(1) (4 5 6 2 3)]
user> (transfer [1 2 3] [4 5 6] 2 :put-to :start)
[(3) (1 2 4 5 6)]
user> (transfer [1 2 3] [4 5 6] 2 :get-from :end :put-to :start)
[(1) (2 3 4 5 6)]
那么剩下的就是在其上创建您的域特定功能:
(defn move [data n]
(let [[from to] (transfer (:a data) (:b data) n
:get-from :end
:put-to :end)]
(assoc data
:a (vec from)
:b (vec to))))
user> (move {:a [1 2 3 4 5] :b [10 20 30 40] :c [:x :y]} 3)
{:a [1 2], :b [10 20 30 40 3 4 5], :c [:x :y]}
在 clojure 中,我试图完成以下逻辑:
Input:
{:a [11 22 33] :b [10 20 30]}, 2
Output:
{:a [11] :b [10 20 30 22 33]}
即将最后 2 个元素从 :a 移动到 :b
这个操作有clojurish的方法吗?
由于您有效地修改了地图中的两个映射,因此显式解构地图可能是最简单的方法,并且只是 return 通过文字 return 新地图,使用 subvec
and into
进行矢量操作:
(defn move [m n]
(let [{:keys [a b]} m
i (- (count a) n)
left (subvec a 0 i)
right (subvec a i)]
{:a left :b (into b right)}))
(move {:a [11 22 33] :b [10 20 30]} 2)
;;=> {:a [11], :b [10 20 30 22 33]}
作为奖励,这个特定的实现既非常地道又非常快。
或者,使用 here 中的 split-at'
函数,你可以这样写:
(defn split-at' [n v]
[(subvec v 0 n) (subvec v n)])
(defn move [m n]
(let [{:keys [a b]} m
[left right] (split-at' (- (count a) n) a)]
{:a left :b (into b right)}))
(defn f [{:keys [a b]} n]
(let [last-n (take-last n a)]
{:a (into [] (take (- (count a) n) a))
:b (into b last-n)}))
(f {:a [11 22 33] :b [10 20 30]} 2)
=> {:a [11], :b [10 20 30 22 33]}
我会做的与乔什略有不同:
(defn tx-vals [ {:keys [a b]} num-to-move ]
{:a (drop-last num-to-move a)
:b (concat b (take-last num-to-move a)) } )
(tx-vals {:a [11 22 33], :b [10 20 30]} 2)
=> {:a (11), :b (10 20 30 22 33)}
更新
有时使用clojure.core/split-at
函数可能更方便,如下所示:
(defn tx-vals-2 [ {:keys [a b]} num-to-move ]
(let [ num-to-keep (- (count a) num-to-move)
[a-head, a-tail] (split-at num-to-keep a) ]
{ :a a-head
:b (concat b a-tail) } ))
如果输出首选矢量(我的最爱!),只需执行:
(defn tx-vals-3 [ {:keys [a b]} num-to-move ]
(let [ num-to-keep (- (count a) num-to-move)
[a-head, a-tail] (split-at num-to-keep a) ]
{:a (vec a-head)
:b (vec (concat b a-tail))} ))
获取结果:
(tx-vals-2 data 2) => {:a (11), :b (10 20 30 22 33)}
(tx-vals-3 data 2) => {:a [11], :b [10 20 30 22 33]}
如果这些项目的顺序无关紧要,这是我的尝试:
(def m {:a [11 22 33] :b [10 20 30]})
(defn so-42476918 [{:keys [a b]} n]
(zipmap [:a :b] (map vec (split-at (- (count a) n) (concat a b)))))
(so-42476918 m 2)
给出:
{:a [11], :b [22 33 10 20 30]}
首先,当要移动的元素数量大于集合大小时,使用其他答案中的 sub-vec 将抛出 IndexOutOfBoundsException。
其次,解构,大多数人在这里完成的方式,将函数耦合到一个特定的数据结构。这是一个带有键 :a 和 :b 的映射,这些键的值是向量。现在,如果您更改输入中的其中一个键,那么您还需要在移动功能中更改它。
我的解决方案如下:
(defn move [colla collb n]
(let [newb (into (into [] collb) (take-last n colla))
newa (into [] (drop-last n colla))]
[newa newb]))
这应该适用于任何集合,并且 return 2 个向量的向量。我的解决方案的可重用性要高得多。尝试:
(move (range 100000) (range 200000) 10000)
编辑:
现在您可以使用 first 和 second 来访问您在 return 中需要的向量。
我会采用一种方法,它与之前的答案略有不同(好吧,从技术上讲它是相同的,但在应用程序规模级别上有所不同)。
首先,在两个集合之间传输数据是一项非常频繁的任务,因此它至少值得在您的库中为此提供一些特殊的实用函数:
(defn transfer [from to n & {:keys [get-from put-to]
:or {:get-from :start :put-to :end}}]
(let [f (if (= get-from :end)
(partial split-at (- (count from) n))
(comp reverse (partial split-at n)))
[from swap] (f from)]
[from (if (= put-to :start)
(concat swap to)
(concat to swap))]))
好的,它看起来很冗长,但它可以让您将数据从一个集合的 start/end 传输到另一个集合的 start/end:
user> (transfer [1 2 3] [4 5 6] 2)
[(3) (4 5 6 1 2)]
user> (transfer [1 2 3] [4 5 6] 2 :get-from :end)
[(1) (4 5 6 2 3)]
user> (transfer [1 2 3] [4 5 6] 2 :put-to :start)
[(3) (1 2 4 5 6)]
user> (transfer [1 2 3] [4 5 6] 2 :get-from :end :put-to :start)
[(1) (2 3 4 5 6)]
那么剩下的就是在其上创建您的域特定功能:
(defn move [data n]
(let [[from to] (transfer (:a data) (:b data) n
:get-from :end
:put-to :end)]
(assoc data
:a (vec from)
:b (vec to))))
user> (move {:a [1 2 3 4 5] :b [10 20 30 40] :c [:x :y]} 3)
{:a [1 2], :b [10 20 30 40 3 4 5], :c [:x :y]}