【问题标题】:Turn a hash map inside out in Clojure在 Clojure 中翻转哈希映射
【发布时间】:2011-09-02 21:29:52
【问题描述】:

我是 Clojure 的新手,有一个有趣的问题想请教 Clojure 专家。我正在阅读“Programming Collective Intelligence”一书,并尝试在 Clojure 中编写示例(这本书中的所有示例都使用 Python)。在第一章中,我们有一个影评人的哈希图设置以及他们对不同电影的排名。它看起来像这样:

{"Lisa Rose" {"Lady in the Water" 2.5, "Snakes on a Plane" 3.5 },
 "Gene Seymour" {"Lady in the Water" 3.0, "Snakes on a Plane" 3.5}}

问题是这样的。如何把它翻过来,以便我得到一个看起来像这样的哈希图:

{"Lady in the Water" {"Lisa Rose" 2.5, "Gene Seymour" 3.0},
 "Snakes on a Plane" {"Lisa Rose" 3.5, "Gene Seymour" 3.5}}

你的职责是什么?

【问题讨论】:

  • 感谢大家的出色回答。我从他们身上学到了一些东西。

标签: clojure


【解决方案1】:
(let [m {"Lisa Rose" {"Lady in the Water" 2.5, "Snakes on a Plane" 3.5 },
         "Gene Seymour" {"Lady in the Water" 3.0, "Snakes on a Plane" 3.5}}]
  (apply merge-with merge
         (for [[ok ov] m
               [ik iv] ov]
           {ik {ok iv}})))

{"Snakes on a Plane" {"Gene Seymour" 3.5, "Lisa Rose" 3.5},
 "Lady in the Water" {"Gene Seymour" 3.0, "Lisa Rose" 2.5}}

【讨论】:

  • 感谢您将我的 question 标记为重复。你认为conjmerge 用于merge-with 更好吗?
  • @viebel 我更喜欢merge。我不太喜欢依赖一个奇怪的事实,即对于地图,conj 等同于 into - 我更愿意假装 conj 只接受地图条目。
【解决方案2】:
(defn inverse-map [m]
  (let [inner-keys (-> m first val keys)
        outer-keys (keys m)]
    (apply merge-with merge
           (for [ik inner-keys
                 ok outer-keys]
             {ik {ok (get-in input [ok ik])}}))))

这假设内部映射中所有感兴趣的键都存在于第一个内部映射上。如果这是不正确的,(-> m first val keys) 必须被替换为返回所有感兴趣键的集合的东西,例如(->> m (map values) (mapcat keys)).

我们的想法是构建{inner-key {outer-key the-value-at-inner-key-in-the-map-at-outer-key}} 形式的地图,然后将它们适当地合并在一起。

问题文本中地图上的返回值与指定的一样。

现在,上面创建了很多中间结构,这可能是性能方面的问题。如果速度至关重要,您可以切换到loop 和瞬态:

(defn transient-inverse-map [m]
  (let [inner-keys (-> m first val keys)
        outer-keys (keys m)
        t (transient {})]
    (loop [inner-keys inner-keys
           t t]
      (if (seq inner-keys)
        (recur (next inner-keys)
               (assoc! t ik
                       (let [ik (first inner-keys)
                             t  (transient {})]
                         (loop [outer-keys outer-keys
                                t t]
                           (if (seq outer-keys)
                             (let [ok (first outer-keys)]
                               (recur (next outer-keys)
                                      (assoc! t ok (get-in m [ok ik]))))
                             (persistent! t))))))
        (persistent! t)))))

这个想法是一样的,如果更难从代码中辨别出来。

【讨论】:

    【解决方案3】:

    我可以提出以下建议: 我们有地图 - 条目集合。将每个条目翻过来,然后将它们合并: 首字母:

    {:Lisa {:Lady 2.5, :Snakes 3.5},
     :Gene {:Lady 3.0, :Snakes 3.5}}
    

    反转每个条目:

    ([:Lady {:Lisa 2.5}], [:Snakes {:Lisa 3.5}])
    ([:Lady {:Gene 3.0}], [:Snakes {:Gene 3.5}])
    

    连接它们:

    ([:Lady {:Lisa 2.5}], [:Snakes {:Lisa 3.5}], [:Lady {:Gene 3.0}], [:Snakes {:Gene 3.5}])
    

    然后他们将它们合并到一张地图中:

    {:Lady {:Lisa 2.5, :Gene 3.0},
     :Snakes {:Lisa 3.5, :Gene 3.5}}
    

    代码:

    (defn inverse-map [m]
            (let [inverse-entry (fn [[name movies]]
                                    (map (fn [[movie rating]]
                                             [movie {name rating}])
                                         movies))]
               (->> (map inverse-entry m)
                    (reduce concat)
                    (reduce (fn [res [movie entry]]
                                (update-in res [movie] merge entry))
                            {}))))
    

    所以我们得到 map,(它是向量 [key value] 的集合),反转每个条目(向量 [key value])。现在我们有了向量集合的集合,将它们连接到一个集合中。最后,使用 reduce 我们将每个向量添加到 map 中。

    我想有更优雅的解决方案,但我的也可以。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-18
      • 2017-12-18
      • 1970-01-01
      • 1970-01-01
      • 2023-04-08
      • 2011-10-25
      相关资源
      最近更新 更多