我有这个原子:
(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)是最惯用的方式。
答案 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])
然后使用诸如friend
和friend-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)