|
(ns sortprop.core |
|
(:require [clojure.math.combinatorics :as combo] |
|
[clojure.test.check :as tc] |
|
[clojure.test.check.generators :as gen] |
|
[clojure.test.check.properties :as prop])) |
|
|
|
|
|
;;;; Property 1. sorting a sorted is sorted ;;;; |
|
(def sort-sort-is-sort |
|
(prop/for-all [v (gen/vector gen/int)] |
|
(= (sort v) (sort (sort v))))) |
|
|
|
(tc/quick-check 100 sort-sort-is-sort) |
|
;; {:result true, :num-tests 100, :seed 1560357962512} |
|
|
|
(def sort-sort-is-sort-str |
|
(prop/for-all [v (gen/not-empty gen/string)] |
|
(= (sort v) (sort (sort v))))) |
|
|
|
(tc/quick-check 100 sort-sort-is-sort-str) |
|
;; {:result true, :num-tests 100, :seed 1560435933339} |
|
|
|
;;;; Property 2. sort uses compare by default ;;;; |
|
(def sort-uses-compare-def |
|
(prop/for-all [v (gen/vector gen/int)] |
|
(= (sort v) (sort compare v)))) |
|
|
|
(tc/quick-check 100 sort-uses-compare-def) |
|
;; {:result true, :num-tests 100, :seed 1560357979976} |
|
|
|
(def sort-uses-compare-def-str |
|
(prop/for-all [v (gen/not-empty gen/string)] |
|
(= (sort v) (sort compare v)))) |
|
|
|
(tc/quick-check 100 sort-uses-compare-def-str) |
|
;; {:result true, :num-tests 100, :seed 1560436022901} |
|
|
|
|
|
;;;; Property 3. Sorting preserves length ;;;; |
|
(def sort-length-invariant |
|
(prop/for-all [v (gen/vector gen/int)] |
|
(= (count v) (count (sort v))))) |
|
|
|
(tc/quick-check 100 sort-length-invariant) |
|
;; {:result true, :num-tests 100, :seed 1560357999327} |
|
|
|
(def sort-length-invariant-str |
|
(prop/for-all [v (gen/not-empty gen/string)] |
|
(= (count v) (count (sort v))))) |
|
|
|
(tc/quick-check 100 sort-length-invariant-str) |
|
;; {:result true, :num-tests 100, :seed 1560436128572} |
|
|
|
|
|
;;;; Property 4. Sort order applies between every 2 successive elements ;;;; |
|
(def sort-applies-pairwise |
|
(prop/for-all [v (gen/vector gen/int)] |
|
(->> (sort v) |
|
(partition 2 1) |
|
(map (fn [[a b]] (compare a b))) |
|
(every? (complement pos?))))) |
|
|
|
(tc/quick-check 100 sort-applies-pairwise) |
|
;; {:result true, :num-tests 100, :seed 1560358019543} |
|
|
|
(def sort-applies-pairwise-str |
|
(prop/for-all [v (gen/not-empty gen/string)] |
|
(->> (sort v) |
|
(partition 2 1) |
|
(map (fn [[a b]] (compare a b))) |
|
(every? (complement pos?))))) |
|
|
|
(tc/quick-check 100 sort-applies-pairwise-str) |
|
;; {:result true, :num-tests 100, :seed 1560436247579} |
|
|
|
|
|
;;;; Property 5. Sort doesn't rearrange equivalent values. ;;;; |
|
|
|
;; This is a generalized function that tests if order is preserved when |
|
;; sort is called with a comparator that returns equal (zero) for |
|
;; multiple values. See writeup for explanation. |
|
(defn eq-order-preserved? [s pred] |
|
(let [comparator (fn [a b] (compare (pred a) (pred b))) |
|
sorted (sort comparator s) |
|
grouped-s (map second (sort (group-by pred s))) |
|
partitioned-sorted-s (partition-by pred sorted)] |
|
(every? true? (map = grouped-s partitioned-sorted-s)))) |
|
|
|
|
|
;;;;;; Property 5 example 1 - compare values mod 10 ;;;;;; |
|
;; sorted output partition-by mod 10 |
|
;; ------------------- |
|
;; ((60 10 90 30) |
|
;; (21 91 41 1) |
|
;; (42 2 52) |
|
;; (23 3 33) |
|
;; (65 65 95 55) |
|
;; (46 66 16) |
|
;; (77 57) |
|
;; (38 38 48) |
|
;; (9 79 29 89)) |
|
|
|
;; original input grouped-by mod 10 |
|
;; ----------------- |
|
;; ([60 10 90 30] |
|
;; [21 91 41 1] |
|
;; [42 2 52] |
|
;; [23 3 33] |
|
;; [65 65 95 55] |
|
;; [46 66 16] |
|
;; [77 57] |
|
;; [38 38 48] |
|
;; [9 79 29 89]) |
|
|
|
;; As you can see, if the mod 10 sort didn't preserve order then the 'chunks' |
|
;; in the sorted-then-partitioned values above wouldn't be in the same order as |
|
;; in the grouping of unsorted input. |
|
;; Note that this depends on group-by preserving order! |
|
|
|
;;;;;; Property 5 Example 2. Compare chars with isUpperCase. ;;;;;; |
|
|
|
(def sort-eq-order-preserved-isupper |
|
(prop/for-all [v (gen/not-empty gen/string)] |
|
(eq-order-preserved? v #(Character/isUpperCase %)))) |
|
|
|
(tc/quick-check 100 sort-eq-order-preserved-isupper) |
|
;; {:result true, :num-tests 100, :seed 1560358046843} |
|
|
|
;; How it works |
|
;; starting with unsorted input: "hMiESddSSeAGnE" |
|
;; sorted with isUpperCase: (\h \i \d \d \e \n \M \E \S \S \S \A \G \E) |
|
|
|
;; partitioned by Character/isUpperCase: |
|
;; ((\h \i \d \d \e \n) (\M \E \S \S \S \A \G \E)) |
|
|
|
;; original (unsorted) input grouped-by Character/isUpperCase: |
|
;; ([\h \i \d \d \e \n] [\M \E \S \S \S \A \G \E]) |
|
|
|
;; The preservation of order for equivalent values is shown by the fact that |
|
;; partitioning the sorted sequence gives the same result as grouping the |
|
;; original unsorted sequence. |
|
|
|
|
|
;;;; Property 6. First is min, last is max. ;;;; |
|
(def sort-first-min-last-max |
|
(prop/for-all [v (gen/not-empty (gen/vector gen/int))] |
|
(and (= (apply min v) (first (sort v))) |
|
(= (apply max v) (last (sort v)))))) |
|
|
|
(tc/quick-check 100 sort-first-min-last-max) |
|
;; {:result true, :num-tests 100, :seed 1560434601500} |
|
|
|
|
|
;;;; Property 7. First is <= last ;;;; |
|
(def sort-first-le-last |
|
(prop/for-all [v (gen/not-empty (gen/vector gen/int))] |
|
(<= (first (sort v)) (last (sort v))))) |
|
|
|
(tc/quick-check 100 sort-first-le-last) |
|
;; {:result true, :num-tests 100, :seed 1560434676200} |
|
|
|
(def sort-first-le-last-str |
|
(prop/for-all [v (gen/not-empty gen/string)] |
|
((complement pos?) (compare (first (sort v)) (last (sort v)))))) |
|
|
|
(tc/quick-check 100 sort-first-le-last-str) |
|
;; {:result true, :num-tests 100, :seed 1560437999911} |