如何使用 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}