2

私の Clojure webapp には、マップを引数として取り、そのマップを何らかの方法でデータベースに挿入する関数を含むさまざまなモデルの名前空間があります。挿入する前に、マップから必要なキーだけを取り出したいと思います。

これの基本的な例は次のとおりです。

(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"}}

コア機能でこれを行う方法はありますか? これを純粋に破壊で行う方法はありますか?

4

2 に答える 2

3

このトピックは、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 グループ スレッドのソリューションの調査です。選択するネストされたキーを指定する方法はそれぞれ異なります。

Christophe Grandソリューションは次のとおりです。

(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 Ulrich の解決策です (彼は、seq をキーとして持つマップでは機能しないと警告しています)。

(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"}}

バイシャンパヤン・ゴースのソリューションは次のとおりです。

(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"}}
于 2014-12-26T00:02:04.577 に答える
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"}}

あなたが提案する構文は、より複雑な実装を必要とします

于 2014-12-25T22:10:35.873 に答える