在 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 哈希映射中的键(甚至是整个其他映射,尽管这不是常见的用例)只要正确实现 hashCode
和 equals
.
如果出于某种原因你需要恢复到原来的结构,像这样的东西会起作用:
(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)
一个对象至少有它的位置和尺寸,但可能还有其他字段,例如。
(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
如果所有字段都同等重要,那么
(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 哈希映射中的键(甚至是整个其他映射,尽管这不是常见的用例)只要正确实现 hashCode
和 equals
.
如果出于某种原因你需要恢复到原来的结构,像这样的东西会起作用:
(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)