如何以堆栈友好的方式反转和连接此映射?

时间:2014-08-13 10:18:21

标签: clojure stack-overflow

我有一个hashmap:

(def x {:a [1 3] :b [2 4] :c [1 2 3 4]})

我想要反转,因此输入值的每个项目都是输出键,值是连接它的键的串联。 e.g。

{1 [:a :c] 2 [:b :c] 3 [:a :c] 4 [:]}

我有一个解决方案:

(defn invert [input]
  (apply merge-with concat
    (apply concat
      (map (fn [[k vs]]
        (map (fn [v] {v [k]}) vs)) input))))

哪个有效:

=> (invert x)
{3 (:a :c), 1 (:a :c), 4 (:b :c), 2 (:b :c)}

但是堆栈溢出的大输入失败了:

=> (def big-x (apply merge (map (fn [i] {i (range 10)}) (range 10000))))
=> (invert big-x)

StackOverflowError   clojure.lang.LazySeq.seq (LazySeq.java:49)

如何为大输入完成此操作?

请注意,这与this question类似但不同。

3 个答案:

答案 0 :(得分:2)

(defn invert [in]
  (reduce (fn [out [key vals]]
            (reduce (fn [o v] (assoc o v (conj (get o v []) key)))
                    out vals))
          {} in))

答案 1 :(得分:1)

我的解决方案,似乎有点复杂,但没有溢出:

(defn invert-entry
  ":a [1 3] => {1 [:a] 3 [:a]}"
  [hash-entry] 
  (reduce #(if (%1 %2) 
             (update-in %1 [%2] conj) 
             (merge %1 {%2 [(key hash-entry)]})) 
           {} 
           (val hash-entry)))

(defn invert 
  [x] 
  (reduce #(let [single-inverted (invert-entry %2)] 
             (merge-with concat single-inverted %1)) 
          {} 
          x))

答案 2 :(得分:1)

如果你正在寻找一个简洁的解决方案,并有周期来燃烧......

(defn invert [m]
  (apply merge-with into (for [[k vs] m, v vs] {v [k]})))

例如,

(invert x)
;{4 [:c :b], 2 [:c :b], 3 [:a :c], 1 [:a :c]}

我已经忘记了我在哪里看到这个(对于集合,使用merge代替into),所以我无法归因于此。这不是我的意思。


我现在意识到,如果我们使用mapcat重写您的解决方案,并将concat替换为into

(defn invert [input]
  (apply merge-with into
         (mapcat (fn [[k vs]] (map (fn [v] {v [k]}) vs))
                 input)))

......它实际上与上述相同。