From 376ae6035ef993e010dd984b2c81f5e6b8f49f4f Mon Sep 17 00:00:00 2001 From: mehbark Date: Fri, 13 Jan 2023 17:21:19 -0500 Subject: [PATCH] refactor into modules --- src/matrix/base.clj | 68 ++++++++++++ src/matrix/core.clj | 217 +------------------------------------ src/matrix/interactive.clj | 33 ++++++ src/matrix/render.clj | 119 ++++++++++++++++++++ 4 files changed, 223 insertions(+), 214 deletions(-) create mode 100644 src/matrix/base.clj create mode 100644 src/matrix/interactive.clj create mode 100644 src/matrix/render.clj diff --git a/src/matrix/base.clj b/src/matrix/base.clj new file mode 100644 index 0000000..412a2fc --- /dev/null +++ b/src/matrix/base.clj @@ -0,0 +1,68 @@ +(ns matrix.base) + +(defn to-vec + [v] + (apply vector v)) + +(defn swap-rows + "Swap rows n and m of the given matrix, 0-indexed" + [matrix n m] + (assoc matrix + m (matrix n) + n (matrix m))) + +(defn add-rows-with-mul + "n * row from + row to" + [matrix n from to] + (let [from-row (get matrix from) + to-row (get matrix to) + new-row (-> (map (fn [f t] (+ (* n f) t)) + from-row to-row) + to-vec)] + (assoc matrix + to new-row))) + +(defn add-rows + "row from + row to" + [matrix from to] + (add-rows-with-mul matrix 1 from to)) + +(defn mul-row + "n * row" + [matrix n row] + (update matrix + row #(to-vec (map (partial * n) %)))) + +(defn row-with-fill + "Creates a vector row with the given width and a specified fill (default 0)" + ([width] (row-with-fill width 0)) + ([width fill] + (apply vector + (map (constantly fill) + (range 0 width))))) + +(defn empty-matrix + "Creates a height x width matrix with the given default value (default 0)" + ([height width] (empty-matrix height width 0)) + ([height width fill] + (let [row (row-with-fill width fill)] + (->> (range 0 height) + (map (constantly row)) + (apply vector))))) + +(defn id-matrix + "Creates the id matrix of the given width" + [width] + (let [base (empty-matrix width width 0)] + (->> base + (map (fn [i row] (assoc row i 1)) + (range 0 width)) + (apply vector)))) + +(def matrix-width + "Get the width of a matrix (assumes that all rows are the same size, as they should be!)" + (comp count first)) + +(def matrix-height + "Get the height of a matrix" + count) diff --git a/src/matrix/core.clj b/src/matrix/core.clj index f5e60c9..ea4b180 100644 --- a/src/matrix/core.clj +++ b/src/matrix/core.clj @@ -1,220 +1,8 @@ (ns matrix.core - (:require [clojure.string :as str]) + (:require [matrix.base :refer :all]) + (:require [matrix.interactive :refer :all]) (:gen-class)) -(defn to-vec - [v] - (apply vector v)) - -(defn swap-rows - "Swap rows n and m of the given matrix, 0-indexed" - [matrix n m] - (assoc matrix - m (matrix n) - n (matrix m))) - -(defn add-rows-with-mul - "n * row from + row to" - [matrix n from to] - (let [from-row (get matrix from) - to-row (get matrix to) - new-row (-> (map (fn [f t] (+ (* n f) t)) - from-row to-row) - to-vec)] - (assoc matrix - to new-row))) - -(defn add-rows - "row from + row to" - [matrix from to] - (add-rows-with-mul matrix 1 from to)) - -(defn mul-row - "n * row" - [matrix n row] - (update matrix - row #(to-vec (map (partial * n) %)))) - -(defn row-with-fill - "Creates a vector row with the given width and a specified fill (default 0)" - ([width] (row-with-fill width 0)) - ([width fill] - (apply vector - (map (constantly fill) - (range 0 width))))) - -(defn empty-matrix - "Creates a height x width matrix with the given default value (default 0)" - ([height width] (empty-matrix height width 0)) - ([height width fill] - (let [row (row-with-fill width fill)] - (->> (range 0 height) - (map (constantly row)) - (apply vector))))) - -(defn id-matrix - "Creates the id matrix of the given width" - [width] - (let [base (empty-matrix width width 0)] - (->> base - (map (fn [i row] (assoc row i 1)) - (range 0 width)) - (apply vector)))) - -(def matrix-width - "Get the width of a matrix (assumes that all rows are the same size, as they should be!)" - (comp count first)) - -(def matrix-height - "Get the height of a matrix" - count) - -(defn surround-with - "Surround a string with the given left and right" - ([left right] #(surround-with left right %)) - ([left right s] (str left s right))) - -(defn add-before-last - [what where] - (concat (take (dec (count where)) where) - [what] - [(last where)])) - -(defn draw-row - "Takes a seq of strs and a max length and draws a row. - Optionally takes a left and right to surround with" - ([seq max-len add-bar] - (let [fmt-str (str "%" max-len "s")] - (->> seq - (map (partial format fmt-str)) - ((if add-bar - (partial add-before-last "│") - identity)) - (str/join " ")))) - ([seq max-len add-bar left right] - ((surround-with left right) (draw-row seq max-len add-bar)))) - -(defn max-str-len - "Returns the maximum length of a stringified seq" - [seq] - (->> seq - (map str) - (map count) - (apply max 0))) - -(defn max-str-len-matrix - "Returns the maximum length of the stringified elements of the matrix" - [m] - (apply max (map max-str-len m))) - -; could do latex, but what's the point -(defn pretty-matrix-rows - "Make a list of fancy display strings of the rows given matrix" - ([m] (pretty-matrix-rows m false)) - ([m add-bar] - (let [max-len (max-str-len-matrix m) - width (matrix-width m) - row-len (+ (- width 1) (* max-len width)) - middle (map #(draw-row % max-len add-bar "┃ " " ┃") m) - space (apply str (repeat (+ (if add-bar 2 0) row-len) " "))] - (concat [(str "┏ " space " ┓")] - middle - [(str "┗ " space " ┛")])))) - -(defn pretty-matrix - "Make a fancy display string from the given matrix" - ([m] (pretty-matrix m false)) - ([m add-bar] - (str/join "\n" (pretty-matrix-rows m add-bar)))) - -(defn pprint-matrix - "Pretty print the given matrix" - ([m] (pprint-matrix m false)) - ([m add-bar] - ((comp println #(pretty-matrix % add-bar)) m) - m)) - -;; repl commands -(defn fmt-num - "Format a number, empty string for 1" - [n] - (if (= 1 n) - "" - (str n))) - -(defn mid-sign - "Format a term as either '+ n' or '- n'" - [n] - (if (pos? n) - (str "+ " (fmt-num n)) - (str "- " (fmt-num (* -1 n))))) - -(defn single-term - [val name first] - (if (zero? val) - "" - (str (if first - (fmt-num val) - (mid-sign val)) - name))) - -; could be generalized -(defn equation-from-row - "Write a pretty equation from one row of an augmented matrix" - ([row] (apply equation-from-row row)) - ([x eq] (str (single-term x "x" true) - " = " - eq)) - ([x y eq] - (let [x (single-term x "x" true) - y (single-term y "y" (empty? x))] - (str x - (if (not-empty x) " ") - y - (if (not-empty y) " ") - "= " eq))) - ([x y z eq] - (let [x (single-term x "x" true) - y (single-term y "y" (empty? x)) - z (single-term z "z" (and (empty? x) (empty? y)))] - (str x - (if (not-empty x) " ") - y - (if (not-empty y) " ") - z - (if (not-empty z) " ") - "= " eq)))) - -(defn print-matrix-equations - [m] - (do - (last (for [row m] - (println (equation-from-row row)))) - m)) - -(defn swap [n m] #(swap-rows % n m)) -(defn add [from to] #(add-rows % from to)) -(defn add-mul [n from to] #(add-rows-with-mul % n from to)) -(defn mul [n at] #(mul-row % n at)) -(def print-equations print-matrix-equations) - -(defn apply-input - [m] - (let [got (read)] - (case got - [:q - :wq - :exit - :x - :done] m - ((eval got) m)))) - -(defn matrix-repl - ([m] (matrix-repl m false)) - ([m add-bar] - (pprint-matrix m add-bar) - (iterate (comp #(pprint-matrix % add-bar) apply-input) m))) - (defn -main "I don't do a whole lot ... yet." [& args] @@ -229,6 +17,7 @@ ; TODO: parse equations (easy, but requires context (e.g. x = 1 could be any number of variables)) ; TODO: undoing ; will have to have a higher level thing for storing state + ; use a set for options on the higher-level thing #{:show-equations? :show-bar? :color?} ; TODO: solving id-matrix ; TODO: solving gaussian-eliminated matrix ; TODO: automation diff --git a/src/matrix/interactive.clj b/src/matrix/interactive.clj new file mode 100644 index 0000000..76b43c9 --- /dev/null +++ b/src/matrix/interactive.clj @@ -0,0 +1,33 @@ +(ns matrix.interactive + (:require [matrix.base :refer :all]) + (:require [matrix.render :refer :all])) + +(defn print-matrix-equations + [m] + (do + (last (for [row m] + (println (equation-from-row row)))) + m)) + +(defn swap [n m] #(swap-rows % n m)) +(defn add [from to] #(add-rows % from to)) +(defn add-mul [n from to] #(add-rows-with-mul % n from to)) +(defn mul [n at] #(mul-row % n at)) +(def print-equations print-matrix-equations) + +(defn apply-input + [m] + (let [got (read)] + (case got + [:q + :wq + :exit + :x + :done] m + ((eval got) m)))) + +(defn matrix-repl + ([m] (matrix-repl m false)) + ([m add-bar] + (pprint-matrix m add-bar) + (iterate (comp #(pprint-matrix % add-bar) apply-input) m))) diff --git a/src/matrix/render.clj b/src/matrix/render.clj new file mode 100644 index 0000000..af26752 --- /dev/null +++ b/src/matrix/render.clj @@ -0,0 +1,119 @@ +(ns matrix.render + (:require [clojure.string :as str]) + (:require [matrix.base :refer :all])) + +(defn surround-with + "Surround a string with the given left and right" + ([left right] #(surround-with left right %)) + ([left right s] (str left s right))) + +(defn add-before-last + [what where] + (concat (take (dec (count where)) where) + [what] + [(last where)])) + +(defn draw-row + "Takes a seq of strs and a max length and draws a row. + Optionally takes a left and right to surround with" + ([seq max-len add-bar] + (let [fmt-str (str "%" max-len "s")] + (->> seq + (map (partial format fmt-str)) + ((if add-bar + (partial add-before-last "│") + identity)) + (str/join " ")))) + ([seq max-len add-bar left right] + ((surround-with left right) (draw-row seq max-len add-bar)))) + +(defn max-str-len + "Returns the maximum length of a stringified seq" + [seq] + (->> seq + (map str) + (map count) + (apply max 0))) + +(defn max-str-len-matrix + "Returns the maximum length of the stringified elements of the matrix" + [m] + (apply max (map max-str-len m))) + +; could do latex, but what's the point +(defn pretty-matrix-rows + "Make a list of fancy display strings of the rows given matrix" + ([m] (pretty-matrix-rows m false)) + ([m add-bar] + (let [max-len (max-str-len-matrix m) + width (matrix-width m) + row-len (+ (- width 1) (* max-len width)) + middle (map #(draw-row % max-len add-bar "┃ " " ┃") m) + space (apply str (repeat (+ (if add-bar 2 0) row-len) " "))] + (concat [(str "┏ " space " ┓")] + middle + [(str "┗ " space " ┛")])))) + +(defn pretty-matrix + "Make a fancy display string from the given matrix" + ([m] (pretty-matrix m false)) + ([m add-bar] + (str/join "\n" (pretty-matrix-rows m add-bar)))) + +(defn pprint-matrix + "Pretty print the given matrix" + ([m] (pprint-matrix m false)) + ([m add-bar] + ((comp println #(pretty-matrix % add-bar)) m) + m)) + +;; repl commands +(defn fmt-num + "Format a number, empty string for 1" + [n] + (if (= 1 n) + "" + (str n))) + +(defn mid-sign + "Format a term as either '+ n' or '- n'" + [n] + (if (pos? n) + (str "+ " (fmt-num n)) + (str "- " (fmt-num (* -1 n))))) + +(defn single-term + [val name first] + (if (zero? val) + "" + (str (if first + (fmt-num val) + (mid-sign val)) + name))) + +; could be generalized +(defn equation-from-row + "Write a pretty equation from one row of an augmented matrix" + ([row] (apply equation-from-row row)) + ([x eq] (str (single-term x "x" true) + " = " + eq)) + ([x y eq] + (let [x (single-term x "x" true) + y (single-term y "y" (empty? x))] + (str x + (if (not-empty x) " ") + y + (if (not-empty y) " ") + "= " eq))) + ([x y z eq] + (let [x (single-term x "x" true) + y (single-term y "y" (empty? x)) + z (single-term z "z" (and (empty? x) (empty? y)))] + (str x + (if (not-empty x) " ") + y + (if (not-empty y) " ") + z + (if (not-empty z) " ") + "= " eq))))