3

我正在使用 clojure.spec 来验证地图条目的向量。向量看起来像:

[{:point {:x 30 :y 30}}
 {:point {:x 34 :y 33}}
 {:user "joe"}]

我想将规范构建为需要 1..N::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类型的任何想法?

此外,关于如何在向量中要求一个且仅一个::user条目和 1..N个条目的任何想法?::point

4

3 回答 3

5

这是您问题中数据的可能规范:

(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嵌套级别使用,但有很多选择
  • 通过尝试 REPL 中的每个子表单,可以及早发现规范中的小错误。
  • 在这种情况下,指定数据的相对困难是暗示这种数据形状对程序也很不方便。当您知道需要时,为什么要强制程序执行 O(N) 搜索:user

希望这可以帮助!

于 2017-06-17T14:36:12.837 回答
4

虽然 Stuart 的回答很有启发性并且解决了您的许多问题,但我认为它并没有涵盖您确保“一个且只有一个::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是一个映射条目,这就是传递给新自定义谓词的内容。

我应该注意到,虽然这有效,但它为您的验证添加了另一个线性扫描。不幸的是,我不知道规范是否提供了一种一次性完成的好方法。

于 2017-06-17T20:02:57.163 回答
2

Tim 和 Stuart 的回答解决了这个问题,而且信息量很大。我想指出,Clojure 规范还有一个特性,可用于指定点向量和用户的结构。

也就是说,规范允许使用正则表达式来指定序列。有关详细信息,请参阅规范指南

以下是使用序列规范的解决方案。这建立在以前的解决方案之上。

(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

于 2017-06-21T08:42:46.817 回答