通过键值,地图中的位置交换地图查找中嵌套的特定项目

Swap nested specific item in map finding by key value, the position in map

我们有一个带有此示例数据的游标或原子图:

    #<Cursor: [:customer] {:name Diego Peña, 

    :addresses [{:id 23, :province Madrid, :country 1, :descripcion aaeeeeeeee iii oooo4444, :locality Gali gali, :country_name SPAIN, :direccion Street Cierva, :id 3, :postalcode 30203, :principal true, :customer 17} 

{:id 35, :province Madrid, :country nil, :descripcion yyy lalala3, :locality Lalala, :direccion calle Maria 3 , :postalcode 333, :principal false, :customer 17} 

{:id 6, :province Madrid, :country 2, :descripcion otra direccioncita444, :locality Leleele, :country_name SPAIN, :direccion Direccion calle Ooo, :postalcode 1236, :main false, :customer 17} 

{:id 27, :province Madrid, :country 1, :descripcion grandisima, :locality Alcantarilla, :country_name SPAIN, :direccion C/ 3 Mayo, :postalcode 3001, :main false, :customer 17}

]}>

我需要通过 id 更改搜索地址的值。我已经设法通过 id 的值找到地址:

(defn get-address [pk]
   (->> @db/customer :addresses (filter #(= (int pk) (int (:id %)))) first)
)

我可以用这个更改所有地址::ok #(swap! db/customer assoc-in [:addresses] %)})。我需要从 API 响应中更改 特定地址 的数据。

我快要得到它了,但是通过这种方法我丢失了项目的位置或索引:#(swap! db/client assoc-in [:addresses ¿position or index in map?] %)我们有项目地址的 ID。

也许这种方法是错误的,有更好的方法吗?

assoc, assoc-in, update, and update-in 函数也适用于向量。在 Clojure 中,向量是关联数据结构,其中键是数字索引 (O..n),值是位置 n.

处的项目

所以你可以这样做:

(assoc [:a :b :c] 1 :new-value)
;;      ^  ^  ^
;;      0  1  2
;; => [:a :new-value :c]

根据您的示例,您将需要:

(defn address-index-by-id
  "Take an `address` map and look it up by `:id` in the `addresses` list.
   Return the numeric index where it was found, nil if not found."
  [address addresses]
  (->> (map-indexed vector addresses) 
       ;; produce a seq `([0 val-at-index-0] … [n val-at-index-n])`
       (filter (fn [[_index {:keys [id]}]] (= id  (:id address)))) ;; filter by id
       (ffirst) ;; get the index of the first match
       ))

(defn set-address
  "Take a `customer` map and an `address` map. Will put the `address` in the
  customer's addresses list. If an address with the same :id key is already
  present in this list, it will be overwritten."
  [customer address]
  (if-let [existing-index (address-index-by-id address (:addresses customer))]
    (assoc-in customer [:addresses existing-index] address)
    (update customer :addresses conj address)))

用法:

(set-address {:name      "Diego Peña"
              :addresses []}
             {:id       1
              :province "Madrid"})
;; => {:name "Diego Peña", :addresses [{:id 1, :province "Madrid"}]}

(-> {:name      "Diego Peña"
     :addresses [{:id       1
                  :province "Madrid"
                  :main     true}
                 {:id       2
                  :province "Barcelona"
                  :main     false}]}
    (set-address {:id       2
                  :province "Barcelona"
                  :main     true})
    (set-address {:id       1
                  :province "Madrid"
                  :main     false}))
;; => {:name "Diego Peña", :addresses [{:id 1, :province "Madrid", :main false} {:id 2, :province "Barcelona", :main true}]}

;; And of course if your `customer` is stored in an Atom:
(swap! customer set-address {:id 1, :province "Madrid", :main true})

看起来您正在从某种数据库中提取数据。如果是这样,您应该让数据库搜索有问题的 ID。然后您可以读取或更新该记录。

如果您确实需要在Clojure 中进行,您可以使用tupelo.forest library 搜索所需的客户地图。以下是您的数据示例:

(ns tst.demo.core
  (:use tupelo.core tupelo.test)
  (:require [tupelo.forest :as tf]))

(def customer
  {:name "Diego Peña",
   :addresses
         [{:id           23
           :province     "Madrid"
           :country      1
           :descripcion  " aaeeeeeeee iii oooo4444"
           :locality     "Gali gali"
           :country_name "SPAIN"
           :direccion    "Street Cierva"
           :postalcode   30203
           :principal    true
           }
          {:id          35
           :province    "Madrid"
           :country     nil
           :descripcion "yyy lalala3"
           :locality    "Lalala"
           :direccion   "calle Maria 3"
           :postalcode  333
           :principal   false
           :customer    17}
          {:id           6
           :province     "Madrid"
           :country      2
           :descripcion  "otra direccioncita444"
           :locality     "Leleele"
           :country_name "SPAIN"
           :direccion    "Direccion calle Ooo"
           :postalcode   1236
           :main         false
           :customer     17}
          {:id           27
           :province     "Madrid"
           :country      1
           :descripcion  "grandisima"
           :locality     "Alcantarilla"
           :country_name "SPAIN"
           :direccion    "C / 3 Mayo"
           :postalcode   3001
           :main         false
           :customer     17}
          ]})

和一些用于查找 :id 35

客户的代码
(dotest
  (tf/with-forest (tf/new-forest)
    (let [root-hid        (tf/add-tree-edn customer)
          cust-num        35
          paths-found     (tf/find-paths root-hid [:**
                                                   {:tag :tupelo.forest/entry, :tupelo.forest/key :id}
                                                   {:tupelo.forest/value cust-num}])
          cust-path-rev   (reverse (last paths-found))
          cust-entity-hid (xthird cust-path-rev)
          ]

      (is= (tf/hid->bush (xfirst cust-path-rev)) [#:tupelo.forest{:value 35, :index nil}])

      (is= (tf/hid->tree cust-entity-hid)
        {:tag                 :tupelo.forest/entity,
         :tupelo.forest/index 1,
         :tupelo.forest/kids  [{:tag                :tupelo.forest/entry,
                                :tupelo.forest/key  :locality,
                                :tupelo.forest/kids [#:tupelo.forest{:kids [], :value "Lalala", :index nil}]}
                               {:tag                :tupelo.forest/entry,
                                :tupelo.forest/key  :customer,
                                :tupelo.forest/kids [#:tupelo.forest{:kids [], :value 17, :index nil}]}
                               {:tag                :tupelo.forest/entry,
                                :tupelo.forest/key  :descripcion,
                                :tupelo.forest/kids [#:tupelo.forest{:kids [], :value "yyy lalala3", :index nil}]}
                               {:tag                :tupelo.forest/entry,
                                :tupelo.forest/key  :direccion,
                                :tupelo.forest/kids [#:tupelo.forest{:kids [], :value "calle Maria 3", :index nil}]}
                               {:tag                :tupelo.forest/entry,
                                :tupelo.forest/key  :id,
                                :tupelo.forest/kids [#:tupelo.forest{:kids [], :value 35, :index nil}]}
                               {:tag                :tupelo.forest/entry,
                                :tupelo.forest/key  :postalcode,
                                :tupelo.forest/kids [#:tupelo.forest{:kids [], :value 333, :index nil}]}
                               {:tag                :tupelo.forest/entry,
                                :tupelo.forest/key  :principal,
                                :tupelo.forest/kids [#:tupelo.forest{:kids [], :value false, :index nil}]}
                               {:tag                :tupelo.forest/entry,
                                :tupelo.forest/key  :province,
                                :tupelo.forest/kids [#:tupelo.forest{:kids [], :value "Madrid", :index nil}]}
                               {:tag                :tupelo.forest/entry,
                                :tupelo.forest/key  :country,
                                :tupelo.forest/kids [#:tupelo.forest{:kids [], :value nil, :index nil}]}]})

您可以将数据从内部树格式转换回 EDN 数据:

      (is= (tf/hid->edn cust-entity-hid)
        {:locality    "Lalala",
         :customer    17,
         :descripcion "yyy lalala3",
         :direccion   "calle Maria 3",
         :id          35,
         :postalcode  333,
         :principal   false,
         :province    "Madrid",
         :country     nil})

      )))

您从未真正说过要如何更改数据。同样,最好使用数据库而不是更改 Clojure 数据结构来执行此操作。


更新

另一种选择是使用像 clojure.walk/postwalk 或增强的 tupelo.core/walk-with-parents 这样的递归遍历。客户 ID #35

的大写街道名称示例
(dotest
  (let [modified (t/walk-with-parents customer
                   {:enter (fn [parents item]
                             (with-nil-default item
                               (when (and (map? item)
                                       (t/submap? {:id 35} item))
                                 (spyx-pretty item)
                                 (update item :direccion str/upper-case))))})]
    (is= modified {:name "Diego Peña",
                   :addresses
                         [{:locality     "Gali gali",
                           :descripcion  " aaeeeeeeee iii oooo4444",
                           :country_name "SPAIN",
                           :direccion    "Street Cierva",
                           :id           23,
                           :postalcode   30203,
                           :principal    true,
                           :province     "Madrid",
                           :country      1}
                          {:locality    "Lalala",
                           :customer    17,
                           :descripcion "yyy lalala3",
                           :direccion   "CALLE MARIA 3",
                           :id          35,
                           :postalcode  333,
                           :principal   false,
                           :province    "Madrid",
                           :country     nil}
                          {:locality     "Leleele",
                           :customer     17,
                           :descripcion  "otra direccioncita444",
                           :country_name "SPAIN",
                           :direccion    "Direccion calle Ooo",
                           :id           6,
                           :postalcode   1236,
                           :main         false,
                           :province     "Madrid",
                           :country      2}
                          {:locality     "Alcantarilla",
                           :customer     17,
                           :descripcion  "grandisima",
                           :country_name "SPAIN",
                           :direccion    "C / 3 Mayo",
                           :id           27,
                           :postalcode   3001,
                           :main         false,
                           :province     "Madrid",
                           :country      1}]})))