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]}