2010-02-04 1 views
12

Какую функцию я могу поставить как FOO здесь, чтобы дать true в конце? Я играл с хэш-множеством (только правильно для первых 2 значений), conj и concat, но я знаю, что я не обрабатываю одноэлементное условие установки с помощью любого из них.Объединить список карт и объединить значения с наборами в Clojure

(defn mergeMatches [propertyMapList] 
    "Take a list of maps and merges them combining values into a set" 
    (reduce #(merge-with FOO %1 %2) {} propertyMapList)) 

(def in 
    (list 
     {:a 1} 
     {:a 2} 
     {:a 3} 
     {:b 4} 
     {:b 5} 
     {:b 6})) 

(def out 
    { :a #{ 1 2 3} 
     :b #{ 4 5 6} }) 

; this should return true 
(= (mergeMatches in) out) 

Какой самый идиоматический способ справиться с этим?

ответ

12

Это будет делать:

(let [set #(if (set? %) % #{%})] 
    #(clojure.set/union (set %) (set %2))) 

переписан более непосредственно на примере (Alex):

(defn to-set [s] 
    (if (set? s) s #{s})) 
(defn set-union [s1 s2] 
    (clojure.set/union (to-set s1) (to-set s2))) 
(defn mergeMatches [propertyMapList] 
    (reduce #(merge-with set-union %1 %2) {} propertyMapList)) 
+0

a) из-за ограничения вложенных анонимных функций оно также недопустимо. б) не слишком симпатичный! :) –

+0

Кроме того, я думаю, что это вернет набор карт вместо карты с установленными значениями. –

+1

@Alex: a) Нет такого ограничения. Вы не можете вложить анонимную функцию * литералы * (созданные с помощью '# (...)'), но вы можете вложить анонимные функции (хотя вам нужно использовать 'fn'). б) Вопрос вкуса, я думаю. :-) Кроме того, вы должны попробовать запустить его, так как это происходит, когда функция, которую она производит, действительно заставит ваше тестовое выражение возвращать 'true', если вместо' FOO' вместо 'FOO' в вашем выражении' reduce'. –

4

Я не использовал бы слиянием с для этого,

(defn fnil [f not-found] 
    (fn [x y] (f (if (nil? x) not-found x) y))) 
(defn conj-in [m map-entry] 
    (update-in m [(key map-entry)] (fnil conj #{}) (val map-entry))) 
(defn merge-matches [property-map-list] 
    (reduce conj-in {} (apply concat property-map-list))) 

user=> (merge-matches in) 
{:b #{4 5 6}, :a #{1 2 3}} 

fnil скоро станет частью ядра, поэтому вы можете игнорировать реализацию ... но он просто создает версию другой функции, которая может обрабатывать nil arg uments. В этом случае conj заменит # {} на nil.

Таким образом, редукция соединяется с набором для каждой клавиши/значения в списке прилагаемых карт.

+0

Мне нравится это как решение и что он использует материал, который перемещается в основные библиотеки. Решение от @amitrathore является одной и той же базовой идеей, но проще. Я еще не определился, действительно ли это функционально любой другой или нет. –

+0

Так что разница в том, что другое решение создает карту списков, а не карту наборов, которая не так хороша для меня. –

+0

При дальнейшем использовании проблема с этим решением заключается в том, что он не имеет отношения к случаю, когда входящие карты уже имеют в качестве значений. (Да, я добавляю требования поздно. :) Например: user => (println (mergeMatches (list {: a # {1 2 3}} {: a # {4 5 6}}))) {: a # {# {1 2 3} # {4 5 6}}} , когда я действительно хочу {: a # {1 2 3 4 5 6}}. \t (печатьln (mergeMatches in2)) –

2

Не очень красивый, но он работает.

(defn mergeMatches [propertyMapList] 
    (for [k (set (for [pp propertyMapList] (key (first pp))))] 
     {k (set (remove nil? (for [pp propertyMapList] (k pp))))})) 
+0

Очень умный. Я немного опасаюсь поддерживать код, используя много вложенных списков! :) Я думаю, что некоторые из других решений немного читабельны и понятны. –

+0

Да, он становится немного плотным, но я никогда не понимал, почему он не используется чаще в Clojure. См. Http://www.bestinclass.dk/index.php/2010/02/clojure-list-comprehension/ для некоторых более аккуратных примеров. – mac

2

Это похоже на работу:

(defn FOO [v1 v2] 
     (if (set? v1) 
      (apply hash-set v2 v1) 
      (hash-set v1 v2))) 
+0

Подобно @Chas, но проще, избегая объединения. –

4

Я не пишу это, но это было contributed по @amitrathore на Twitter:

(defn kv [bag [k v]] 
    (update-in bag [k] conj v)) 
(defn mergeMatches [propertyMapList] 
    (reduce #(reduce kv %1 %2) {} propertyMapList)) 
+0

В целом, я думаю, что это самое простое и наиболее понятное решение. Также полагается на update-in, как ответ @Timothy Pratley. Я думаю, что основная проблема с этим заключается в том, что он создает карту списков вместо карты наборов, что не дает мне ненужного дублирования. –

+0

изменение 'conj' на' (fnil conj # {}) 'делает трюк – DanLebrero

3

Другим решением, пополняемая @wmacgyver на Twitter на основе multimaps:

(defn add 
    "Adds key-value pairs the multimap." 
    ([mm k v] 
    (assoc mm k (conj (get mm k #{}) v))) 
    ([mm k v & kvs] 
    (apply add (add mm k v) kvs))) 
(defn mm-merge 
    "Merges the multimaps, taking the union of values." 
    [& mms] 
    (apply (partial merge-with union) mms)) 

(defn mergeMatches [property-map-list] 
    (reduce mm-merge (map #(add {} (key (first %)) (val (first %))) property-map-list)))  
+1

Мне нравится идея создания (или использования) библиотеки multimap для работы с этой структурой данных и просто опираясь на нее. В зависимости от того, насколько я в конечном итоге использую его, я мог бы пойти в этом направлении в долгосрочной перспективе. –

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