2014-12-25 4 views
2

В моем веб-приложении Clojure у меня есть различные пространства имен моделей с функциями, которые принимают карту как инструмент и как-то вставляют эту карту в базу данных. Я хотел бы иметь возможность вынимать только нужные ключи с карты, прежде чем делать вставку.Фильтрация карты на основе ожидаемых ключей

Основным примером этого является:

(let [msg-keys [:title :body] 
     msg {:title "Hello" :body "This is an example" :somekey "asdf" :someotherkey "asdf"}] 
    (select-keys msg msg-keys)) 

;; => {:title "Hello" :body "This is an example"} 

select-keys не вариант, когда карта является довольно сложным, и я хотел бы, чтобы выбрать определенный набор вложенных ключей:

(let [person {:name {:first "john" :john "smith"} :age 40 :weight 155 :something {:a "a" :b "b" :c "c" :d "d"}}] 
    (some-select-key-fn person [:name [:first] :something [:a :b]])) 

;; => {:name {:first "john"} :something {:a "a" :b "b"}} 

Is есть ли способ сделать это с помощью основных функций? Есть ли способ сделать это исключительно с деструктурированием?

ответ

3

Эта тема была discussed in the Clojure Google Group наряду с несколькими решениями.

Разрушение, вероятно, является самым близким к «ядру», и может быть прекрасным решением, если ваша проблема довольно статична, и карта имеет все ожидаемые ключи (таким образом, избегая nil). Это может выглядеть следующим образом:

(let [person {:name {:first "john" :john "smith"} :age 40 :weight 155 :something {:a "a" :b "b" :c "c" :d "d"}} 
     {{:keys [first]} :name {:keys [a b]} :something} person] 
    {:name {:first first} :something {:a a :b b}}) 
;; => {:name {:first "john"}, :something {:a "a", :b "b"}} 

Ниже приведен обзор решений в потоке Clojure Google Group, применительно к вашей карте образца. У каждого из них есть другое мнение о том, как указать вложенные ключи для выбора.

Вот Christophe Grand «s solution:

(defprotocol Selector 
    (-select [s m])) 

(defn select [m selectors-coll] 
    (reduce conj {} (map #(-select % m) selectors-coll))) 

(extend-protocol Selector 
    clojure.lang.Keyword 
    (-select [k m] 
    (find m k)) 
    clojure.lang.APersistentMap 
    (-select [sm m] 
    (into {} 
      (for [[k s] sm] 
      [k (select (get m k) s)])))) 

Используя это требует немного измененный синтаксис:

(let [person {:name {:first "john" :john "smith"} :age 40 :weight 155 :something {:a "a" :b "b" :c "c" :d "d"}}] 
    (select person [{:name [:first] :something [:a :b]}])) 
;; => {:something {:b "b", :a "a"}, :name {:first "john"}} 

Вот решение Moritz Ульриха (он предупреждает, что она не работает на картах seqs as keys):

(defn select-in [m keyseq] 
    (loop [acc {} [k & ks] (seq keyseq)] 
    (if k 
     (recur 
     (if (sequential? k) 
      (let [[k ks] k] 
      (assoc acc k 
        (select-in (get m k) ks))) 
      (assoc acc k (get m k))) 
     ks) 
     acc))) 

Для этого требуется другое скольжение ждении модифицированный синтаксис:

(let [person {:name {:first "john" :john "smith"} :age 40 :weight 155 :something {:a "a" :b "b" :c "c" :d "d"}}] 
    (select-in person [[:name [:first]] [:something [:a :b]]])) 
;; => {:something {:b "b", :a "a"}, :name {:first "john"}} 

Вот решение Jay Fields в:

(defn select-nested-keys [m top-level-keys & {:as pairs}] 
    (reduce #(update-in %1 (first %2) select-keys (last %2)) (select-keys m top-level-keys) pairs)) 

Он использует другой синтаксис:

(let [person {:name {:first "john" :john "smith"} :age 40 :weight 155 :something {:a "a" :b "b" :c "c" :d "d"}}] 
    (select-nested-keys person [:name :something] [:name] [:first] [:something] [:a :b])) 
;; => {:something {:b "b", :a "a"}, :name {:first "john"}} 

Вот Baishampayan Гуз-х solution:

(defprotocol ^:private IExpandable 
      (^:private expand [this])) 


(extend-protocol IExpandable 
    clojure.lang.Keyword 
    (expand [k] {k ::all}) 

    clojure.lang.IPersistentVector 
    (expand [v] (if (empty? v) 
       {} 
       (apply merge (map expand v)))) 

    clojure.lang.IPersistentMap 
    (expand [m] 
    (assert (= (count (keys m)) 1) "Number of keys in a selector map can't be more than 1.") 
    (let [[k v] (-> m first ((juxt key val)))] 
      {k (expand v)})) 

    nil 
    (expand [_] {})) 


(defn ^:private extract* [m selectors expand?] 
    (let [sels (if expand? (expand selectors) selectors)] 
    (reduce-kv (fn [res k v] 
       (if (= v ::all) 
        (assoc res k (m k)) 
        (assoc res k (extract* (m k) v false)))) 
       {} sels))) 

(defn extract 
    "Like select-keys, but can select nested keys. 

    Examples - 

    (extract [{:b {:c [:d]}} :g] {:a 1 :b {:c {:d 1 :e 2}} :g 42 :xxx 11}) 
    ;=> {:g 42, :b {:c {:d 1}}} 

    (extract [:g] {:a 1 :b {:c {:d 1 :e 2}} :g 42 :xxx 11}) 
    ;=> {:g 42} 

    (extract [{:b [:c]} :xxx] {:a 1 :b {:c {:d 1 :e 2}} :g 42 :xxx 11}) 
    ;=> {:b {:c {:d 1, :e 2}}, :xxx 11} 

    Also see - exclude" 
    [selectors m] 
    (extract* m selectors true)) 

Он использует другой синтаксис (и параметры меняются на противоположные):

(let [person {:name {:first "john" :john "smith"} :age 40 :weight 155 :something {:a "a" :b "b" :c "c" :d "d"}}] 
    (extract [{:name [:first]} {:something [:a :b]}] person)) 
;; => {:name {:first "john"}, :something {:a "a", :b "b"}} 
0

Лучше всего использовать клавиши выбора для каждой вложенной части структуры.

(-> person 
    (select-keys [:name :something]) 
    (update-in [:name] select-keys [:first]) 
    (update-in [:something] select-keys [:a :b])) 

Конечно, вы можете использовать обобщенную версию выше для реализации синтаксиса вы предлагаете в функции (с reduce, а не -> формы, скорее всего, и рекурсивные вызовы для каждого вложенного подбора ключей) , Деструктурирование не помогло бы, это делает привязанные вложенные данные удобными, но на самом деле это не так полезно для построения значений.

Вот как я бы сделал это с reduce и рекурсией:

(defn simplify 
    [m skel] 
    (if-let [kvs (not-empty (partition 2 skel))] 
    (reduce (fn [m [k nested]] 
       (if nested 
       (update-in m [k] simplify nested) 
       m)) 
      (select-keys m (map first kvs)) 
      kvs) 
    m)) 

отметить, что предложенный ваш аргумент формат неудобно, поэтому я изменил его немного

user=> (simplify {:name {:first "john" :john "smith"} 
        :age 40 
        :weight 155 
        :something {:a "a" :b "b" :c "c" :d "d"}} 
       [:name [:first nil] :something [:a nil :b nil]]) 
{:something {:b "b", :a "a"}, :name {:first "john"}} 

синтаксиса вы предлагаете потребуете более сложная реализация

+0

отредактировано. использование 'clojure.walk' потребует какой-то конструкции, состоящей из состояний, и, вероятно, будет абсолютным беспорядком. Я ничего не знаю в 'clojure.data. *', Который бы помог здесь ('clojure.data' - это семейство пакетов, каждое из которых имеет свое собственное репо и т. Д.), – noisesmith

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

  • Нет связанных вопросов^_^