Как я могу создать список из вложенной карты в Clojure? - PullRequest
1 голос
/ 08 июня 2019

У меня есть этот атом:

(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 ]

3 голосов
/ 08 июня 2019

Правда, вам не нужен промежуточный атом.

(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 голос
/ 08 июня 2019

Я бы использовал атомы только на «самом низком уровне» всего приложения.Они являются хранилищем для разделяемых, одновременно доступных, изменяемых, ... данных (часто глобальных).

Напишите функции, которые вам нужны, так, чтобы они были независимы от таких вещей, насколько это возможно.Это значительно облегчает тестирование, если вы получаете чистые функции.Если вы хотите накапливать данные, формируйте их;есть 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)
0 голосов
/ 10 июня 2019

Вот способ решить переходную проблему друзей с помощью 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])))

Затем напишите функцию, которая принимает друга и связывает ответ с друзьями всех этих друзей друзей.

(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)
0 голосов
/ 09 июня 2019

Другой способ (или вы называете это «косвенным» способом) выполнения этого типа запроса - через 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. Например. найти друга друга :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), а % является правилами, определенными как:

[[(friend ?p ?f)                        ; 1. What is a friend?
  [?p :person/friend ?f]]               ;   ?p has an attribute :person/friend defined with ?f
 [(friend-of-friend ?p ?f)              ; 2. Friend of friend
  (friend ?p ?x)                        ;   ?p and ?x are friends (base on #1)
  (friend ?x ?f)                        ;   ?x and ?f are friends
  [(not= ?p ?f)]]]                      ;   ?p and ?f not same person

Примечание: приведенный выше пример использует datascript

...