在 clojure 中存储对象及其功能的最佳方式是什么?

What's the best way to store an object and its features in clojure?

一个对象至少有它的位置和尺寸,但可能还有其他字段,例如。

(def obj1 '(cup1 x 0 y 0 z 0 width 10 height 10))
(def obj2 '(cup2 x 0 y 0 z 0 width 10 height 10))
(def objs '(obj1 obj2))

如果我想访问和修改值、比较它们的名称或维度并允许我在将来添加新字段,那么存储这些类型对象的最有效方法是什么?

是否有像 python 中那样的有序字典之类的东西?

Clojure 映射非常常用于此类事情,通常以 Clojure 关键字作为键,但键可以是字符串、符号、数字、向量等,任何不可变的 Clojure 值:

(def obj1 {:name "cup1" :x 0 :y 0 :z 0 :width 10 :height 10})
(def obj2 {:name "cup2" :x 0 :y 0 :z 0 :width 10 :height 10})
(def objs [obj1 obj2])

Clojure 映射是无序的,可以非常快速地通过键查找以获取相应的值,并且可以快速“更新”,意思是“return 一个新映射,其中一个或多个键具有与之关联的新值",通过函数 assoc 和其他几个内置函数。

有第三方库提供的映射可以记住 key/value 对相对于彼此的插入顺序,但 Clojure 开发人员很少使用它们:https://github.com/clj-commons/ordered

如果所有字段都同等重要,那么 很好,但是查找单个对象会很慢,因为您必须遍历整个列表来查找它。如果每个对象的“名称”是一个 unique/primary 键,那么您可以使用映射的映射来非常简单(快速)地按名称查找单个对象。

(def objs
  {"cup1" {:x 0 :y 0 :z 0 :width 10 :height 10}
   "cup2" {:x 0 :y 0 :z 0 :width 10 :height 10}})

;; retrieve by name (will error if name is not found)
(get objs "cup1")

;; add (will replace any existing obj with the same name)
(assoc objs "cup3" {:x 0 :y 0 :z 0 :width 10 :height 10})

;; remove (will not error even if name is not found)
(dissoc objs "cup3")

;; update by name (will error if name is not found)
(assoc-in objs ["cup1" :x] 5)

;; get all objects without names (i.e. list of maps)
(vals objs)

;; get all objects with names
(map (fn [[n obj]]
       (assoc obj :name n))
     objs)

请注意,除非另有说明,否则Clojure中的所有内容都是不可变的,因此上述所有操作都会产生一个单独的数据结构,而不会影响原始数据。实际上没有任何东西被“更新”、“覆盖”或“删除”——Clojure 只是返回给你如果这些事情完成后数据会是什么样子的视图。

Clojure 主网站有一个专用于 domain modelling using maps 的部分。

要将您问题中的数据结构转换为这张地图中的地图,您可以:

;; from question
(def obj1 '(cup1 x 0 y 0 z 0 width 10 height 10))
(def obj2 '(cup2 x 0 y 0 z 0 width 10 height 10))
(def objs '(obj1 obj2))

;; since '(obj1 obj2) contains merely symbols, not references to obj1 and obj2,
;; it will be necessary to store it like this instead:
(def objs [obj1 obj2])

(defn map-keys
  "Applies function f to each key of map m.
   Ex: (map-keys inc {1 100 2 200}) -> {2 100 3 200}"
  [f m]
  (reduce-kv (fn f-of-k [acc k v]
               (assoc acc (f k) v))
             (empty m) m))

(defn symlist->map
  "Converts the OP's symbol list into a keywordized map with a stringified name.
   Ex: (symlist->map '(a x 1)) -> {:name \"a\" :x 1}"
  [symbol-list]
  (update (map-keys keyword
                    (apply hash-map (conj symbol-list 'name)))
          :name str))

(def obj-lookup
  (->> objs
       (map (comp (juxt :name identity) symlist->map))
       (into {})))

;; now you can
(get obj-lookup "cup1")

以上将名称转换为字符串,但您可以通过从 symlist->map 中删除 (update ... :name str) 将名称保留为符号(您甚至可以将 属性 名称保留为符号通过删除 (map-keys keyword ...))。事实上,几乎任何东西都可以成为 Clojure 哈希映射中的键(甚至是整个其他映射,尽管这不是常见的用例)只要正确实现 hashCodeequals.

如果出于某种原因你需要恢复到原来的结构,像这样的东西会起作用:

(map (fn [[obj-name obj-properties]]
       (seq (reduce (fn symbolify-entry [acc [k v]]
                      (conj acc (symbol k) v))
                    [(symbol obj-name)] obj-properties)))
     obj-lookup)

显然,如果您决定将所有键都保留为符号,那显然会更简单,因为不需要转换回符号:

(map (fn [[obj-name obj-properties]]
       (conj (flatten (seq obj-properties)) obj-name))
     obj-lookup)