如何从Clojure中的嵌套地图生成列表?

时间:2019-06-08 19:49:31

标签: clojure maps

我有这个原子:

(def test (atom {:james {:friends [:lucy :john :daisy]},
                 :lucy {:friends [:james :daisy]},
                 :daisy {:friends [:james :lucy]},
                 :john {:friends [:james]}}))

给出:james作为参数,我需要遍历它的:friends并将每个:james friends的朋友放在一个列表中。结果必须是这样的:

(:james :daisy :james :james :lucy)

这是我到目前为止的最大努力:

(def user :james)

(def test2 (atom []))

(defn access
  [user]
  (get-in @test [user :friends]))

(doseq [connection (access user)]
  (swap! test2 concat (access connection)))

@test2

我不认为使用另一个原子(test2)是最惯用的方式。

4 个答案:

答案 0 :(得分:3)

是的,您不需要中间原子。

(def users (atom {:james {:friends [:lucy :john :daisy]},
                 :lucy   {:friends [:james :daisy]},
                 :daisy  {:friends [:james :lucy]},
                 :john   {:friends [:james]}}))

(defn friends [dict level users]
    (-> (fn [users] (mapcat #(get-in dict [% :friends]) users))
        (iterate users)
        (nth level)))

(friends @users 2 [:james])

答案 1 :(得分:1)

我只会在整个应用程序的“最高级”上使用原子。它们存储可共享的,并发访问的,可变的……数据(通常是全局数据)。

编写您需要的功能,使其尽可能不了解此类事物。如果最终得到的是纯函数,则使很多测试变得更加容易。如果要累积数据,则对数据进行整形。有let会在返回等之前确定计算步骤。

因此,这大致就是我要走的路(请注意,有一些方法可以使您的猫皮肤化以简化您的列表,我选择了mapcat)

(defn user
  [users user-name]
  (get users user-name))

(defn friends
  [user]
  (get user :friends))

(defn user-friends
  [users user-name]
  (some->> user-name (user users) (friends)))

(defn friends-friends
  [users user-name]
  (when-let [friend-names (user-friends users user-name)]
    (mapcat (partial user-friends users) friend-names))) ; XXX replacement for the accumulating concat

最后在您的测试或REPL中:

(let [users {:james {:friends [:lucy :john :daisy]}
             :lucy  {:friends [:james :daisy]}
             :daisy {:friends [:james :lucy]}
             :john  {:friends [:james]}}]
  (friends-friends users :james))
; => (:james :daisy :james :james :lucy)

答案 2 :(得分:0)

进行这种查询的另一种方法(或称其为“间接”方法)是通过Datalog进行的,在该方法中,您首先将嵌套地图转化为事实:

(def friendship (mapcat (fn [[p {xs :friends}]]
                          (for [f xs]
                            [p :person/friend f]))
                        {:james {:friends [:lucy :john :daisy]},
                         :lucy  {:friends [:james :daisy]},
                         :daisy {:friends [:james :lucy]},
                         :john  {:friends [:james]}}))
;; =>
([:james :person/friend :lucy]
 [:james :person/friend :john]
 [:james :person/friend :daisy]
 [:lucy :person/friend :james]
 [:lucy :person/friend :daisy]
 [:daisy :person/friend :james]
 [:daisy :person/friend :lucy]
 [:john :person/friend :james])

然后使用诸如friendfriend-of-friend之类的自定义规则对事实执行Datalog查询。例如。找到:james的朋友:

(d/q '[:find [?f ...]
       :where (friend-of-friend ?p ?f)
       :in $ ?p %]
     friendship
     :james
     '[[(friend ?p ?f)
        [?p :person/friend ?f]]
       [(friend-of-friend ?p ?f)
        (friend ?p ?x)
        (friend ?x ?f)
        [(not= ?p ?f)]]])
;; => [:daisy :lucy]

其中

[:find [?f ...]
 :where (friend-of-friend ?p ?f)
 :in $ ?p %]

是查询,friendship是事实,并映射到$:james是查询的主题(映射到参数?p)和{{1 }}定义为:

%

注意:上面的示例正在使用datascript

答案 3 :(得分:0)

这是一种使用core.logic和事实解决传递朋友关系问题的方法。首先创建一个事实数据库,并db-rel表示友谊:

(require '[clojure.core.logic :refer :all]
         '[clojure.core.logic.pldb :as pldb])

(def input
  {:james {:friends [:lucy :john :daisy]},
   :lucy  {:friends [:james :daisy]},
   :daisy {:friends [:james :lucy]},
   :john  {:friends [:james]}})

(pldb/db-rel friend p1 p2)

(def friends
  (apply pldb/db
    (for [[p1 {:keys [friends]}] input
          p2 friends]
      [friend p1 p2])))

然后编写一个函数,该函数接受 friend ,并将答案绑定到所有 friend 朋友的朋友。

(defn friends-once-removed [f]
  (pldb/with-db friends
    (run* [q]
      (fresh [fs]
        (friend f fs)
        (friend fs q)))))

(friends-once-removed :james)
=> (:lucy :james :daisy :james :james)