如何使用 clojure.spec 生成相互关联的参数?
How to generate inter-related args with clojure.spec?
我有一个方法可以从字符串对中删除公共前缀,并且一直在尝试为它创建一个生成器。生成随机字符串对是微不足道的,但我如何才能强制许多字符串对具有共同的前缀?简单地过滤对生成的示例数量不足,因此我正在尝试创建一个自定义生成器来满足要求。
这是我现在拥有的;它有效,但我想生成更好的参数:
(ns liblevenshtein.distance
(:require [clojure.spec.alpha :as spec]
[clojure.spec.gen.alpha :as gen]))
(spec/def ::word
(spec/and string? (complement nil?)))
(spec/def ::v-w (spec/cat :v ::word, :w ::word))
(spec/def ::non-empty-v-w
(spec/and ::v-w (fn [{:keys [v w]}]
(and (not-empty v)
(not-empty w)))))
(defn- drop-common-prefix [v w]
(loop [v v, a (.charAt v 0), s (.substring v 1),
w w, b (.charAt w 0), t (.substring w 1)]
(if (and (= a b)
(not-empty s)
(not-empty t))
(recur s (.charAt s 0) (.substring s 1)
t (.charAt t 0) (.substring t 1))
[v a s, w b t])))
(spec/fdef drop-common-prefix
:args ::non-empty-v-w
:ret (spec/tuple string? char? string?, string? char? string?)
:fn (fn [{{:keys [v w]} :args, [v' a s, w' b t] :ret}]
(and (= v' (str a s))
(.endsWith v v')
(= w' (str b t))
(.endsWith w w'))))
通过使用生成器进行试验,我得出了以下结论。它生成满足我要求的字符串对,但我不知道如何将它们拆分为我的函数的参数:
user=> (def prefix-pair-gen (gen/fmap (fn [[u v w]] [(str u v) (str u w)]) (spec/gen (spec/coll-of string? :type vector? :count 3))))
#'user/prefix-pair-gen
user=> (spec/def ::prefix-pair (spec/with-gen (spec/coll-of string? :type vector? :count 2) (fn [] prefix-pair-gen)))
:user/prefix-pair
user=> (gen/sample (spec/gen ::prefix-pair))
(["" ""]
["c" "cR"]
["lZ" "2F"]
["8a" "8a4"]
["n1D8CSq" "n1D8Gb1k"]
["X4PO" "X4Pu"]
["eAVM1" "eAVM1qg"]
["5e3DkZ6i" "5e3DkZv4Y"]
["3P7210" "3P7245cHM"]
["1c4D2j4UUK738" "1c4D2joFjd"])
我找到了解决方案,而且很简单。我应该更多地关注文档。 :args
documentation for fdef
状态:
:args A regex spec for the function arguments as they were a list to
be passed to apply - in this way, a single spec can handle functions
with multiple arities
因此,我可以直接提供生成的向量,如下:
(defn- drop-common-prefix [v w]
(loop [v v, a (.charAt v 0), s (.substring v 1),
w w, b (.charAt w 0), t (.substring w 1)]
(if (and (= a b)
(not-empty s)
(not-empty t))
(recur s (.charAt s 0) (.substring s 1)
t (.charAt t 0) (.substring t 1))
[v a s, w b t])))
(def prefix-pair-gen
(gen/fmap
(fn [[u v w]]
[(str u v) (str u w)])
(spec/gen
(spec/and (spec/coll-of string? :type vector? :count 3)
(fn [[u v w]]
(and (not-empty v)
(not-empty w)))))))
(spec/def ::prefix-pair
(spec/with-gen
(spec/coll-of string? :type vector? :count 2)
(constantly prefix-pair-gen)))
(spec/fdef drop-common-prefix
:args ::prefix-pair
:ret (spec/tuple string? char? string?, string? char? string?)
:fn (fn [{[v w] :args, [v' a s, w' b t] :ret}]
(and (= v' (str a s))
(.endsWith v v')
(= w' (str b t))
(.endsWith w w'))))
我可以通过以下方式验证其正确性:
user> (stest/summarize-results (stest/check `liblevenshtein.distance/drop-common-prefix))
{:sym liblevenshtein.distance/drop-common-prefix}
{:total 1, :check-passed 1}
我有一个方法可以从字符串对中删除公共前缀,并且一直在尝试为它创建一个生成器。生成随机字符串对是微不足道的,但我如何才能强制许多字符串对具有共同的前缀?简单地过滤对生成的示例数量不足,因此我正在尝试创建一个自定义生成器来满足要求。
这是我现在拥有的;它有效,但我想生成更好的参数:
(ns liblevenshtein.distance
(:require [clojure.spec.alpha :as spec]
[clojure.spec.gen.alpha :as gen]))
(spec/def ::word
(spec/and string? (complement nil?)))
(spec/def ::v-w (spec/cat :v ::word, :w ::word))
(spec/def ::non-empty-v-w
(spec/and ::v-w (fn [{:keys [v w]}]
(and (not-empty v)
(not-empty w)))))
(defn- drop-common-prefix [v w]
(loop [v v, a (.charAt v 0), s (.substring v 1),
w w, b (.charAt w 0), t (.substring w 1)]
(if (and (= a b)
(not-empty s)
(not-empty t))
(recur s (.charAt s 0) (.substring s 1)
t (.charAt t 0) (.substring t 1))
[v a s, w b t])))
(spec/fdef drop-common-prefix
:args ::non-empty-v-w
:ret (spec/tuple string? char? string?, string? char? string?)
:fn (fn [{{:keys [v w]} :args, [v' a s, w' b t] :ret}]
(and (= v' (str a s))
(.endsWith v v')
(= w' (str b t))
(.endsWith w w'))))
通过使用生成器进行试验,我得出了以下结论。它生成满足我要求的字符串对,但我不知道如何将它们拆分为我的函数的参数:
user=> (def prefix-pair-gen (gen/fmap (fn [[u v w]] [(str u v) (str u w)]) (spec/gen (spec/coll-of string? :type vector? :count 3))))
#'user/prefix-pair-gen
user=> (spec/def ::prefix-pair (spec/with-gen (spec/coll-of string? :type vector? :count 2) (fn [] prefix-pair-gen)))
:user/prefix-pair
user=> (gen/sample (spec/gen ::prefix-pair))
(["" ""]
["c" "cR"]
["lZ" "2F"]
["8a" "8a4"]
["n1D8CSq" "n1D8Gb1k"]
["X4PO" "X4Pu"]
["eAVM1" "eAVM1qg"]
["5e3DkZ6i" "5e3DkZv4Y"]
["3P7210" "3P7245cHM"]
["1c4D2j4UUK738" "1c4D2joFjd"])
我找到了解决方案,而且很简单。我应该更多地关注文档。 :args
documentation for fdef
状态:
:args A regex spec for the function arguments as they were a list to be passed to apply - in this way, a single spec can handle functions with multiple arities
因此,我可以直接提供生成的向量,如下:
(defn- drop-common-prefix [v w]
(loop [v v, a (.charAt v 0), s (.substring v 1),
w w, b (.charAt w 0), t (.substring w 1)]
(if (and (= a b)
(not-empty s)
(not-empty t))
(recur s (.charAt s 0) (.substring s 1)
t (.charAt t 0) (.substring t 1))
[v a s, w b t])))
(def prefix-pair-gen
(gen/fmap
(fn [[u v w]]
[(str u v) (str u w)])
(spec/gen
(spec/and (spec/coll-of string? :type vector? :count 3)
(fn [[u v w]]
(and (not-empty v)
(not-empty w)))))))
(spec/def ::prefix-pair
(spec/with-gen
(spec/coll-of string? :type vector? :count 2)
(constantly prefix-pair-gen)))
(spec/fdef drop-common-prefix
:args ::prefix-pair
:ret (spec/tuple string? char? string?, string? char? string?)
:fn (fn [{[v w] :args, [v' a s, w' b t] :ret}]
(and (= v' (str a s))
(.endsWith v v')
(= w' (str b t))
(.endsWith w w'))))
我可以通过以下方式验证其正确性:
user> (stest/summarize-results (stest/check `liblevenshtein.distance/drop-common-prefix))
{:sym liblevenshtein.distance/drop-common-prefix}
{:total 1, :check-passed 1}