#lang racket

;; a shorthand for use below
(define-syntax ⇒ 
  (syntax-rules ()
    [(⇒ antecedent consequent) (if antecedent consequent #t)]))

;; implementation 
(define-struct dictionary (l value? eq?))
;; the keys should probably be another parameter (exercise)

(define (initialize p eq) (make-dictionary '() p eq))
(define (put d k v)
  (make-dictionary (cons (cons k v) (dictionary-l d))
                   (dictionary-value? d)
                   (dictionary-eq? d)))
(define (rem d k)
  (make-dictionary
   (let loop ([l (dictionary-l d)])
     (cond
       [(null? l) l]
       [(eq? (caar l) k) (loop (cdr l))]
       [else (cons (car l) (loop (cdr l)))]))
   (dictionary-value? d)
   (dictionary-eq? d)))
(define (count d) (length (dictionary-l d)))
(define (value-for d k) (cdr (assq k (dictionary-l d))))
(define (has? d k) (pair? (assq k (dictionary-l d))))
(define (not-has? d) (lambda (k) (not (has? d k))))
;; end of implementation 

;; interface
(provide/contract 
 ;; predicates
 [dictionary? (-> any/c boolean?)]
 ;; basic queries
 ;; how many items are in the dictionary?
 [count       (-> dictionary? natural-number/c)]
 ;; does the dictionary define key k? 
 [has?        (->d ([d dictionary?] [k symbol?])
                   ()
                   [result boolean?]
                   #:post-cond
                   ((zero? (count d)) . ⇒ . (not result)))]
 ;; what is the value of key k in this dictionary? 
 [value-for   (->d ([d dictionary?]
                    [k (and/c symbol? (lambda (k) (has? d k)))])
                   ()
                   [result (dictionary-value? d)])]
 ;; initialization 
 ;; post condition: for all k in symbol, (has? d k) is false. 
 [initialize  (->d ([p contract?] [eq (p p . -> . boolean?)]) 
                   ()
                   [result (and/c dictionary? (compose zero? count))])]
 ;; commands 
 ;; Mitchell and McKim say that put shouldn't consume Void (null ptr) 
 ;; for v. We allow the client to specify a contract for all values
 ;; via initialize. We could do the same via a key? parameter
 ;; (exercise). add key k with value v to this dictionary 
 [put         (->d ([d dictionary?]
                    [k (and symbol? (not-has? d))]
                    [v (dictionary-value? d)])
                   ()
                   [result dictionary?]
                   #:post-cond
                   (and (has? result k) 
                        (= (count d) (- (count result) 1))
                        ([dictionary-eq? d] (value-for result k) v)))]
 ;; remove key k from this dictionary 
 [rem         (->d ([d dictionary?]
                    [k (and/c symbol? (lambda (k) (has? d k)))])
                   ()
                   [result (and/c dictionary? not-has?)]
                   #:post-cond
                   (= (count d) (+ (count result) 1)))])
;; end of interface
