From a0afbd6231a6acdeaab0d4b9eddc12b0085dee08 Mon Sep 17 00:00:00 2001 From: Vadych Date: Fri, 26 May 2023 18:22:18 +0300 Subject: [PATCH 1/5] dz4 is done --- otus-06/project.clj | 2 +- otus-06/src/otus_06/homework.clj | 211 ++++++++++++++++++++++++++++++- 2 files changed, 210 insertions(+), 3 deletions(-) diff --git a/otus-06/project.clj b/otus-06/project.clj index aa12fd1..cee1664 100644 --- a/otus-06/project.clj +++ b/otus-06/project.clj @@ -6,4 +6,4 @@ :dependencies [[org.clojure/clojure "1.11.1"] [aero "1.1.6"]] - :repl-options {:init-ns otus-06.core}) + :repl-options {:init-ns otus-06.homework}) diff --git a/otus-06/src/otus_06/homework.clj b/otus-06/src/otus_06/homework.clj index d5d5228..f093c8c 100644 --- a/otus-06/src/otus_06/homework.clj +++ b/otus-06/src/otus_06/homework.clj @@ -1,4 +1,7 @@ -(ns otus-06.homework) +(ns otus-06.homework + (:require [clojure.string :as str] + [clojure.edn :as edn] + [clojure.java.io :as io])) ;; Загрузить данные из трех файлов на диске. ;; Эти данные сформируют вашу базу данных о продажах. @@ -92,5 +95,209 @@ ;; *** Дополнительно можно реализовать возможность добавлять новые записи в исходные файлы ;; Например добавление нового пользователя, добавление новых товаров и новых данных о продажах - ;; Файлы находятся в папке otus-06/resources/homework + + +;; **************************** +;; Функции для работы с БД + +(def db { + :customer { + :data [] + :field [:id :name :addres :phone] + :field-type [int str str str] + :format ["%3d" "%20s" "%-20s" "%10s"] + :file "homework/cust.txt"} + :product {:data [] + :field [:id :description :cost] + :field-type [int str double] + :format ["%3d" "%20s" "%5.2f"] + :file "homework/prod.txt"} + :sales { + :data [] + :field [:sales-id :customer-id :product-id :count] + :field-type [int int int int] + :format ["%3d" + [:customer :id :name "%20s"] + [:product :id :description "%10s"] + "%3d"] + :file "homework/sales.txt"}}) + +(defn get-settings [settings] + (fn [db table] + (get-in db [table settings]))) +(def get-filename (get-settings :file)) +(def get-fieldname (get-settings :field)) +(def get-data (get-settings :data)) +(def get-format (get-settings :format)) + + +(defn read-string* [s] + ;; Я не нашел как заставить read-string + ;; корректно прочитать "123-456" "123 jonh" и т.п. + ;; Решил обойти таким образом. + ;; Буду благодарен за подсказку + (if (re-matches #"\d+\.*\d*" s) + (edn/read-string s) + s)) + +(defn create-row + "Делает из строки или суквенции мапу для вставки в БД" + [line fields] + (->> (if (string? line) + (->> line + (#(str/split % #"\|")) + (map read-string*)) + line) + (zipmap fields))) + +(defn add-row + "Добавляет строку в БД" + [table] + (fn [db line] + (update-in db + [table :data] + conj + (create-row line (get-fieldname db table))))) + + +(defn load-table + "Читает данные из файла и загружает их в таблицу" + [db table] + (with-open [f (-> (get-filename db table) + (io/resource) + (io/reader))] + (->> f + line-seq + (reduce (add-row table) db)))) + +(defn load-db + "Загружает всю БД" + [db] + (reduce load-table db (keys db))) + +(defn find-row + "Ищет строки в которых field = x" + [db table fieeld x] + (filterv #( = x (fieeld %))(get-data db table))) + +(defn format-field + "Форматирует поле таблицы в соответсвии с заданным форматом. + Заодно подгружает название из других таблиц, если есть справочник" + [db] + (fn [fmt s] + (if (vector? fmt) + (let [[tbl fld name-field fs] fmt + name (name-field(first (find-row db tbl fld s)))] + (format fs name)) + (format fmt s)))) + +(defn show-table + "Отображает всю таблицу. + Основная функция для входа из меню" + [db table] + (let [format-str (get-format db table)] + (doseq [row (get-data db table)] + (->> (vals row) + (map (format-field db) format-str) + (str/join " | ") + println)) + (println "Enter to continue..") + (read-line) + db)) + +(defn nothing + "Ничего не делает" + [& args] + (first args)) + +;;************************************* +;; Функции для работы с отчетом + +(def reports {:cost-by-user + {:data [* [[:product-id [:product :cost]] [:count]]] + :groupby [:customer-id [:customer :id :name]] + :format ["\nEnter user name: " "%s: %5.2f\n"]} + :prod-count + {:data [nothing [[:count]]] + :groupby [:product-id [:product :id :description]] + :format ["\nEnter product name: " "%s: %3.0f\n"]}}) + +(defn get-rep-exp [rep] + (get-in reports [rep :data])) +(defn get-rep-groupby [rep] + (get-in reports [rep :groupby])) +(defn get-rep-format [rep] + (get-in reports [rep :format])) + + +(defn get-val + "Получает данные в соответсвии с exp, + опирается при этом на row" + [db exp row] + (if (= 1 (count exp)) + ((first exp) row) + (let [[f-id [tbl fld]] exp] + (get-in (find-row db tbl :id (f-id row)) [0 fld] 0)))) + +(defn calc-report + "Вычисляет значение, необходимое для отчета" + [db rep name] + (let [[f args] (get-rep-exp rep) + [f-group [t-search f-id f-search]] (get-rep-groupby rep)] + (if-let [id (f-id (first (find-row db t-search f-search name)))] + (let [rows (find-row db :sales f-group id) + arg-rows (for [r rows] + (map #(get-val db % r) args))] + (* (apply + (map #(apply f %) arg-rows)) 1.0)) + 0.0))) + +(defn show-report + "Выводит отчет. Основная для входа из менб" + [db rep] + (let [[promt, fmt] (get-rep-format rep)] + (println promt) + (let [name (read-line)] + (println (format fmt name (calc-report db rep name))))) + db) + +;;************************************* +;; Функции для работы с меню + +(def menu {"1" {:name-menu "Display Customer Table" + :func #(show-table % :customer)} + "2" {:name-menu "Display Product Table" + :func #(show-table % :product)} + "3" {:name-menu "Display Sales Table" + :func #(show-table % :sales)} + "4" {:name-menu "Total Sales for Customer" + :func #(show-report % :cost-by-user)} + "5" {:name-menu "Total Count for Product" + :func #(show-report % :prod-count)} + "6" {:name-menu "Exit" + :func nil}}) + +(defn get-user-choice + "Выводит меню и просит сделать выбор" + [] + (println "*** MAIN MENU ***") + (doseq [[k v] menu] + (println k " - " (:name-menu v))) + (loop [] + (println "Enter your choice: ") + (let [choice (read-line)] + (if (contains? menu choice) + choice + (recur))))) + + +(defn -main + "Точка входа. Крутит меню и запускает нужные функции" + [] + (loop [db (load-db db)] + (when-let [func (get-in menu [(get-user-choice) :func])] + (recur (func db))))) + +;; (-main) + + From 9db0e6e94ed339ba1a6c7df33890591b7e5da97b Mon Sep 17 00:00:00 2001 From: Vadych Date: Mon, 29 May 2023 09:50:50 +0300 Subject: [PATCH 2/5] review dz4 --- otus-06/src/otus_06/homework.clj | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/otus-06/src/otus_06/homework.clj b/otus-06/src/otus_06/homework.clj index f093c8c..730008e 100644 --- a/otus-06/src/otus_06/homework.clj +++ b/otus-06/src/otus_06/homework.clj @@ -181,6 +181,11 @@ [db table fieeld x] (filterv #( = x (fieeld %))(get-data db table))) +(defn find-first-row + "Ищет первую строку, попадающую под условия"[db table fieeld x] + (first (find-row db table fieeld x))) + + (defn format-field "Форматирует поле таблицы в соответсвии с заданным форматом. Заодно подгружает название из других таблиц, если есть справочник" @@ -188,7 +193,7 @@ (fn [fmt s] (if (vector? fmt) (let [[tbl fld name-field fs] fmt - name (name-field(first (find-row db tbl fld s)))] + name (name-field(find-first-row db tbl fld s))] (format fs name)) (format fmt s)))) @@ -238,14 +243,14 @@ (if (= 1 (count exp)) ((first exp) row) (let [[f-id [tbl fld]] exp] - (get-in (find-row db tbl :id (f-id row)) [0 fld] 0)))) + (fld (find-first-row db tbl :id (f-id row)))))) (defn calc-report "Вычисляет значение, необходимое для отчета" [db rep name] (let [[f args] (get-rep-exp rep) [f-group [t-search f-id f-search]] (get-rep-groupby rep)] - (if-let [id (f-id (first (find-row db t-search f-search name)))] + (if-let [id (f-id (find-first-row db t-search f-search name))] (let [rows (find-row db :sales f-group id) arg-rows (for [r rows] (map #(get-val db % r) args))] From cfdf9aa03ac9ec4f168ffe0b67dc6e54cfe759e8 Mon Sep 17 00:00:00 2001 From: Vadych Date: Tue, 30 May 2023 09:49:14 +0300 Subject: [PATCH 3/5] add save to file --- otus-06/src/otus_06/homework.clj | 102 ++++++++++++++++++++++++++----- 1 file changed, 86 insertions(+), 16 deletions(-) diff --git a/otus-06/src/otus_06/homework.clj b/otus-06/src/otus_06/homework.clj index 730008e..c277e6b 100644 --- a/otus-06/src/otus_06/homework.clj +++ b/otus-06/src/otus_06/homework.clj @@ -107,20 +107,25 @@ :field [:id :name :addres :phone] :field-type [int str str str] :format ["%3d" "%20s" "%-20s" "%10s"] - :file "homework/cust.txt"} + :file "homework/cust.txt" + :save-promt [["Name: "] ["Addres: "] ["Phone: "]]} :product {:data [] :field [:id :description :cost] :field-type [int str double] :format ["%3d" "%20s" "%5.2f"] - :file "homework/prod.txt"} + :file "homework/prod.txt" + :save-promt [["Description: "] ["Cost:"] ["Phone: "]]} :sales { :data [] - :field [:sales-id :customer-id :product-id :count] + :field [:id :customer-id :product-id :count] :field-type [int int int int] :format ["%3d" [:customer :id :name "%20s"] [:product :id :description "%10s"] "%3d"] + :save-promt [["Customer: " [:customer :name]] + ["Product: " [:product :description]] + ["Count: "]] :file "homework/sales.txt"}}) (defn get-settings [settings] @@ -130,6 +135,7 @@ (def get-fieldname (get-settings :field)) (def get-data (get-settings :data)) (def get-format (get-settings :format)) +(def get-save-promt (get-settings :save-promt)) (defn read-string* [s] @@ -185,7 +191,6 @@ "Ищет первую строку, попадающую под условия"[db table fieeld x] (first (find-row db table fieeld x))) - (defn format-field "Форматирует поле таблицы в соответсвии с заданным форматом. Заодно подгружает название из других таблиц, если есть справочник" @@ -265,22 +270,87 @@ (let [name (read-line)] (println (format fmt name (calc-report db rep name))))) db) +;;************************************* +;; Функции для добавления записей + +(defn create-string + "Принимает вектор значений и возвращает строку, как в файле" + [row] + (->> (vals row) + (map str) + (str/join "|"))) + +(defn save-table + "Сохраняет таблицу в файл" + [db table] + (spit + (io/resource (get-filename db table)) + (->> (get-data db table) + (map create-string) + (str/join "\n"))) + db) +(defn promt-cell + "Запрашивает у пользователя ввод очередного поля. + Проверяет его на допустимость и возвращает нужное значение" + [acc exp] + (let [[db _] acc + [promt [table field]] exp] + (loop [] + (println promt) + (let [name (read-line) + id (:id (find-first-row db table field name))] + (cond + (nil? table) name + (some? id) id + :else (recur)))))) + +(defn add-cell + "Добавляет новую ячейку в строку" + [acc exp] + (update-in acc [1] conj (promt-cell acc exp))) + + +(defn promt-row + "Запрашивает все данные для новой записи" + [db table] + (let [max-id (apply max (map :id (get-data db table))) + row (reduce add-cell [db [(str (inc max-id))]] (get-save-promt db table))] + (-> (row 1) + (#(str/join "|" %))))) + + +(defn new-row + "Реализует пункт меню с добавлением записей" + [db table] + (->> (promt-row db table) + ((add-row table) db) + (#(save-table % table)))) + + ;;************************************* ;; Функции для работы с меню -(def menu {"1" {:name-menu "Display Customer Table" - :func #(show-table % :customer)} - "2" {:name-menu "Display Product Table" - :func #(show-table % :product)} - "3" {:name-menu "Display Sales Table" - :func #(show-table % :sales)} - "4" {:name-menu "Total Sales for Customer" - :func #(show-report % :cost-by-user)} - "5" {:name-menu "Total Count for Product" - :func #(show-report % :prod-count)} - "6" {:name-menu "Exit" - :func nil}}) +(def menu (into + (sorted-map) + {"1" {:name-menu "Display Customer Table" + :func #(show-table % :customer)} + "2" {:name-menu "Display Product Table" + :func #(show-table % :product)} + "3" {:name-menu "Display Sales Table" + :func #(show-table % :sales)} + "4" {:name-menu "Total Sales for Customer" + :func #(show-report % :cost-by-user)} + "5" {:name-menu "Total Count for Product" + :func #(show-report % :prod-count)} + "6" {:name-menu "New Customer" + :func #(new-row % :customer)} + "7" {:name-menu "New Customer" + :func #(new-row % :product)} + "8" {:name-menu "New Customer" + :func #(new-row % :sales)} + "9" {:name-menu "Exit" + :func nil}})) (defn get-user-choice "Выводит меню и просит сделать выбор" From e3e2afa42d7b3eced93b44be8b6562f822b13786 Mon Sep 17 00:00:00 2001 From: Vadych Date: Tue, 30 May 2023 11:42:39 +0300 Subject: [PATCH 4/5] code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Убрал loop из -main. --- otus-06/src/otus_06/homework.clj | 54 +++++++++++++++++--------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/otus-06/src/otus_06/homework.clj b/otus-06/src/otus_06/homework.clj index c277e6b..3770539 100644 --- a/otus-06/src/otus_06/homework.clj +++ b/otus-06/src/otus_06/homework.clj @@ -98,6 +98,16 @@ ;; Файлы находятся в папке otus-06/resources/homework +(defn get-user-answer + "Принимает запрос к пользователю и функцию pf, которой проверяется + ответ пользователя. Если возвращается nil, повторяет запрос" + [promt pf] + (loop [] + (println promt) + (if-let [ans (pf (read-line))] + ans + (recur)))) + ;; **************************** ;; Функции для работы с БД @@ -114,7 +124,7 @@ :field-type [int str double] :format ["%3d" "%20s" "%5.2f"] :file "homework/prod.txt" - :save-promt [["Description: "] ["Cost:"] ["Phone: "]]} + :save-promt [["Description: "] ["Cost:"]]} :sales { :data [] :field [:id :customer-id :product-id :count] @@ -296,14 +306,10 @@ [acc exp] (let [[db _] acc [promt [table field]] exp] - (loop [] - (println promt) - (let [name (read-line) - id (:id (find-first-row db table field name))] - (cond - (nil? table) name - (some? id) id - :else (recur)))))) + (if (nil? table) + (get-user-answer promt nothing) + (get-user-answer promt + #(:id (find-first-row db table field %)))))) (defn add-cell "Добавляет новую ячейку в строку" @@ -331,6 +337,9 @@ ;;************************************* ;; Функции для работы с меню +(defn exit [_] + (System/exit 0)) + (def menu (into (sorted-map) {"1" {:name-menu "Display Customer Table" @@ -345,34 +354,27 @@ :func #(show-report % :prod-count)} "6" {:name-menu "New Customer" :func #(new-row % :customer)} - "7" {:name-menu "New Customer" + "7" {:name-menu "New Product" :func #(new-row % :product)} - "8" {:name-menu "New Customer" + "8" {:name-menu "New Sale" :func #(new-row % :sales)} "9" {:name-menu "Exit" - :func nil}})) + :func exit}})) + +(defn check-menu [answer] + (get-in menu [answer :func])) -(defn get-user-choice +(defn show-menu "Выводит меню и просит сделать выбор" [] (println "*** MAIN MENU ***") (doseq [[k v] menu] (println k " - " (:name-menu v))) - (loop [] - (println "Enter your choice: ") - (let [choice (read-line)] - (if (contains? menu choice) - choice - (recur))))) - - + (get-user-answer "Enter your choice: " check-menu)) + (defn -main "Точка входа. Крутит меню и запускает нужные функции" [] - (loop [db (load-db db)] - (when-let [func (get-in menu [(get-user-choice) :func])] - (recur (func db))))) + (reduce #(%2 %1) (load-db db) (repeatedly show-menu))) ;; (-main) - - From 62183091771892e31a7240a7f66029c6a2f283da Mon Sep 17 00:00:00 2001 From: Vadych Date: Thu, 1 Jun 2023 15:38:13 +0300 Subject: [PATCH 5/5] work on bugs 1 work on bugs 2 work on bugs 2 --- otus-06/src/otus_06/homework.clj | 46 ++++++++++++++++---------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/otus-06/src/otus_06/homework.clj b/otus-06/src/otus_06/homework.clj index 3770539..914d204 100644 --- a/otus-06/src/otus_06/homework.clj +++ b/otus-06/src/otus_06/homework.clj @@ -153,7 +153,7 @@ ;; корректно прочитать "123-456" "123 jonh" и т.п. ;; Решил обойти таким образом. ;; Буду благодарен за подсказку - (if (re-matches #"\d+\.*\d*" s) + (if (re-matches #"\d+\.?\d*" s) (edn/read-string s) s)) @@ -167,14 +167,13 @@ line) (zipmap fields))) -(defn add-row +(defn add-row "Добавляет строку в БД" - [table] - (fn [db line] - (update-in db - [table :data] - conj - (create-row line (get-fieldname db table))))) + [table db line] + (update-in db + [table :data] + conj + (create-row line (get-fieldname db table)))) (defn load-table @@ -185,7 +184,7 @@ (io/reader))] (->> f line-seq - (reduce (add-row table) db)))) + (reduce (partial add-row table) db)))) (defn load-db "Загружает всю БД" @@ -194,12 +193,12 @@ (defn find-row "Ищет строки в которых field = x" - [db table fieeld x] - (filterv #( = x (fieeld %))(get-data db table))) + [db table field x] + (filterv #( = x (field %))(get-data db table))) (defn find-first-row - "Ищет первую строку, попадающую под условия"[db table fieeld x] - (first (find-row db table fieeld x))) + "Ищет первую строку, попадающую под условия"[db table field x] + (first (find-row db table field x))) (defn format-field "Форматирует поле таблицы в соответсвии с заданным форматом. @@ -218,7 +217,7 @@ [db table] (let [format-str (get-format db table)] (doseq [row (get-data db table)] - (->> (vals row) + (->> (map row (get-fieldname db table)) (map (format-field db) format-str) (str/join " | ") println)) @@ -260,15 +259,15 @@ (let [[f-id [tbl fld]] exp] (fld (find-first-row db tbl :id (f-id row)))))) -(defn calc-report +(defn calc-report "Вычисляет значение, необходимое для отчета" [db rep name] (let [[f args] (get-rep-exp rep) [f-group [t-search f-id f-search]] (get-rep-groupby rep)] (if-let [id (f-id (find-first-row db t-search f-search name))] (let [rows (find-row db :sales f-group id) - arg-rows (for [r rows] - (map #(get-val db % r) args))] + arg-func (map #(partial get-val db %) args) + arg-rows (map (apply juxt arg-func) rows)] (* (apply + (map #(apply f %) arg-rows)) 1.0)) 0.0))) @@ -285,8 +284,8 @@ (defn create-string "Принимает вектор значений и возвращает строку, как в файле" - [row] - (->> (vals row) + [fields row] + (->> (map row fields) (map str) (str/join "|"))) @@ -295,9 +294,10 @@ [db table] (spit (io/resource (get-filename db table)) - (->> (get-data db table) - (map create-string) - (str/join "\n"))) + (let [fields (get-fieldname db table)] + (->> (get-data db table) + (map (partial create-string fields)) + (str/join "\n")))) db) (defn promt-cell @@ -330,7 +330,7 @@ "Реализует пункт меню с добавлением записей" [db table] (->> (promt-row db table) - ((add-row table) db) + (add-row table db) (#(save-table % table))))