Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

homework-06-ataranchiev #8

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions otus-06/project.clj
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
(defproject otus-06 "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
:url "https://www.eclipse.org/legal/epl-2.0/"}
:dependencies [[org.clojure/clojure "1.11.1"]
[aero "1.1.6"]]

:repl-options {:init-ns otus-06.core})
:license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0",
:url "https://www.eclipse.org/legal/epl-2.0/"}
:dependencies [[org.clojure/clojure "1.11.1"] [aero "1.1.6"]]
:repl-options {:init-ns otus-06.core}
:main otus-06.homework
:aot [otus-06.homework])
222 changes: 211 additions & 11 deletions otus-06/src/otus_06/homework.clj
Original file line number Diff line number Diff line change
@@ -1,4 +1,43 @@
(ns otus-06.homework)
(ns otus-06.homework
(:gen-class)
(:require [clojure.string :as string]
[clojure.java.io :as io]))

;; Helpers
(defn clear-screen
[]
(print (str (char 27) "[2J")) ; clear screen
(print (str (char 27) "[;H")) ; move cursor to the top left corner of the
; screen
)

(defn wait-for-enter
[]
(let [dot-count (atom 0)
animation ["/" "-" "\\" "|"]]
(while (zero? (.available System/in))
(print (str "\r" "Hit Enter " (nth animation @dot-count) " "))
(flush)
(Thread/sleep 1000)
(swap! dot-count #(if (= % 3) 0 (inc %))))))

;; Patch for (read-line) not working in terminal
;; https://stackoverflow.com/questions/7707558/clojure-read-line-doesnt-wait-for-input
(defn my-read-line [] (nth (line-seq (java.io.BufferedReader. *in*)) 1))


;; Main
(defn base-loader
"All in memory.
Instanciates via vec."
([file-name schema filter-k filter-v]
(with-open [f (io/reader (io/resource file-name))]
(vec (for [line (line-seq f)
:let [result (zipmap schema (string/split line #"[|]"))]
:when (if (some? filter-k)
(string/includes? (get result filter-k nil) filter-v)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just got noticed, that here might be a problem if ID is small and may intersect with others as a same sub-string. Will think about it

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't get your point. You split string by separator read and then it doesn't matter is ID big or small, as I think

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For instance "3" will be caught in ids like "33" "30" and so on. Because "inclides?" will find it as a sub string.

true)]
result)))))

;; Загрузить данные из трех файлов на диске.
;; Эти данные сформируют вашу базу данных о продажах.
Expand All @@ -7,16 +46,38 @@

;; cust.txt: это данные для таблицы клиентов. Схема:
;; <custID, name, address, phoneNumber>
(def customer-schema [:custID :name :address :phoneNumber])

;; Примером файла cust.txt может быть:
;; 1|John Smith|123 Here Street|456-4567
;; 2|Sue Jones|43 Rose Court Street|345-7867
;; 3|Fan Yuhong|165 Happy Lane|345-4533

;; Каждое поле разделяется символом «|». и содержит непустую строку.
(defn get-customers
([] (get-customers nil nil))
([filter-k filter-v]
(base-loader "homework/cust.txt" customer-schema filter-k filter-v)))


(comment
(get-customers))
; [{"custID" "1",
; "name" "John Smith",
; "address" "123 Here Street",
; "phoneNumber" "456-4567"}
; {"custID" "2",
; "name" "Sue Jones",
; "address" "43 Rose Court Street",
; "phoneNumber" "345-7867"}
; {"custID" "3",
; "name" "Fan Yuhong",
; "address" "165 Happy Lane",
; "phoneNumber" "345-4533"}]

;; prod.txt: это данные для таблицы продуктов. Схема
;; <prodID, itemDescription, unitCost>
(def product-schema [:prodID :itemDescription :unitCost])

;; Примером файла prod.txt может быть:
;; 1|shoes|14.96
Expand All @@ -25,36 +86,152 @@
;; 4|gum|1.25
;; 5|eggs|2.98
;; 6|jacket|42.99
(defn get-products
([] (get-products nil nil))
([filter-k filter-v]
(base-loader "homework/prod.txt" product-schema filter-k filter-v)))

(comment
(get-products))
; [{"prodID" "1", "itemDescription" "shoes", "unitCost" "14.96"}
; {"prodID" "2", "itemDescription" "milk", "unitCost" "1.98"}
; {"prodID" "3", "itemDescription" "jam", "unitCost" "2.99"}
; {"prodID" "4", "itemDescription" "gum", "unitCost" "1.25"}
; {"prodID" "5", "itemDescription" "eggs", "unitCost" "2.98"}
; {"prodID" "6", "itemDescription" "jacket", "unitCost" "42.99"}]

;; sales.txt: это данные для основной таблицы продаж. Схема:
;; <salesID, custID, prodID, itemCount>.
(def sales-schema [:salesID :custID :prodID :itemCount])
;;
;; Примером дискового файла sales.txt может быть:
;; 1|1|1|3
;; 2|2|2|3
;; 3|2|1|1
;; 4|3|3|4

;; Например, первая запись (salesID 1) указывает, что Джон Смит (покупатель 1) купил 3 пары обуви (товар 1).
(defn get-sales
([] (get-sales nil nil))
([filter-k filter-v]
(base-loader "homework/sales.txt" sales-schema filter-k filter-v)))

(comment
(get-sales))
; [{"salesID" "1", "custID" "1", "prodID" "1", "itemCount" "3"}
; {"salesID" "2", "custID" "2", "prodID" "2", "itemCount" "3"}
; {"salesID" "3", "custID" "2", "prodID" "1", "itemCount" "1"}
; {"salesID" "4", "custID" "3", "prodID" "3", "itemCount" "4"}]

;; Например, первая запись (salesID 1) указывает, что Джон Смит (покупатель 1)
;; купил 3 пары обуви (товар 1).

;; Задача:
;; Предоставить следующее меню, позволяющее пользователю выполнять действия с данными:
;; Предоставить следующее меню, позволяющее пользователю выполнять действия с
;; данными:

;; *** Sales Menu ***
;; ------------------

;; 1. Display Customer Table
(defn display-customers
[]
(doseq [line (for [customer (get-customers)
:let [{:keys [name address phoneNumber]} customer]]
(format "Name: %s, Address: %s, Phone: %s"
name
address
phoneNumber))]
(println line)))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discover full magic of destructuring and avoid that complex nested doseq & for with let :)

(defn display-customers
  []
  (doseq [{:keys [name address phoneNumber]} (get-customers)]
    (println (format "Name: %s, Address: %s, Phone: %s"
                     name
                     address
                     phoneNumber))))

Copy link
Author

@ewok ewok Jul 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm your solution works.... Actually I tried it and it didn't work. Seems that I made another mistake and decided that it is not possible in "for" without "let". Funny =)...
Thanks


;; 2. Display Product Table
(defn display-products
[]
(doseq [line (for [product (get-products)
:let [{:keys [itemDescription unitCost]} product]]
(format "Name: %s, Cost: $%s" itemDescription unitCost))]
(println line)))

;; 3. Display Sales Table
(defn display-sales
[]
(let [customers (into {}
(for [customer (get-customers)
:let [{:keys [custID name]} customer]]
[custID name]))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You do know zipmap, I saw it above :)

(let [cs (get-customers)] (zipmap (map :custID cs) (map :name cs)))

But yes, it's a matter of taste

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice variant thanks 👍🏼

products (into {}
(for [product (get-products)
:let [{:keys [prodID itemDescription]} product]]
[prodID itemDescription]))]
(doseq [line (for [sales (get-sales)
:let [{:keys [custID prodID itemCount]} sales
client (get customers custID)
product (get products prodID)]]
(format "Client: %s, Product: %s, Count: %s"
client
product
itemCount))]
(println line))))

;; 4. Total Sales for Customer
(defn show-sales-customer
"Supports showing result for multiple customers."
[]
(print "Enter customer name: ")
(flush)
(let [name (my-read-line)
product-prices (into {}
(for [product (get-products)
:let [{:keys [prodID unitCost]} product]]
[prodID unitCost]))
sales (apply merge-with
+
(for [customer (get-customers :name name)
:let [{:keys [custID name]} customer]
sales (get-sales :custID custID)
:let [{:keys [itemCount prodID]} sales
sum (* (Double. itemCount)
(Double. (get product-prices prodID)))]]
{name sum}))
sales-with-default (if (seq sales) sales {"Not found" 0})]
(doseq [line sales-with-default]
(println (format "Name: %s, Sum: $%s" (key line) (val line))))))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, the power of destructuring (doseq [[k v] sales-with-default] ...


;; 5. Total Count for Product

(defn show-sales-product
[]
(print "Enter product name: ")
(flush)
(let [item-desc (my-read-line)
sales (apply merge-with
+
(for [product (get-products :itemDescription item-desc)
:let [{:keys [prodID itemDescription]} product]
sales (get-sales :prodID prodID)
:let [{:keys [itemCount]} sales]]
{itemDescription (Double. itemCount)}))
sales-with-default (if (seq sales) sales {"Not found" 0})]
(doseq [line sales-with-default]
(println (format "Product: %s, Count sold: %s" (key line) (val line))))))
;; 6. Exit
(defn exit [] (println "Bye!") (System/exit 0))

;; Enter an option?

(defn menu
[]
(clear-screen)
(doseq [menu ["1. Display Customer Table" "2. Display Product Table"
"3. Display Sales Table" "4. Total Sales for Customer"
"5. Total Count for Product" "6. Exit"]]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dude, make it readable, split on different lines :)

"1. ...
"2. ...
...
"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know. But auto formatter did that

(println menu))
(flush)
(read))

;; Варианты будут работать следующим образом

;; 1. Вы увидите содержимое таблицы Customer. Вывод должен быть похож (не обязательно идентичен) на
;; 1. Вы увидите содержимое таблицы Customer. Вывод должен быть похож (не
;; обязательно идентичен) на

;; 1: ["John Smith" "123 Here Street" "456-4567"]
;; 2: ["Sue Jones" "43 Rose Court Street" "345-7867"]
Expand All @@ -64,7 +241,8 @@

;; 3. Таблица продаж немного отличается.
;; Значения идентификатора не очень полезны для целей просмотра,
;; поэтому custID следует заменить именем клиента, а prodID — описанием продукта, как показано ниже:
;; поэтому custID следует заменить именем клиента, а prodID — описанием
;; продукта, как показано ниже:
;; 1: ["John Smith" "shoes" "3"]
;; 2: ["Sue Jones" "milk" "3"]
;; 3: ["Sue Jones" "shoes" "1"]
Expand All @@ -76,21 +254,43 @@
;; Sue Jones: $20.90

;; Это соответствует 1 паре обуви и 3 пакетам молока.
;; Если клиент недействителен, вы можете либо указать это в сообщении, либо вернуть $0,00 за результат.
;; Если клиент недействителен, вы можете либо указать это в сообщении, либо
;; вернуть $0,00 за результат.

;; 5. Здесь мы делаем то же самое, за исключением того, что мы вычисляем количество продаж для данного продукта.
;; 5. Здесь мы делаем то же самое, за исключением того, что мы вычисляем
;; количество продаж для данного продукта.
;; Итак, для обуви у нас может быть:
;; Shoes: 4

;; Это представляет три пары для Джона Смита и одну для Сью Джонс.
;; Опять же, если продукт не найден, вы можете либо сгенерировать сообщение, либо просто вернуть 0.
;; Опять же, если продукт не найден, вы можете либо сгенерировать сообщение,
;; либо просто вернуть 0.

;; 6. Наконец, если выбрана опция «Выход», программа завершится с сообщением «До свидания».
;; 6. Наконец, если выбрана опция «Выход», программа завершится с сообщением
;; «До
;; свидания».
;; В противном случае меню будет отображаться снова.


;; *** Дополнительно можно реализовать возможность добавлять новые записи в исходные файлы
;; Например добавление нового пользователя, добавление новых товаров и новых данных о продажах
;; *** Дополнительно можно реализовать возможность добавлять новые записи в
;; исходные файлы
;; Например добавление нового пользователя, добавление новых товаров и
;; новых
;; данных о продажах


;; Файлы находятся в папке otus-06/resources/homework

(defn menu-item [func] (clear-screen) (func) (wait-for-enter))

(defn -main
[& args]
(while true
(condp = (menu)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

case will look nice here, but it's also a matter of taste

1 (menu-item display-customers)
2 (menu-item display-products)
3 (menu-item display-sales)
4 (menu-item show-sales-customer)
5 (menu-item show-sales-product)
6 (exit)
(do (println "Error: Non existing menu item.\n") (wait-for-enter)))))