Sales Commission
November 17, 2020
Here is our solution:
> (define (commission)
(let loop ((commissions (list)))
(display "Enter sales amount or CTRL-D: ")
(let ((sale (read)))
(if (eof-object? sale)
(begin (newline)
(values (list->vector (reverse commissions))
(sum commissions)))
(let ((commission (+ (* sale 0.1) 200)))
(display " Commission is ")
(display commission) (newline)
(loop (cons commission commissions)))))))
> (commission)
Enter sales amount or CTRL-D: 1000
Commission is 300.0
Enter sales amount or CTRL-D: 500
Commission is 250.0
Enter sales amount or CTRL-D: 2500
Commission is 450.0
Enter sales amount or CTRL-D: 2000
Commission is 400.0
Enter sales amount or CTRL-D:
#(300.0 250.0 450.0 400.0)
1400.0
You can run the program at https://ideone.com/9G4Ngf.
;; Input file format: ;; Fixed size record: ;; employee name 60 chars ;; sales amount 12 chars ;; Sentinel record: employee name = "END" ;; ;; Compute: ;; Commission = $200 + 10% of sales amount ;; ;; Output format: ;; Employee name | Commission ;; --------------|----------------- ;; Total: | Total Commission (defstruct (commission-entry (:type list) (:conc-name nil)) employee-name sales-amount commission) (defvar *commission-rate* 10/100) (defun compute-commission (entry) (setf (commission entry) (+ 200.00 (* *commission-rate* (sales-amount entry))))) (define-condition invalid-sales (parse-error) ((field :initarg :field :reader invalid-sales-field) (employee-name :initarg :employee-name :reader invalid-sales-employee-name)) (:report (lambda (condition stream) (format stream "Invalid sales field: ~S for employee ~S" (invalid-sales-field condition) (invalid-sales-employee-name condition))))) (define-condition invalid-record (file-error) ((record :initarg :record :reader invalid-record-record) (reason :initarg :reason :reader invalid-record-reason)) (:report (lambda (condition stream) (format stream "Invalid record: ~S ~%because ~A" (invalid-record-record condition) (invalid-record-reason condition))))) (defun read-employee-sales (stream) " Reads fixed length records, separated by a newline: employee name: 60 chars (space padded) sales amount: 12 chars Reading ends when the name is 'END'. Return a list of (employee-name sales-amount). " (loop :with result := '() :for record := (read-line stream nil nil) :while record :do (if (<= 72 (length record)) (let ((name (string-trim " " (subseq record 0 60))) (sales (ignore-errors (let ((*read-eval* nil) (*read-default-float-format* 'double-float)) (read-from-string (string-trim " " (subseq record 60 72))))))) (when (string= "END" name) (loop-finish)) (if (realp sales) (push (make-commission-entry :employee-name name :sales-amount sales) result) (cerror "Ignore the record" 'invalid-sales :field (subseq record 60 72) :employee-name name))) (cerror "Ignore the record" 'invalid-record :record record :reason "record is too small")) :finally (return (nreverse result)))) (defun test/read-employee-sales () (assert (equal '(("John Wayne" 123456.89d0 nil) ("Chuck Norris" 999910.50d0 nil) ("Bruce Willis" 1504242.33d0 nil)) (with-input-from-string (input " John Wayne 000123456.89 Chuck Norris 999910.50 Bruce Willis 001504242.33 END 000000000.00 Bugs Bunny 1.00 ") (handler-bind ((invalid-record (lambda (condition) (declare (ignore condition)) (invoke-restart 'continue)))) (read-employee-sales input))))) :success) (defun read-employee-sales-file (pathname) (with-open-file (input pathname) (read-employee-sales input))) (defun print-commissions (entries) (let ((total (reduce (function +) entries :key (function commission) :initial-value 0.0d0)) (ftitle "~60A | ~12A~%") (fentry "~60A | ~12,2F~%") (fline (format nil "~60@{-~}-|-~12@{-~}~%" nil))) (format t ftitle "Employee Name" "Commission") (write-string fline) (dolist (entry entries) (format t fentry (employee-name entry) (commission entry))) (write-string fline) (format t fentry "Total:" total) (values))) (defun test/print-commissions () (assert (equal (with-output-to-string (*standard-output*) (print-commissions (mapc (function compute-commission) (list (make-commission-entry :employee-name "John Wayne" :sales-amount 123456.89d0) (make-commission-entry :employee-name "Chuck Norris" :sales-amount 999910.50d0) (make-commission-entry :employee-name "Bruce Willis" :sales-amount 1504242.33d0))))) "Employee Name | Commission -------------------------------------------------------------|------------- John Wayne | 12545.69 Chuck Norris | 100191.05 Bruce Willis | 150624.23 -------------------------------------------------------------|------------- Total: | 263360.97 ")) :success) (defun test/all () (test/read-employee-sales) (test/print-commissions)) (test/all) (defun main (pathname &rest arguments) (declare (ignore arguments)) (print-commissions (mapc (function compute-commission) (read-employee-sales-file pathname))) (values)) ;; (main #P"~/src/public/common-lisp-exercises/programming-praxis/20201117--sales-commission.input") ;; Employee Name | Commission ;; -------------------------------------------------------------|------------- ;; John Wayne | 12545.69 ;; Chuck Norris | 100191.05 ;; Bruce Willis | 150624.23 ;; Michel Serrault | 200.10 ;; Michel Bouquet | 300.00 ;; Michel Blanc | 10000200.00 ;; -------------------------------------------------------------|------------- ;; Total: | 10264061.07Reminder: You can find all my solutions at https://gitlab.com/common-lisp-exercises/programming-praxis
Here’s a solution in Python.
from decimal import Decimal TWO_PLACES = Decimal('0.01') print('Type <Ctrl-D> to stop.') commissions = [] while True: try: prompt = str(len(commissions) + 1) + ': ' sales = Decimal(input(prompt)) commissions.append(200 + sales / 10) except EOFError: break except Exception: print('Error. Try again.') comma_sep_values = ', '.join(str(x.quantize(TWO_PLACES)) for x in commissions) print('Commissions: [', comma_sep_values, ']') print('Total:', sum(commissions).quantize(TWO_PLACES))Example usage:
After reading the problem specification, I realised that I did not know what was required. After looking at the preceding solutions, I realised that other people thought they knew what was required but did not agree, which made me feel
less stupid. We need either an unambiguous specification or an input-output sample. At the very least, a clear statement like “The input consists of sales numbers for a single employee, and the $200 base is applicable to each sale, even a sale of $0.” or that “The input may contain (name sale) amounts for multiple employees and the sales for a single employee need not be adjacent” or whatever.
As it stands, this is not an interesting exercise in programming but a frustrating exercise in struggling to deal with a hopeless vague specification.
@Richard A. O’Keefe : indeed, this is the normal situation in real life.
Customers come to you with problem statements that are in general:
idiotic,
ill-defined,
wrong,
not what the customer wanted,
incomplete,
contradictory,
ambiguous,
the examples are wrong and inconsistent,
and so on.
A customer cannot provide you with a formal specification, or just a consistent unambiguous problem statement, because if they could produce that, they could be programmers themselves!
So indeed, the first task, is to clarify the problem statement. In absence of a closed tight interaction loop with the customer (the teacher), you can only write down the specifications yourself. Note how my solution starts with a 13-line comment giving precisions on the input format, the output format, and the formula to be used.