2015-01-12 2 views
3

Ищу функцию присоединения, которая, как присоединиться к SQL, например:Есть ли функция clojure для «объединения» двух списков карт?

Вот два списка карт:

(def a [{:user_id 1 :name "user 1"} 
     {:user_id 2 :name "user 2"}]) 

(def b [{:user_id 2 :email "e 2"} 
     {:user_id 1 :email "e 1"}]) 

Я хочу присоединиться к а и Ь на user_id, чтобы получить:

[{:user_id 1 :name "user 1" :email "e 1"} 
{:user_id 2 :name "user 2" :email "e 2"}] 

Есть ли какая-либо функция в clojure или другой библиотеке, которая могла бы это достичь?

+0

Af ter Я отправил свой ответ, я заметил [этот вопрос] (http://stackoverflow.com/questions/10209218/clojure-merging-two-array-of-maps). Я не уверен, что вопрос строго один и тот же; непонятно, что задается, потому что пример перезаписывает значение одного ключа (': b' на карте, содержащей' 2' для ': a'). Однако ответы на вопрос - это ответы на ваш вопрос. Поэтому я думаю, что это считается дублирующим вопросом. Поскольку мой ответ отличается, я оставил его здесь, но, может быть, я должен отправить его другому вопросу? Не уверен. – Mars

ответ

8

clojure.set/join сделаю.

(use 'clojure.set) 

(clojure.set/join a b) ; => #{{:email "e 1", :name "user 1", :user_id 1} {:email "e 2", :name "user 2", :user_id 2}} 

без предоставления 3-го аргумента, функция будет вступать во всех общих ключей:

(def a [{:id1 1 :id2 2 :name "n 1"} {:id1 2 :id2 3 :name "n 2"}]) 
(def b [{:id1 1 :id2 2 :url "u 1"} {:id1 2 :id2 4 :url "u 2"}]) 
(def c [{:id1 1 :id2 2 :url "u 1"} {:id1 2 :url "u 2"}]) ; :id2 is missing in 2nd record 

(clojure.set/join a b) ; #{{:name "n 1", :url "u 1", :id1 1, :id2 2}} 
(clojure.set/join a c) ; #{{:name "n 2", :url "u 2", :id1 2, :id2 3} {:name "n 1", :url "u 1", :id1 1, :id2 2}} 

Чтобы присоединиться к и б только на ID1:

(clojure.set/join a b {:id1 :id1}) ; #{{:name "n 2", :url "u 2", :id1 2, :id2 4} {:name "n 1", :url "u 1", :id1 1, :id2 2}} 

Мы даже можем присоединиться разными ключами из различных коллекций:

(clojure.set/join a b {:id1 :id2}) ; #{{:name "n 2", :url "u 1", :id1 1, :id2 2}} 
+0

'(в {} (clojure.set/join a b))' для возврата карты. –

1

Я не думаю, что есть какая-то простая функция, которая уже делает это, но я могу ошибаться.

Если вы знаете, что каждый user_id существует в каждой последовательности, то вы можете просто сортировать по user_id, а затем применить сливаться в соответствующие карты:

(defn sort-by-user-id 
    [m] 
    (sort #(< (:user_id %1) (:user_id %2)) m)) 

(map merge (sort-by-user-id a) (sort-by-user-id b)) 
; => ({:email "e 1", :name "user 1", :user_id 1} {:email "e 2", :name "user 2", :user_id 2}) 

Если вы не можете предположить, что все же user_id существуют в каждой последовательности, я думаю, вам нужно сделать что-то немного сложнее, чтобы соответствовать user_id s. Я предполагаю, что если карта имени не имеет соответствующей карты электронной почты, вы хотите оставить карту имен без изменений (или наоборот для отсутствующих карт имен). Если нет, то одним из вариантов будет удаление этих карт и использование метода, приведенного выше.

Здесь вы можете объединить соответствующие названия и карты электронной почты. Мы можем использовать user_id s как ключи в карте карт, чтобы соответствовать соответствующим картам. Сначала создает карту, содержащую все карты с user_ids как ключи, например, так:

(def az (zipmap (map :user_id a) a)) ; => {2 {:name "user 2", :user_id 2}, 1 {:name "user 1", :user_id 1}} 
(def bz (zipmap (map :user_id b) b)) ; => {1 {:email "e 1", :user_id 1}, 2 {:email "e 2", :user_id 2}} 

Затем объединить отдельные карты, как это, обнажая из ключей в конце процесса:

(vals (merge-with merge az bz)) 
; => ({:email "e 2", :name "user 2", :user_id 2} {:email "e 1", :name "user 1", :user_id 1}) 

Собираем все вместе:

(defn map-of-maps 
    [cm] 
    (zipmap (map :user_id cm) cm)) 

(defn merge-maps 
    [& cms] 
    (vals 
    (apply merge-with merge 
      (map map-of-maps cms)))) 

Давайте удостоверимся, что он работает с отсутствующими user_id S:

(def a+ (conj a {:name "user 3", :user_id 3})) 
(def b+ (conj b {:email "e 4", :user_id 4})) 

(merge-maps a+ b+) 
; => ({:email "e 4", :user_id 4} {:name "user 3", :user_id 3} {:email "e 2", :name "user 2", :user_id 2} {:email "e 1", :name "user 1", :user_id 1}) 

Я не удивлюсь, если есть более простые или более элегантные методы. Это только одна стратегия, которая пришла мне в голову.

+2

Спасибо! Я обнаружил, что clojure.set/join может это сделать. Не так очевидно, чтобы искать функции «set'.clojure.set/join», может получить продукт Cartision из двух наборов и также может принимать пары ключевых пар, чтобы присоединиться к ним , – user2219372

3

Другим варианта, немного проще, я думаю:

user=> (map #(apply merge %) (vals (group-by :user_id (concat a b)))) 
({:email "e 1", :name "user 1", :user_id 1} {:email "e 2", :name "user 2", :user_id 2}) 

group-by создает отображение из :user_id на все карты, содержащее заданное значение, vals получает только значение (каждые один вектор), и, наконец, каждый вектор значений, они объединяются.

Смежные вопросы