2010-05-02 13 views
31

Mam trwałą mapę, którą chcę filtrować. Coś takiego:Jak filtrować trwałą mapę w Clojure?

(filter #(-> % val (= 1)) {:a 1 :b 1 :c 2}) 

Powyższy wychodzi jako ([:a 1] [:b 1]) (leniwy kolejność zgłoszeń map). Jednak chcę otrzymać {:a 1 :b 1}.

Jak mogę filtrować mapę tak, aby pozostała mapą bez potrzeby jej odbudowywania z sekwencji wpisów na mapie?

Odpowiedz

45

a drugi:

(let [m {:a 1 :b 2 :c 1}] 
    (select-keys m (for [[k v] m :when (= v 1)] k))) 
17
(into {} (filter #(-> % val (= 1)) {:a 1 :b 1 :c 2})) 

Oczywiście to robi odbudować mapę z sekwencji wpisów map, ale nie ma sposobu, wokół niego. Jeśli chcesz filtrować wpisy według wartości, będziesz musiał je kolejno przeglądać, aby sprawdzić, które wartości pasują do Twojego predykatu, a które nie.

aktualizowane (patrz komentarz poniżej):

z nowo wprowadzonej keep funkcji, źródło, które można zobaczyć here (powinien działać dobrze w Clojure 1.1 jeśli chcesz backport), to wydaje się ładny sposób się do tego zabrać jeśli nie używasz nil jako kluczowy:

(let [m {:a 1 :b 1 :c 2}] 
    (apply dissoc m (keep #(-> % val (= 1) (if nil (key %))) m))) 
; => {:a 1, :b 1} 

Ponadto, jeśli faktycznie zobaczyć spowolnienie związane z przebudowa swoją mapę, można użyć na etapie przebudowy przemijające mapę :

(persistent! (loop [m (transient {}) 
        to-go (seq [[:a 1] [:b 2]])] 
       (if to-go 
       (recur (apply assoc! m (first to-go)) 
         (next to-go)) 
       m))) 
; => {:a 1, :b 2} 
+0

Cóż, teoretycznie * * można mieć ono filtrowane przez wartość bez odbudowy wracając mapę z kluczami dissoc-owych, które odpowiadają wartościom niedopasowanych. Miałem nadzieję, że jest to obsługiwany język. –

+0

Ok, rozumiem co masz na myśli. Dodam dwa sposoby, aby to zrobić w ciągu sekundy, ale zauważ, że prawdopodobnie nie dostrzeżesz wielkiego zysku w dziale wydajności (chyba że masz naprawdę ogromną mapę i rozproszysz tylko małą liczba kluczy). –

+0

Hm, w rzeczywistości to nie tyle "dwa sposoby na zrobienie tego", co "jeden sposób na zrobienie tego i jeden sposób na mniej obaw o odbudowę". Nie żebyś musiał się martwić. :-) –

3

Per swoim komentarzu do Michała Marczyka:

(defn filter* [f map] 
    (reduce (fn [m [k v :as x]] 
      (if-not (f x) 
       (dissoc m k) 
       m)) 
      map map)) 

user> (filter* #(-> % val (= 1)) {:a 1 :b 1 :c 2}) 
{:a 1, :b 1} 

Nie widzę że masz zamiar zdobyć wiele z tego vs. wersji Michała.

3

Need przemierzać wszystkie wpisy, ale można wykorzystać Clojures uporczywe mapy:

(apply dissoc my-map (for [[k v] my-map :when (not= v 1)] k)) 
1

Starałem się na makro za to na podstawie wersji kotarak użytkownika. To moje pierwsze makro robi coś pożytecznego, więc proszę o komentarz ze mną i komentarze.

(defmacro filter-map [bindings pred m] 
    `(select-keys ~m 
    (for [~bindings ~m 
     :when ~pred] 
     ~(first bindings) 
    ) 
) 
) 

Przykład

user=> (filter-map [key val] (even? (:attr val)) {:a {:attr 2} :b {:attr 3} :c {:attr 4}}) 
{:c {:attr 4}, :a {:attr 2}}