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.

Advertisement

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)
      ((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.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,
    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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: