summaryrefslogtreecommitdiff
path: root/northwoods/guile.scm
blob: 8dea3158486572e3b9eeeed4982e188cd9727495 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#|
Babysitter Kata

Background
----------
This kata simulates a babysitter working and getting paid for one night.  The rules are pretty straight forward:

The babysitter
- starts no earlier than 5:00PM
- leaves no later than 4:00AM
- gets paid $12/hour from start-time to bedtime
- gets paid $8/hour from bedtime to midnight
- gets paid $16/hour from midnight to end of job
- gets paid for full hours (no fractional hours)


Feature:
As a babysitter
In order to get paid for 1 night of work
I want to calculate my nightly charge
|#

;; doing this in guile scheme because they said i could use any language, and if
;; i'm gonna be writing code on saturday morning its gonna be lisp :P

(import (only (rnrs base) assert))

(define +max+ 4) ;; can't work longer than 4am
(define +start+ 5) ;; can't start before 5pm

;; times are represented as a pair of (hours . minutes)
(define (h time) (car time))
(define (m time) (cdr time))

;; if hour is > 1 && < +max+, add 12h to account for midnight rollover
;; (always call this on end time)
(define (handle-midnight time)
  (if (and (>= (h time) 1) (<= (h time) +max+))
      `(,(+ 12 (h time)) . ,(m time))
      time))

;; round-* functions return just the hour for simplicity, but ideally these
;; would operate on a <time> object that has + and - methods defined

(define (round-down time) (h time))

(define (round-up time)
  (if (> (m time) 0)
      (+ (h time) 1)
      (h time)))

;; lookup table for pay rates
(define (payrate s)
  (case s
    ((prebed) 12)       ;; before baby is asleep
    ((postbed) 8)       ;; after baby is asleep
    ((postmidnight) 16) ;; from midnight until 4am or end of job
    (else #f)           ;; bad input, blowup
    ))

;; better way to do this would be with contracts, or parsing, or objects, but
;; base scheme doesn't have these so whatever
(define (valid-inputs? start end bedtime)
  (and (pair? start)
       (pair? end)
       (pair? bedtime)
       (or (>= (h start) +start+)
           (error "cannot start before 5pm"))
       (or (<= (h (handle-midnight end)) (h (handle-midnight `(,+max+ . 00))))
           (error "working hours must be between 5pm-4am (+start+ and +max+)"))))

;; after validating and conforming the data, the core of the program is just
;; bucket the hours by payrate and sum them up
(define (charge start end bedtime)
  (if (valid-inputs? start end bedtime)
      (let ((hours-prebed (- (round-up bedtime) (round-down start)))
            (hours-postbed (- 12 (round-up bedtime)))
            (hours-postmidnight (- (round-up (handle-midnight end)) 12)))
        (+ (* (payrate 'prebed) hours-prebed)
           (* (payrate 'postbed) hours-postbed)
           (* (payrate 'postmidnight) hours-postmidnight)))
      #f))

;; simple tests
(define (test)
  ;; happy path
  ;; 3*12 + 4*8 + 3*12 = 116
  (assert (= 116
             (charge
              '(5 . 34)
              '(2 . 45) ;; late night!
              '(8 . 00))))
  ;; 12*4 + 8*2 + 16*0 = 64
  (assert (= 64
             (charge
              '(6 . 00)
              '(11 . 30) ;; this is more my style
              '(9 . 15))))
  ;; test bad inputs
  (assert (equal? #f (charge '(60 . 60) "string" 1234))))