## Sales Commission

### November 17, 2020

Today’s exercise is simple drill for beginning programmers; it was inspired by a student asking on a beginning programmer’s platform for someone to do his homework for him. The requested answer was in C++, and it was a few weeks ago, so I don’t feel too bad posting a Scheme solution:

Write a program that inputs each employee’s sales, then computes and prints the employee’s commission according to the formula \$200 plus 10% of sales. Stop the input using a sentinel value. At the end of the input, display a one-dimensional array containing all the commission amounts, and the total amount of commission paid. You may not use a switch statement or multiple if statements.

Your task is to write a solution to the student’s homework; don’t use C++. When you are finished, you are welcome to read or run a suggested solution, or to post your own solution or discuss the exercise in the comments below.

Pages: 1 2

### 5 Responses to “Sales Commission”

1. Informatimago said
```
;; 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)
(: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)
(:report (lambda (condition stream)
(format stream "Invalid record: ~S ~%because ~A"
(invalid-record-record condition)
(invalid-record-reason condition)))))

"
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
(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))))

(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))))
:success)

(with-open-file (input pathname)

(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/print-commissions))

(test/all)

(defun main (pathname &rest arguments)
(declare (ignore arguments))
(print-commissions
(mapc (function compute-commission)
(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.07

```
2. Daniel said

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:

```Type <Ctrl-D> to stop.
1: 6313.46
2: five
Error. Try again.
2: 353.70
3: 957.91
4: 4948.72
5: ^D
Commissions: [ 831.35, 235.37, 295.79, 694.87 ]
Total: 2057.38
```
3. Richard A. O'Keefe said

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.

4. pjbinformatimagocom said

@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,