2017-05-11 1 views
3

J'utilise les spécifications Clojure aux spécifications d'une structure de données simple:Création d'un générateur de spécifications Clojure pour une structure de données imbriquées avec des contraintes à travers les couches

{:max 10 
:data [[3 8 1] 
     [9 0 1]]} 

La valeur :data est un vecteur de vecteurs de taille égale des nombres entiers l'intervalle de zéro à la valeur :max inclusivement. J'ai exprimé cela avec spec comme suit:

(s/def ::max pos-int?) 
(s/def ::row (s/coll-of nat-int? :kind vector?, :min-count 1)) 
(s/def ::data (s/and (s/coll-of ::row :kind vector?, :min-count 1) 
        #(apply = (map count %)))) 

(s/def ::image (s/and (s/keys :req-un [::max ::data]) 
         (fn [{:keys [max data]}] 
         (every? #(<= 0 % max) (flatten data))))) 

générateurs automatiques fonctionnent très bien pour les trois premières spécifications, mais pas pour ::image. (s/exercise ::image) échoue toujours après 100 essais.

J'ai essayé de créer un générateur personnalisé pour ::image mais je n'ai pas réussi. Je ne vois pas comment je pourrais exprimer les contraintes qui traversent les couches de la structure imbriquée (la clé :max contraint les valeurs dans un vecteur ailleurs).

Est-il possible de créer un générateur Clojure spec/test.check qui génère ::image s?

Répondre

4

Certainement! La clé ici est de créer un modèle du domaine. Ici, je pense que le modèle est la taille max, col-size et row-size. C'est suffisant pour générer un exemple valide.

donc quelque chose comme ceci:

(def image-gen 
    (gen/bind 
    (s/gen (s/tuple pos-int? (s/int-in 1 8) (s/int-in 1 8))) 
    (fn [[max rows cols]] 
     (gen/hash-map 
     :max (s/gen #{max}) 
     :data (gen/fmap #(into [] (partition-all cols) %) 
       (s/gen (s/coll-of (s/int-in 0 (inc max)) 
            :kind vector? 
            :count (* rows cols)))))))) 

D'abord, nous générons un tuple de [<max-value> <rows> <cols>]. Le gen/bind renvoie ensuite un nouveau générateur qui crée des cartes dans la forme souhaitée. Nous imbriquons gen/fmap à l'intérieur pour construire un vecteur de toutes les valeurs de données aléatoires, puis le reformater dans le formulaire vectoriel imbriqué approprié.

Vous pouvez combiner cela en image avec:

(s/def ::image 
    (s/with-gen 
    (s/and (s/keys :req-un [::max ::data]) 
      (fn [{:keys [max data]}] 
      (every? #(<= 0 % max) (flatten data)))) 
    (fn [] image-gen))) 

Une chose peut-être intéressant de noter est que je bondis les lignes et Col. à plus de 7 que le générateur peut par ailleurs tenter de générer très grand nombre aléatoire valeurs d'échantillon aléatoires. Nécessitant de limiter les choses comme ceci est assez commun dans les générateurs personnalisés.

Avec plus d'effort, vous pourriez obtenir une plus grande réutilisation de certaines de ces spécifications et des pièces de générateur ainsi.