我正在使用clojure.spec来验证地图条目的向量。矢量看起来像:
*.ts
我想将规范结构化为需要1..N [{:point {:x 30 :y 30}}
{:point {:x 34 :y 33}}
{:user "joe"}]
个条目,并且只需要一个::point
条目。
这是我(不成功)尝试构建此规范:
::user
当我只运行一个(s/def ::coord (s/and number? #(>= % 0)))
(s/def ::x ::coord)
(s/def ::y ::coord)
(s/def ::point (s/keys :req-un [::x ::y]))
(s/def ::user (s/and string? seq))
(s/def ::vector-entry (s/or ::pt ::user))
(s/def ::my-vector (s/coll-of ::vector-entry :kind vector))
条目的验证时,它可以工作:
::point
有关如何构建spec> (s/valid? ::point {:point {:x 0 :y 0}})
true
spec> (s/valid? ::my-vector [{:point {:x 0 :y 0}}])
false
部分的任何想法,以便向量条目可以是s/or
或::user
类型吗?
此外,有关如何在向量中仅需要一个::point
条目和1..N ::user
条目的任何想法?
答案 0 :(得分:4)
以下是您问题中数据的可能规范:
(require '[clojure.spec.alpha :as s])
(s/def ::coord nat-int?)
(s/def ::x ::coord)
(s/def ::y ::coord)
(s/def ::xy (s/keys :req-un [::x ::y]))
(s/def ::point (s/map-of #{:point} ::xy))
(s/def ::username (s/and string? seq))
(s/def ::user (s/map-of #{:user} ::username))
(s/def ::vector-entry (s/or :point ::point :user ::user))
(s/def ::my-vector (s/coll-of ::vector-entry :kind vector))
(s/valid? ::point {:point {:x 0 :y 0}})
(s/valid? ::my-vector [{:point {:x 0 :y 0}}])
(s/valid? ::my-vector [{:point {:x 0 :y 0}} {:user "joe"}])
一些观察结果:
or
规范要求为规格命名。:point
或:user
对不同项目进行标记需要间接级别,我在顶部使用map-of
,在嵌套级别使用keys
但是有很多选择:user
时,为什么强制程序进行O(N)搜索?希望这有帮助!
答案 1 :(得分:4)
虽然斯图尔特的答案非常有启发性并解决了许多你的问题,但我认为它不符合你确保“只有一个::user
条目”的标准。
重复他的回答:
(s/def ::coord nat-int?)
(s/def ::x ::coord)
(s/def ::y ::coord)
(s/def ::xy (s/keys :req-un [::x ::y]))
(s/def ::point (s/map-of #{:point} ::xy))
(s/def ::username (s/and string? seq))
(s/def ::user (s/map-of #{:user} ::username))
(s/def ::vector-entry (s/or :point ::point
:user ::user))
(s/def ::my-vector (s/and (s/coll-of ::vector-entry
:kind vector)
(fn [entries]
(= 1
(count (filter (comp #{:user}
key)
entries))))))
(s/valid? ::point {:point {:x 0 :y 0}})
;; => true
(s/valid? ::my-vector [{:point {:x 0 :y 0}}])
;; => false
(s/valid? ::my-vector [{:point {:x 0 :y 0}}
{:user "joe"}])
;; => true
(s/valid? ::my-vector [{:point {:x 0 :y 0}}
{:point {:x 1 :y 1}}
{:user "joe"}])
;; => true
(s/valid? ::my-vector [{:point {:x 0 :y 0}}
{:user "joe"}
{:user "frank"}])
;; => false
重要的补充是::my-vector
的规范。请注意,s/or
的一致输出是一个映射条目,这是传递给新自定义谓词的内容。
我应该注意到,虽然这有效,但它会为您的验证添加另一个线性扫描。不幸的是,我不知道spec是否提供了一次通过的好方法。
答案 2 :(得分:2)
蒂姆和斯图亚特的答案解决了这个问题并且提供了非常丰富的信息。我想指出的是,Clojure规范还有一个功能,可用于指定点向量和用户的结构。
即,spec允许使用正则表达式来指定序列。有关更多信息,请参阅the Spec guide。
以下是使用序列规格的解决方案。这建立在以前的解决方案之上。
(require '[clojure.spec.alpha :as s])
(s/def ::coord nat-int?)
(s/def ::x ::coord)
(s/def ::y ::coord)
(s/def ::xy (s/keys :req-un [::x ::y]))
(s/def ::point (s/map-of #{:point} ::xy))
(s/def ::username (s/and string? seq))
(s/def ::user (s/map-of #{:user} ::username))
(s/def ::my-vector (s/cat :points-before (s/* ::point)
:user ::user
:points-after (s/* ::point)))
(s/valid? ::point {:point {:x 0 :y 0}})
;; => true
(s/valid? ::my-vector [{:point {:x 0 :y 0}}])
;; => false
(s/valid? ::my-vector [{:point {:x 0 :y 0}}
{:user "joe"}])
;; => true
(s/valid? ::my-vector [{:point {:x 0 :y 0}}
{:point {:x 1 :y 1}}
{:user "joe"}])
;; => true
(s/valid? ::my-vector [{:point {:x 0 :y 0}}
{:user "joe"}
{:user "frank"}])
;; => false
(s/valid? ::my-vector [{:point {:x 0 :y 0}}
{:user "joe"}
{:point {:x 1 :y 1}}])
;; => true
如果最后需要::user
条目,则可以很容易地进行调整。