pseudohkoo / src / pseudohkoo / core.clj

(ns pseudohkoo.core)
(require 'clojure.set)

; NOTE: some of these functions will disappear - I've been writing them to
;       help me understand Clojure as well as aspects of the problem space

;; box functions
(defn extract-slice [vec index]
  ; extracts a three item slice from vec which corresponds to the slice of 3x3 box index is within
  ; this needs a better description!
  (let [start-index (* 3 (quot index 3))]
    (subvec vec start-index (+ start-index 3))))

(defn get-box [grid row-index column-index]
  ; return set of values of 3x3 box containing given location
  (set (flatten (map
		 (fn [row] (extract-slice row column-index))
		 (extract-slice grid row-index)))))

;; line functions
(defn format-value [value]
  ; returns either an integer or a vector, depending on whether
  ; value holds single or multiple values respectively
  (if (= 1 (count value))
    (first value)
    (vec value)))

(defn current-values [line]
  ; returns the set of known/definite current values for the line
  (set (filter integer? line)))

(defn required-values [line]
  ; returns the set of required values for the line
  ; i.e. the set of numbers needed for a line to be complete
  (set (range 1 (+ (count line) 1))))

(defn missing-values [line]
  ; returns the set of numbers that are still missing from the line
  (clojure.set/difference (required-values line) (current-values line)))

(defn refine-line [line]
  ; returns a line with non-definite values refined, where possible
  (map (fn [cell]
	 (if (nil? cell)
	   (format-value (missing-values line))
	   (if (vector? cell)
	     (format-value (clojure.set/intersection (missing-values line) (set cell)))

;; row functions
(defn get-row [grid row-index]
  ; returns the specified row from the grid, or [] if row out-of-bounds
  (nth grid row-index []))

(defn refine-row [row]
  ; return a row with non-definite values refined, where possible
  (refine-line row))

(defn row-complete? [row]
  ; returns true if row has been completed or false if not
  (= (required-values row) (set row)))

(defn update-row [row column-index new-value]
  ; returns a copy of row with the indicated column set to new-value
  (let [head (take column-index row)
	tail (nthnext row (+ column-index 1))]
    (concat head [new-value] tail)))

;; column functions
(defn get-column [grid column-index]
  ; returns the specified column from the grid
  (map (fn [row] (nth row column-index)) grid))

(defn get-columns [grid]
  ; retrieve all columns from the grid (this is like rotating the grid by 90 degrees)
  (map (fn [index] (get-column grid index)) (range (count (first grid)))))

(defn refine-column [column]
  ; return a column with non-definite values refined, where possible
  (refine-line column))

(defn replace-column [grid column-index new-column]
  ; returns a copy of the grid with the given column replaced by the new one
   (fn [index]
     (update-row (get-row grid index) column-index (nth new-column index)))
   (range (count new-column))))

;; grid functions
(defn refine-grid [grid]
  ; return a "refined" grid - replacing nil cells with definite or possible values as appropriate 
  (map refine-row grid))

(defn grid-complete? [grid]
  ; returns true if grid is finished or false if not
  (reduce (fn [x y] (and x y)) (map row-complete? grid)))