(ns clojure.math.test-combinatorics
  (:require [clojure.math.combinatorics :refer 
             [subsets combinations selections permutations cartesian-product
              partitions nth-permutation permutation-index drop-permutations 
              count-permutations count-combinations count-subsets 
              nth-subset nth-combination]]
            [clojure.test :refer [deftest is are testing run-tests]]))

(deftest test-combinations
  (are [x y] (= x y)
       (combinations [1 2 3] 2) '((1 2) (1 3) (2 3))))

(defn old-subsets [l]
  (map (partial map deref) (subsets (map atom l)))) 

(deftest test-subsets
  (are [x y] (= x y)
       (subsets []) '(())
       (subsets [1 2 3]) '(() (1) (2) (3) (1 2) (1 3) (2 3) (1 2 3))
       (subsets [3 2 1]) '(() (3) (2) (1) (3 2) (3 1) (2 1) (3 2 1))
       (subsets [1 2 3 4]) '(() (1) (2) (3) (4) (1 2) (1 3) (1 4) (2 3) (2 4) (3 4) (1 2 3) (1 2 4) (1 3 4) (2 3 4) (1 2 3 4))
       (subsets [1 1 2]) (subsets [1 2 1])
       (subsets [1 3 2 3]) (subsets [1 3 3 2]))  
  (are [x] (and (= (subsets x) (distinct (old-subsets x)))
                (= (count (subsets x)) (count-subsets x)))
       []
       [1 2 3 4]
       [1 1 2 2 2 3]
       [1 1 1 2 2 3]
       [1 1 1 1 1]
       [1 2 2 3 3 3]
       [1 1 2 2 3 3]
       [1 1 1 2 2 2]
       [1 2 2 2 3 3]
       [1 1 1 1 2 2]
       [1 2 3 3 4 4]
       [1 1 2 3 3 4]))

(deftest test-nth-combination
  (are [x]
       (every?
         (fn [t]
           (and (= (count-combinations x t) (count (combinations x t)))
                (let [c (count-combinations x t)]
                  (= (for [i (range c)] (nth-combination x t i))
                     (combinations x t)))))
         (range (inc (count x))))
       [1 2 3 4]
       []
       [1 2 2 3 3 3]
       [1 1 1 2 2 3]
       [1 2 2 2 3 3]
       [1 1 1 2 3 3]
       [1 1 2 3 3 3]
       [1 2 3 1 2 1]
       [\a \b \c]
       [\a \b \c \a \b \c]
       ))

(deftest test-nth-subset
  (are [x]
       (let [c (count-subsets x)]
         (= (for [i (range c)] (nth-subset x i))
            (subsets x)))
       [1 2 3 4]
       []
       [1 2 2 3 3 3]
       [1 1 1 2 2 3]
       [1 2 2 2 3 3]
       [1 1 1 2 3 3]
       [1 1 2 3 3 3]
       [1 2 3 1 2 1]
       [\a \b \c]
       [\a \b \c \a \b \c]))

(deftest test-cartesian-product
  (are [x y] (= x y)
       (cartesian-product [1 2] [3 4]) '((1 3) (1 4) (2 3) (2 4))))

(deftest test-selections
  (are [x y] (= x y)
       (selections [1 2] 3) '((1 1 1) (1 1 2) (1 2 1) (1 2 2) (2 1 1) (2 1 2) (2 2 1) (2 2 2))))

(def lex-permutations @#'clojure.math.combinatorics/lex-permutations)

(deftest test-permutations
  (are [x y] (= x y)
       (permutations [1 2 3]) '((1 2 3) (1 3 2) (2 1 3) (2 3 1) (3 1 2) (3 2 1))
       (permutations [2 3 1]) '((2 3 1) (2 1 3) (3 2 1) (3 1 2) (1 2 3) (1 3 2))
       (permutations [1 1 2]) (lex-permutations [1 1 2])
       (permutations [:a :b]) [[:a :b] [:b :a]]
       (permutations [2 1 1]) [[2 1 1] [1 2 1] [1 1 2]]))

(deftest test-lex-permutations
  (are [x y] (= x y)
       (lex-permutations [1 1 2]) '([1 1 2] [1 2 1] [2 1 1])))

(def sorted-numbers? @#'clojure.math.combinatorics/sorted-numbers?)

(deftest test-sorted-numbers?
  (are [x y] (= x y)
       (sorted-numbers? [1 2 3]) true
       (sorted-numbers? [1 1 2]) true
       (sorted-numbers? []) true
       (sorted-numbers? [1 4 2]) false
       (sorted-numbers? [1 :a 2]) false))

(def factorial-numbers @#'clojure.math.combinatorics/factorial-numbers)

(deftest test-factorial-numbers
  (are [x y] (= (factorial-numbers x) y)
       463 [3 4 1 0 1 0]
       0 []
       1 [1 0]
       2 [1 0 0]))

(def nth-permutation-distinct @#'clojure.math.combinatorics/nth-permutation-distinct)

(deftest test-nth-permutation-distinct
  (let [perms (permutations (range 4))]
    (doseq [i (range 24)]
      (is (= (nth perms i) (nth-permutation-distinct (range 4) i))))))

(def nth-permutation-duplicates  @#'clojure.math.combinatorics/nth-permutation-duplicates)

(deftest test-nth-permutation-duplicates
  (let [perms (permutations [1 1 2 2 2 3])]
    (doseq [i (range 60)]
      (is (= (nth perms i) (nth-permutation-duplicates [1 1 2 2 2 3] i))))))

(deftest test-count-permutations
  (are [x] (= (count-permutations x) (count (permutations x)))
       (range 4)
       [1 1 2]
       [1 1 2 2]
       [1 1 1 2 2 3]))

(deftest test-nth-permutation
  (let [sortedDistinctNumbers (range 4)
        sortedDuplicateNumbers [1 1 1 2 3 3]
        distinctChars [\a \b \c \d]
        duplicates [\a \a \b \c \c]
        duplicates2 [1 3 1 2 1 2]]
    (doseq [collection [sortedDistinctNumbers 
                        sortedDuplicateNumbers
                        distinctChars
                        duplicates
                        duplicates2],
            :let [perms (permutations collection)
                  c (count perms)]
            n (range c)]
      (is (= (nth perms n) (nth-permutation collection n)))
      (is (= c (count-permutations collection))))))

(deftest test-drop-permutations
  (doseq [x [[1 2 3]
             [1 1 2]
             [\a \b \c]
             [\a \a \b \c \c]
             [1 3 1 2 1 2]]
          :let [c (count-permutations x)]
          i (range c)]
    (is (= (drop-permutations x i) (drop i (permutations x))))))

(deftest test-permutation-index
  (let [sortedDistinctNumbers (range 4)
        sortedDuplicateNumbers [1 1 1 2 3 3]
        distinctChars [\a \b \c \d]
        duplicates [\a \a \b \c \c]
        duplicates2 [1 3 1 2 1 2]]
    (doseq [collection [sortedDistinctNumbers 
                        sortedDuplicateNumbers
                        distinctChars
                        duplicates
                        duplicates2],
            perm (permutations collection)]
      (is (= (nth-permutation (sort collection) (permutation-index perm))
             perm)))))
            
(deftest test-partitions
  (do (are [x y] (= x y)
           (partitions [1 2 3]) '(([1 2 3]) ([1 2] [3]) ([1 3] [2]) ([1] [2 3]) ([1] [2] [3]))
           (partitions [1 2]) '(([1 2]) ([1] [2]))
           (partitions [1]) '(([1]))
           (partitions []) '(())
           (partitions nil) '(())
           (partitions [1 1 1]) '(([1 1 1]) ([1 1] [1]) ([1] [1] [1]))
           (partitions [1 1 2]) '(([1 1 2]) ([1 1] [2]) ([1 2] [1]) ([1] [1] [2])))
    (doseq [num-items (range 0 4)
            lo (range -1 (+ 2 num-items))
            hi (range -1 (+ 2 num-items))]
      (is (= (partitions (range num-items) :min lo :max hi)
             (filter #(<= lo (count %) hi) (partitions (range num-items)))))) ; tests partitions-H
    (doseq [num-items (range 2 4)
            lo (range -1 (+ 2 num-items))
            hi (range -1 (+ 2 num-items))]
      (is (= (partitions (cons 0 (range (dec num-items))) :min lo :max hi)
             (filter #(<= lo (count %) hi) (partitions (cons 0 (range (dec num-items)))))))))) ; tests partitions-M