有没有更好的方法在Clojure中编写此透视函数?

时间:2019-05-23 21:08:49

标签: clojure

我编写了一个函数来“透视”一些数据表,但想知道是否有更简单的方法来实现相同的结果。

(defn pivot-ab-ba
  [data]
  (r/reduce
   (fn [result [ks c]]
     (assoc-in result ks c))
   {}
   ;; pivot {a [b1 c1 b2 c2]} => [[b1 a] c1] [[b2 a] c2]
   (mapcat (fn [[a bcseq]]
             ;; pivot [a [b c]] => [[[b a] c]]
             (mapcat (fn [[b c]] [[[b a] c]]) bcseq))
           data)))

(let [data {1 {:good [1 2] :bad [3 4]}
            2 {:good [5 6] :bad [7 8]}}]
  (pivot-ab-ba data))

; => {:good {1 [1 2], 2 [5 6]}, :bad {1 [3 4], 2 [7 8]}}

这有效,但似乎过于复杂。

更新:

@TaylorWood在下面提出了一个解决方案。这是修改后的答案,以避免传入被旋转的键:

(defn pivot [data]
  (reduce-kv
    (fn [acc k v]
      (reduce (fn [acc' k'] (assoc-in acc' [k' k] (k' v)))
              acc
              (keys v)))
    {}
    data))

更新2:谢谢大家的回答。由于答案的多样性如此之多,因此我对结果进行了简要介绍,以了解其效果。诚然,这是一个测试,但仍然很有趣:

Benchmarks performed with (criterium.core/bench pivot-function)

# Original pivot-ab-ba
Evaluation count : 8466240 in 60 samples of 141104 calls.
Execution time mean : 7.274613 µs
Execution time std-deviation : 108.681498 ns

# @TaylorWood - pivot
Evaluation count : 39848280 in 60 samples of 664138 calls.
Execution time mean : 1.568971 µs
Execution time std-deviation : 32.567822 ns

# @AlanThompson - reorder-tree
Evaluation count : 25999260 in 60 samples of 433321 calls.
Execution time mean : 2.385929 µs
Execution time std-deviation : 33.130731 ns

# @AlanThompson reorder-tree-reduce
Evaluation count : 14507820 in 60 samples of 241797 calls.
Execution time mean : 4.249135 µs
Execution time std-deviation : 89.933197 ns

# @amalloy - pivot
Evaluation count : 12721980 in 60 samples of 212033 calls.
Execution time mean : 5.087314 µs
Execution time std-deviation : 226.242206 ns

4 个答案:

答案 0 :(得分:1)

这是另一种方法:

from __future__ import unicode_literals

这将获取一个映射和期望的键,然后对(defn pivot [data ks] (reduce-kv (fn [acc k v] (reduce (fn [acc' k'] (assoc-in acc' [k' k] (k' v))) acc ks)) {} data)) 中的每个键/值对进行归约,然后对每个期望的键进行另一个内部归约,从data映射和assoc中获取其值。将其导入输出映射。

data

答案 1 :(得分:0)

这就是我要怎么做。我正在使用atom来累积结果,但是如果您确实需要,可以将其转换为reduce

(ns tst.demo.core
  (:use demo.core tupelo.core tupelo.test) )

(defn reorder-tree
  [data]
  (let [result (atom {})]
    (doseq [[letter gb-data] data]
      (doseq [[gb-key nums-data] gb-data]
        (swap! result assoc-in [gb-key letter] nums-data)))
    @result))

(dotest
  (let [data     {:a {:good [1 2]
                      :bad  [3 4]}
                  :b {:good [5 6]
                      :bad  [7 8]}}

        expected {:good {:a [1 2]
                         :b [5 6]}
                  :bad  {:a [3 4]
                         :b [7 8]}}]

    (is= expected (reorder-tree data))))

更新:好的,我无法抗拒使用嵌套的reduce来编写for版本:

(defn reorder-tree-reduce
  [data]
  (reduce
    (fn [cum-map [letter gb-key nums-data]]
      (assoc-in cum-map [gb-key letter] nums-data))
    {}
    (for [[letter gb-data] data
          [gb-key nums-data] gb-data]
      [letter gb-key nums-data])))

答案 2 :(得分:0)

Reduce是实现此目标的不必要的低级功能。我更愿意以一种不言而喻的正确方式来生成一系列地图,然后使用merge来组合它们。将组合逻辑与生产逻辑交织在一起,会使函数的读者更难查看其作用以及是否正确。取而代之的是依靠简单易懂的功能mergemerge-with,这意味着没有多余的复杂性需要重新理解。

(defn pivot [coll]
  (apply merge-with merge
         (for [[a m] coll
               [b x] m]
           {b {a x}})))

答案 3 :(得分:0)

快速perc枢纽:

(let [data {1 {:good [1 2] :bad [3 4]}
            2 {:good [5 6] :bad [7 8]}}]
  (-> data
    (#%/%{:bad  (into {} (map #%/$[(key $) (-> $ val :bad)]  %))
          :good (into {} (map #%/$[(key $) (-> $ val :good)] %))})))