de.dixieflatline/lehmer

0.1.0-SNAPSHOT


Encode permutations to numbers and vice versa.

dependencies

org.clojure/clojure
1.11.1
org.clojure/test.check
1.1.1



(this space intentionally left almost blank)
 
(ns lehmer.core
  (:require [clojure.spec.alpha :as s]
            [lehmer.specs :as specs]))
(def ^:private factorial (memoize #(reduce * % (range 1N %))))
(defn- elements->indeces
  [elements]
  (into [] (range (count elements))))
(defn- permutate-indeces
  [n v]
  (if (= (count v) 1)
    v
    (let [divisor (factorial (dec (count v)))
          [l [r & xs]] (split-at (quot n divisor) v)]
      (when r
        (cons r
              (permutate-indeces (rem n divisor)
                                 (concat l xs)))))))
(defn- nthx
  [coll indeces]
  (mapv #(nth coll %) indeces))
(defn- nth-elements
  [n elements]
  (->> (elements->indeces elements)
       (permutate-indeces (dec n))
       (nthx elements)))

Returns n-th permutation of elements or nil if n is greater than number of permutations. elements must be a distinct list, vector or string.

(defn nth-permutation
  [n elements]
  {:pre [(s/valid? ::specs/index n)
         (s/valid? ::specs/elements elements)]
   :post [(or (nil? %)
              (and (s/valid? ::specs/elements %)
                   (= (type elements) (type %))))]}
  (when (<= n (factorial (count elements)))
    (cond->> (nth-elements n elements)
      (string? elements) (apply str)
      (list? elements) (apply list))))
(defn- permutation->indeces
  [permutation elements]
  (let [m (zipmap elements
                  (range (count elements)))]
    (mapv #(m %) permutation)))
(defn- lehmer-code
  [l [r & xs]]
  (let [n (- r (count (filter #(< % r) l)))]
    (if-not xs
      [n]
      (cons n (lehmer-code (conj l r) xs)))))

Returns lehmer code of permutation, where permutation is a rearrangement of elements. elements must be a distinct list, vector or string.

(defn permutation->lehmer-code
  [permutation elements]
  {:pre [(s/valid? ::specs/elements permutation)
         (s/valid? ::specs/elements elements)
         (= (set permutation) (set elements))]
   :post [(s/valid? ::specs/lehmer-code %)]}
  (lehmer-code [] (permutation->indeces permutation elements)))

Converts lehmer-code to base 10.

(defn lehmer-code->base-10
  [lehmer-code]
  {:pre [(s/valid? ::specs/lehmer-code lehmer-code)]
   :post [(s/valid? ::specs/natural-integer %)]}
  (reduce + 1 (map-indexed (fn [idx n]
                             (* (factorial (- (count lehmer-code) idx 1)) n))
                           lehmer-code)))