From 1ee2d591844c893e425cb57e9e2aa6dc14cda6d5 Mon Sep 17 00:00:00 2001 From: bondiano Date: Tue, 30 May 2023 21:34:54 +0600 Subject: [PATCH 1/2] finish 4 hw --- otus-06/project.clj | 3 +- otus-06/src/otus_06/homework.clj | 159 ++++++++++++++++++++++++++++++- 2 files changed, 160 insertions(+), 2 deletions(-) diff --git a/otus-06/project.clj b/otus-06/project.clj index aa12fd1..a1ea124 100644 --- a/otus-06/project.clj +++ b/otus-06/project.clj @@ -5,5 +5,6 @@ :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.11.1"] [aero "1.1.6"]] + :main otus-06.homework - :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..899a7f1 100644 --- a/otus-06/src/otus_06/homework.clj +++ b/otus-06/src/otus_06/homework.clj @@ -1,4 +1,6 @@ -(ns otus-06.homework) +(ns otus-06.homework + (:require [clojure.string :as str] + [clojure.java.io :as io])) ;; Загрузить данные из трех файлов на диске. ;; Эти данные сформируют вашу базу данных о продажах. @@ -94,3 +96,158 @@ ;; Файлы находятся в папке otus-06/resources/homework + +(def cust-file "resources/homework/cust.txt") +(def prod-file "resources/homework/prod.txt") +(def sales-file "resources/homework/sales.txt") + +(def table-schema + {:cust {:file cust-file :id 0 :name 1 :address 2 :phone 3} + :prod {:file prod-file :id 0 :name 1 :cost 2} + :sales {:file sales-file :id 0 :cust-id 1 :prod-id 2 :count 3}}) + +(defn map-line [line schema] + (into {} (for [[key value] (dissoc schema :file)] + [key (nth line value)]))) + +(defn clear-terminal [] + (print "\033c")) + +(defn parse-line [line] + (str/split line #"\|")) + +(defn for-each-line [file cb] + (with-open [rdr (io/reader file)] + (doseq [[index line] (map-indexed vector (line-seq rdr))] + (let [line (parse-line line)] + (cb line index))))) + +(defn format-row [row index] + (->> row + (mapv #(format "\"%s\"" %)) + (str/join ", ") + (format "%d: [%s]" (inc index)))) + +(defn print-table [file] + (for-each-line file (fn [line index] + (-> (format-row line index) + (println))))) + +(defn load-table [table] + (let [{:keys [file] :as schema} (table table-schema) + all-ids (atom []) + by-id (atom {})] + (for-each-line file (fn [line _] ;; do we have a way to pass callback with only one argument? + (let [[id] line + data (map-line line schema)] + (swap! all-ids conj id) + (swap! by-id assoc id data)))) + {:all-ids @all-ids + :by-id @by-id})) + +(defn load-tables [] + {:cust (load-table :cust) + :prod (load-table :prod) + :sales (load-table :sales)}) + +(defn find-by-id [db table id] + (get-in db [table :by-id id])) + +(defn find-by-name [db table name] + (let [table (get-in db [table :by-id]) + [[id data]] (filter (fn [[_key value]] (= name (:name value))) table)] + {:id id :data data})) + +(defn calc-total-sales-for-customer [db cust-id] + (let [sales (get-in db [:sales :by-id]) + sales-to-customer (filter (fn [[_key value]] (= cust-id (:cust-id value))) sales)] + (reduce (fn [acc [_key value]] + (let [count (Integer/parseInt (get value :count "0")) + product (get-in db [:prod :by-id (:prod-id value)]) + cost (Float/parseFloat (get product :cost "0.00"))] + (+ acc (* count cost)))) + 0 sales-to-customer))) + +(defn calc-total-count-for-product [db prod-id] + (let [sales (get-in db [:sales :by-id]) + product-sales (filter (fn [[_key value]] (= prod-id (:prod-id value))) sales)] + (reduce (fn [acc [_key value]] + (let [count (Integer/parseInt (get value :count "0"))] + (+ acc count))) + 0 product-sales))) + +(defn on-exit [running?] + (println "Goodbye") + (dosync (ref-set running? false))) + +(defn on-display-customer-table [] + (print-table cust-file)) + +(defn on-display-product-table [] + (print-table prod-file)) + +(defn on-display-sales-table [] + (let [db (load-tables)] + (doseq [[index id] (map-indexed vector (get-in db [:sales :all-ids])) + :let [{:keys [cust-id prod-id count]} (find-by-id db :sales id) + {cust-name :name} (find-by-id db :cust cust-id) + {prod-name :name} (find-by-id db :prod prod-id)]] + (println (format-row [cust-name prod-name count] index))))) + +(defn on-display-total-sales-for-customer [] + (let [db (load-tables)] + (println "Enter customer name:") + (let [customer-name (read-line) + {customer-id :id customer :data} (find-by-name db :cust customer-name)] + (if customer + (let [total (calc-total-sales-for-customer db customer-id)] + (println (format "Total Sales for Customer %s: $%.2f" customer-name total))) + (println (format "Customer %s not found" customer-name)))))) + +(defn on-display-total-count-for-product [] + (let [db (load-tables)] + (println "Enter product name:") + (let [product-name (read-line) + {product-id :id product :data} (find-by-name db :prod product-name)] + (if product + (let [count (calc-total-count-for-product db product-id)] + (println (format "Total Count of Product %s: %d" product-name count))) + (println (format "Product %s not found" product-name)))))) + +(defn render-menu [menu] + (clear-terminal) + (println "*** Sales Menu ***") + (println "==================") + (doseq [[index item] (map-indexed vector menu)] + (println (str (inc index) ". " (:title item)))) + (println "Enter an option? ")) + +(defn get-user-input [] + (let [input (read-line)] + (try + (Integer/parseInt input) + (catch Exception e + (println "Invalid input") + -1)))) + +(defn start-app + "Displaying main menu and processing user choices." + [] + (let [running? (ref true) + menu [{:title "Display Customer Table" :action on-display-customer-table} + {:title "Display Product Table" :action on-display-product-table} + {:title "Display Sales Table" :action on-display-sales-table} + {:title "Total Sales for Customer" :action on-display-total-sales-for-customer} + {:title "Total Count for Product" :action on-display-total-count-for-product} + {:title "Exit" :action (partial on-exit running?)}]] + (render-menu menu) + (while @running? + (let [choice (get-user-input) + item (get menu (dec choice)) + action (get item :action (fn []))] + (action))))) + +(defn -main + "Main function calling app." + [& _args] + (start-app)) From 2569a54d32b9ff0ee1e8397a369ca37f046ad3bb Mon Sep 17 00:00:00 2001 From: bondiano Date: Fri, 2 Jun 2023 21:52:16 +0600 Subject: [PATCH 2/2] fixes after review --- otus-06/src/otus_06/homework.clj | 82 ++++++++++++++++---------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/otus-06/src/otus_06/homework.clj b/otus-06/src/otus_06/homework.clj index 899a7f1..8d09014 100644 --- a/otus-06/src/otus_06/homework.clj +++ b/otus-06/src/otus_06/homework.clj @@ -102,12 +102,12 @@ (def sales-file "resources/homework/sales.txt") (def table-schema - {:cust {:file cust-file :id 0 :name 1 :address 2 :phone 3} - :prod {:file prod-file :id 0 :name 1 :cost 2} - :sales {:file sales-file :id 0 :cust-id 1 :prod-id 2 :count 3}}) + {:cust {:file cust-file :title "customer" :id 0 :name 1 :address 2 :phone 3} + :prod {:file prod-file :title "product" :id 0 :name 1 :cost 2} + :sales {:file sales-file :title "sales" :id 0 :cust-id 1 :prod-id 2 :count 3}}) (defn map-line [line schema] - (into {} (for [[key value] (dissoc schema :file)] + (into {} (for [[key value] (dissoc schema :file :title)] [key (nth line value)]))) (defn clear-terminal [] @@ -116,12 +116,6 @@ (defn parse-line [line] (str/split line #"\|")) -(defn for-each-line [file cb] - (with-open [rdr (io/reader file)] - (doseq [[index line] (map-indexed vector (line-seq rdr))] - (let [line (parse-line line)] - (cb line index))))) - (defn format-row [row index] (->> row (mapv #(format "\"%s\"" %)) @@ -129,21 +123,26 @@ (format "%d: [%s]" (inc index)))) (defn print-table [file] - (for-each-line file (fn [line index] - (-> (format-row line index) - (println))))) + (with-open [rdr (io/reader file)] + (->> (line-seq rdr) + (map parse-line) + (map-indexed vector) + (mapv (fn [[index line]] + (format-row line index))) + (str/join "\n") + (println)))) (defn load-table [table] - (let [{:keys [file] :as schema} (table table-schema) - all-ids (atom []) - by-id (atom {})] - (for-each-line file (fn [line _] ;; do we have a way to pass callback with only one argument? - (let [[id] line - data (map-line line schema)] - (swap! all-ids conj id) - (swap! by-id assoc id data)))) - {:all-ids @all-ids - :by-id @by-id})) + (let [{:keys [file] :as schema} (table table-schema)] + (with-open [rdr (io/reader file)] + (->> (line-seq rdr) + (map parse-line) + (mapv #(map-line % schema)) + (reduce (fn [acc value] + (-> acc + (update :by-id assoc (:id value) value) + (update :all-ids conj (:id value)))) + {:by-id {} :all-ids []}))))) (defn load-tables [] {:cust (load-table :cust) @@ -194,25 +193,26 @@ {prod-name :name} (find-by-id db :prod prod-id)]] (println (format-row [cust-name prod-name count] index))))) +(defn handle-entity-metric [entity-type metric-calc-fn] + (let [db (load-tables) + title (str/capitalize (get-in table-schema [entity-type :title]))] + (println (format "Enter %s name:" title)) + (let [entity-name (read-line) + {entity-id :id entity :data} (find-by-name db entity-type entity-name)] + (if entity + (let [result (metric-calc-fn db entity-id)] + (println (format "Total for %s %s: %s" + title entity-name result))) + (println (format "%s %s not found" title entity-name)))))) + (defn on-display-total-sales-for-customer [] - (let [db (load-tables)] - (println "Enter customer name:") - (let [customer-name (read-line) - {customer-id :id customer :data} (find-by-name db :cust customer-name)] - (if customer - (let [total (calc-total-sales-for-customer db customer-id)] - (println (format "Total Sales for Customer %s: $%.2f" customer-name total))) - (println (format "Customer %s not found" customer-name)))))) + (handle-entity-metric :cust (comp #(format "%.2f" %) calc-total-sales-for-customer))) (defn on-display-total-count-for-product [] - (let [db (load-tables)] - (println "Enter product name:") - (let [product-name (read-line) - {product-id :id product :data} (find-by-name db :prod product-name)] - (if product - (let [count (calc-total-count-for-product db product-id)] - (println (format "Total Count of Product %s: %d" product-name count))) - (println (format "Product %s not found" product-name)))))) + (handle-entity-metric :prod calc-total-count-for-product)) + +(defn on-no-such-action [] + (println "Not valid option. Please enter a valid one.")) (defn render-menu [menu] (clear-terminal) @@ -226,7 +226,7 @@ (let [input (read-line)] (try (Integer/parseInt input) - (catch Exception e + (catch Exception _ (println "Invalid input") -1)))) @@ -244,7 +244,7 @@ (while @running? (let [choice (get-user-input) item (get menu (dec choice)) - action (get item :action (fn []))] + action (get item :action on-no-such-action)] (action))))) (defn -main