Helper functions; One Task One Function

When designing our data to match information from the problem domain it is typically the case that our data will have dependencies. For example, recall the last set of problems from Lab 2 that dealt with Media and Peripherals

Here are the data definitions

;;;; Data Definitions

;; A Media is one of
;; - 'dvd
;; - 'cd
;; - 'blueray
;; - 'floppy
;; INTERP: represents each kind of media kept at the store

;; media-fn: Media -> ???
#; (define (media-fn media)
     (cond
       [(symbol=? 'dvd media) ...]
       [(symbol=? 'cd media) ...]
       [(symbol=? 'blueray media) ...]
       [(symbol=? 'floppy media) ...]))

;; A Peripheral is one of
;; - "keyboard"
;; - "mouse"
;; - "driving wheel"
;; - "headset"
;; - "controller"
;; INTERP: represents each kind of peripheral kept at the store

;; peripheral-fn: Peripheral -> ???
#; (define (peripheral-fn peripheral)
     (cond
       [(string=? "keyboard" peripheral) ...]
       [(string=? "mouse" peripheral) ...]
       [(string=? "driving wheel" peripheral) ...]
       [(string=? "headset" peripheral) ...]
       [(string=? "controller" peripheral) ...]))

Now we want to have a new data definition MediaOrPeripheral that can be either a Media or a Peripheral.

;; A MediaOrPeripheral is one of
;; - Media
;; - Peripheral

We rely (or depend) on our existing data definitions of Media and Peripheral in order to define our new data definition MediaOrPeripheral. Let’s try to write our deconstructor template for MediaOrPeripheral

;; media-or-peripheral: MediaOrPeripheral -> ???
#; (define (media-or-peripheral media-peripheral)
     (cond
       [(XXX media-peripheral) ...]
       [(YYY media-peripheral) ...]))

Our deconstructor template is not complete and we are a little stuck. We are not sure what to use in the place of XXX and YYY? What do we need to do for the first cond -clause? We need to figure out a function that we can call at the location of XXX that will return true when media-peripheral is a Media and false otherwise. Similarly we want a function at the location of YYYY that will return true when media-peripheral is a Peripheral and false otherwise. So let’s design these two functions.

We will use media? to check if a Racket value is a Media and peripheral? to check if a Racket value is a Peripheral.

;; media? : Any -> Boolean
;;;; Purpose:
;; GIVEN: any Racket value
;; RETURNS: true of the input is kind of media, false otherwise

;;;; Examples
;; (media? 1) => #false
;; (media? "dvd") => #false
;; (media? 'dvd) => #true
;; (media? 'cd) => #true
;; (media? 'blueray) => #true
;; (media? 'floppy) => #true
;; (media? 'xxx) => #false

;;;; Funciton Definition
(define (media? value)
  (and (symbol? value)
       (or (symbol=? 'dvd value)
           (symbol=? 'cd value)
           (symbol=? 'blueray value)
           (symbol=? 'floppy value))))

;;;; Tests
(check-expect (media? 1) #false)
(check-expect (media? "dvd") #false)
(check-expect (media? 'dvd) #true)
(check-expect (media? 'cd) #true)
(check-expect (media? 'blueray) #true)
(check-expect (media? 'floppy) #true)
(check-expect (media? 'xxx) #false)


;;;; Signature:
;; peripheral? : Any -> Boolean
;;;; Purpose:
;; GIVEN: any Racket value
;; RETURNS: true of the input is kind of peripheral, false otherwise

;;; Examples
;; (peripheral? 1) => #false
;; (peripheral? "dvd") => #false
;; (peripheral? "keyboard") => #true
;; (peripheral? "mouse") => #true
;; (peripheral? "driving wheel") => #true
;; (peripheral? "headset") => #true
;; (peripheral? "controller") => #true

;;;; Function Definition
(define (peripheral? value)
  (and (string? value)
       (or (string=? "keyboard" value)
           (string=? "mouse" value)
           (string=? "driving wheel" value)
           (string=? "headset" value)
           (string=? "controller" value))))

;;; Tests
(check-expect (peripheral? 1) #false)
(check-expect (peripheral? "dvd") #false)
(check-expect (peripheral? "keyboard") #true)
(check-expect (peripheral? "mouse") #true)
(check-expect (peripheral? "driving wheel") #true)
(check-expect (peripheral? "headset") #true)
(check-expect (peripheral? "controller") #true)

So let’s replace XXX and YYY

;; media-or-peripheral: MediaOrPeripheral -> ???
#; (define (media-or-peripheral media-peripheral)
     (cond
       [(media? media-peripheral) ...]
       [(peripheral? media-peripheral) ...]))

Now our cond -clauses test each case for a MediaOrPeripheral. But we are not done!

Let’s focus on the first cond -clause first. If (media? media-peripheral) returns #true the cond will then continue its evaluation by going to the …​ in the first cond -clause. In this case we know with certainty that media-peripheral is value that is a Media. It cannot be a Peripheral because (media? media-peripheral) returned #true. Do we have a deconstructor template for Media? Yes it’s media-fn. So let’s call (depend) on that.

A similar argument can be made for the second cond -clause. Our final deconstructor template for MediaOrPeripheral then becomes

;; media-or-peripheral: MediaOrPeripheral -> ???
#; (define (media-or-peripheral media-peripheral)
     (cond
       [(media? media-peripheral) ... (media-fn media-peripheral) ...]
       [(peripheral? media-peripheral) ... (peripheral-fn media-peripheral) ...]))

Observe that the dependecies we had in our data definition are now reflected in our deconstructor templates.

Our data definition for MediaOrPeripheral depends on Media and Peripheral.

Our deconstructor template for MediaOrPeripheral depends on the deconstructor template for Media and on the deconstructor template for Peripheral

Let’s continue and answer the question from Lab 2 that asks to calculate the tax to be payed for each item.

;;;; Signature:
;; media-peripheral->tax: MediaOrPeripheral NonNegReal -> NonNegReal
;;;; Purpose
;; GIVEN: a media or peripheral and its price
;; RETURNS: the amount of tax to be payed

;;;; Examples
;; (media-peripheral->tax 'dvd 100) => 5
;; (media-peripheral->tax 'cd 100) => 3
;; (media-peripheral->tax 'blueray 100) => 7
;; (media-peripheral->tax 'floppy 100) => 2
;; (media-peripheral->tax "keyboard" 100) => 2.5
;; (media-peripheral->tax "mouse" 100) => 1.5
;; (media-peripheral->tax "driving wheel" 100) => 3.5
;; (media-peripheral->tax "headset" 100) => 5.2
;; (media-peripheral->tax "controller" 100) => 6

(define (media-peripheral->tax media-peripheral price)
  (cond
    [(media? media-peripheral) (media->tax media-peripheral price)]
    [(peripheral? media-peripheral) (peripheral->tax media-peripheral price)]))

;;;; Tests
(check-expect (media-peripheral->tax 'dvd 100)  5)
(check-expect (media-peripheral->tax 'cd 100)  3)
(check-expect (media-peripheral->tax 'blueray 100)  7)
(check-expect (media-peripheral->tax 'floppy 100)  2)
(check-expect (media-peripheral->tax "keyboard" 100)  2.5)
(check-expect (media-peripheral->tax "mouse" 100)  1.5)
(check-expect (media-peripheral->tax "driving wheel" 100)  3.5)
(check-expect (media-peripheral->tax "headset" 100)  5.2)
(check-expect (media-peripheral->tax "controller" 100)  6)

;;;; Signature:
;; media->tax: Media NonNegReal -> NonNegReal
;;;; Purpose:
;; GIVEN: a media item and its price
;; RETURNS: the amount to be payed in tax

;;;; Examples
;; (media->tax 'dvd 100) => 5
;; (media->tax 'cd 100) => 3
;; (media->tax 'blueray 100) => 7
;; (media->tax 'floppy 100) => 2


(define (media->tax media price)
  (cond
    [(symbol=? 'dvd media) (* price 0.05)]
    [(symbol=? 'cd media) (* price 0.03)]
    [(symbol=? 'blueray media) (* price 0.07)]
    [(symbol=? 'floppy media) (* price 0.02)]))

;;;; Tests
(check-expect (media->tax 'dvd 100)  5)
(check-expect (media->tax 'cd 100)  3)
(check-expect (media->tax 'blueray 100)  7)
(check-expect (media->tax 'floppy 100)  2)

;;;; Signature:
;; peripheral->tax: Peripheral NonNegReal -> NonNegReal
;;;; Purpose:
;; GIVEN: a peripheral item and its price
;; RETURNS: the amount to be payed in tax

;;;; Examples
;; (peripheral->tax "keyboard" 100) => 2.5
;; (peripheral->tax "mouse" 100) => 1.5
;; (peripheral->tax "driving wheel" 100) => 3.5
;; (peripheral->tax "headset" 100) => 5.2
;; (peripheral->tax "controller" 100) => 6


(define (peripheral->tax peripheral price)
  (cond
    [(string=? "keyboard" peripheral) (* price 0.025)]
    [(string=? "mouse" peripheral) (* price 0.015)]
    [(string=? "driving wheel" peripheral) (* price 0.035)]
    [(string=? "headset" peripheral) (* price 0.052)]
    [(string=? "controller" peripheral) (* price 0.06)]))

;;;; Tests
(check-expect (peripheral->tax "keyboard" 100)  2.5)
(check-expect (peripheral->tax "mouse" 100)  1.5)
(check-expect (peripheral->tax "driving wheel" 100)  3.5)
(check-expect (peripheral->tax "headset" 100)  5.2)
(check-expect (peripheral->tax "controller" 100)  6)

Again, observe how dependencies in our data definitions are reflected in our code this time. In fact the same dependencies that originated in our data definitions are reflected through the steps of the Design Recipe

  • in our deconstructor templates

  • in our code

This is one of our design rules

Design Rules
The structure of our code follows the structure of our data.
One task, one function

The functions media→tax and peripheral→tax are helper functions that assist the function media-peripheral→tax. Following the Design Recipe and our Design Rule, helper functions emerge naturally.

Structures

Structures in our Beginning Student Language (BSL) allow us to combine several, fixed, number of pieces of data together and create a new value. The BSL has special syntax to create structures (or structs for short).

Let’s look at an example. Think of your contacts that you keep on your phone. A contact is made up of a certain pieces of data

  1. The contact’s full name

  2. The contact’s telephone number

  3. The contact’s short name

We would like to capture these pieces of information as one value that stores inside of it 3 other values,

  1. One for the contact’s full name

  2. One for the contact’s telephone number

  3. One for the contact’s short name

As a first attempt we will map these pieces of information to data as follows.

  • For the contact’s full name we are going to use 1 long String [1]

  • For the contact’s telephone number we will use a NonNegInteger [1].

  • For the contact’s short name we will use a String.

(define-struct contact (full-name phone nick))

define-struct is followed by the name of the new structure we want to create. This can be any name we want. In our example we chose contact. Then we wrap in parenthesis the number of members that we would like this structure to have separated by spaces. In our example contact will have 3 members

  1. the first member is called full-name

  2. the second member is called phone

  3. the third member is called nick

When we define a new structure using define-struct DrRacket generates for free a set of functions that allow us to

  • create an instance of our structure

  • check if a value (any Racket value) is an instance of our structure

  • one function for each member of our structure that allows us to obtain the values inside an instance of a structure

So for our contact structure we have the following functions available to us

  • CONSTRUCTOR : make-contact used to create a new instance of contact. Takes as many inputs as there are structure members

  • PREDICATE : contact?, takes one input and returns true if the input is an instance (create using make-contact) and false otherwise

  • SELECTORS

    • contact-full-name, takes an instance of contact and returns the value stored under the member name full-name

    • contact-phone, takes an instance of contact and returns the value stored under the member name phone

    • contact-nick, takes an instance of contact and returns the value stored under the member name nick

The names for these auto-generated functions follow a pattern

  1. Constructor names are created by prepending the string make- to the structure name, e.g., make-contact

  2. Predicate names are created by appending the string ? to the structure name, e.g., contact?

  3. Selector names are created by appending the structure name, a - and the name of the member, e.g., contact-full-name, contact-phone, contact-nick

> (define mike (make-contact "Michael Tomas" 5556755656 "Mike"))    (1)
> mike
(make-contact "Michael Tomas" 5556755656 "Mike")    (2)
> (contact? mike)    (3)
#true
> (contact? "Michael Tomas")    (4)
#false
> (contact? 3)
#false
> (contact-full-name mike)    (5)
"Michael Tomas"
> (contact-phone mike)    (6)
5556755656
> (contact-nick mike)    (7)
"Mike"
> (contact-nick 3)    (8)
contact-nick: expects a contact, given 3
> (contact-full-name "Michale Tomas")    (9)
contact-full-name: expects a contact, given "Michale Tomas"
1 create a contact and bind it to the variable mike
2 DrRacket prints a structure as a string and it looks identical to calling the constructor for the structure
3 contact? is our predicate and detects if its input is an instance of contact or not
4 we can give any value as input to contact? and contact? will tell us if it is an instance of contact or not
5 the selector contact-full-name must be given a value that is an instance of contact and then it reaches inside the instance and returns the value stored under the member named full-name
6 the selector contact-phone must be given a value that is an instance of contact and then it reaches inside the instance and returns the value stored under the member named phone
7 the selector contact-nick must be given a value that is an instance of contact and then it reaches inside the instance and returns the value stored under the member named nick
8 if we provide a value that is not an instance of contact to a selector (here contact-nick) we get an error!
9 if we provide a value that is not an instance of contact to a selector (here contact-full-name) we get an error!
The value (make-contact "Michael Tomas" 5556755656 "Mike") is not the same as the three values "Michael Tomas" 5556755656 "Mike". The instance of the structure contact is a new value that we consider as one value that we can pass around. The difference is that we can now open the instance and peek at its members stored inside of it.
Note

Recall our definition for a value? Here it is again:

A value is anything in our program that can be

  1. Given as an input to a function.

  2. Returned as a result of a function call.

  3. Stored (this criterion will make more sense as we progress in the course and introduce more language features)

See the last criterion? By stored we mean we can place it inside a structure’s slot, in other words, store it as part of a structure.

Data Definitions for Structs

Let’s complete the Data Definition for contact

;;;; Data Definition
(define-struct contact (full-name phone nick))
;; Constructor Template:
;; A Contact is a (make-contact String NonNegInteger String)
;; INTERP: represents a contact with their full name, phone number and short name (nickname)

;; Deconstructor Template:
;; contact-fn: Contact -> ???
#; (define (contact-fn contact)
     ... (contact-full-name contact) ...
     ... (contact-phone contact) ...
     ... (contact-nick contact) ... )
Constructor Templates

For structures our data definition now has code, (define-struct contact (full-name phone nick)). Then we need the name of our Data definition Contact and our Constructor Template. A Constructor Template uses the structure’s constructor function to define the appropriate data that we expect to store for each member. So a Contact is an instance of the structure contact that

  1. Uses a String for the first member called full-name

  2. Uses a NonNegInteger for the second member called phone

  3. Uses a String for the third member called nick

DrRacket will allow any value to be placed in each of the members. Our data definition however restricts the kinds of values we can store in each member of contact. We performed similar restrictions to previous data definitions we saw last week.

Deconstructor Templates

Our deconstructor template that is part of our data definition needs to show how we can obtain an instance of our data definition and deconstruct it to its subcomponents. In the case of a structure, we need to select each member of the structure.

Let’s write a function that will allow us to update the nick name of a contact. Our function should take a current contact and a new nick name and return a contact with their nickname updated.

;;;; Signature:
;; contact-update-nick: Contact String -> Contact
;;;; Purpose
;; GIVEN: a contact and a new nick name for the contact
;; RETURNS: the original contact with the nickname updated.

;;;; Examples
;; (contact-update-nick (make-contact "A B" 1111 "Little John") "Johny") =>
;;   (make-contact "A B" 1111 "Johny")

;;;; Function Definition
(define (contact-update-nick contact new-nick)
  (make-contact (contact-full-name contact)
                (contact-phone contact)
                new-nick))
;;;; Tests
(check-expect
 (contact-update-nick (make-contact "A B" 1111 "Little John") "Johny")
 (make-contact "A B" 1111 "Johny"))

Structs within Structs

Let’s review our design for contacts.

The full name that we use for a contact is currently mapped to a String. We know that we typically have a first name and a surname for a person. So a Full name for a person consists of a first and a surname. It is more appropriate to then map a full name to a structure that has 2 members, a first and a surname.

Also, the phone number (at least here in the US) has a pattern or a grouping of the numbers 555-888-9999, that is 3 digits, 3 digits and 4 digits. It is more appropriate to map this to a structure as well. So let’s do that.

(define-struct full-name (first surname))
;; Constructor Template:
;; A FullName is a (make-full-name String String)
;; INTERP: represents a person full name as first and surname

;; Examples
;; (make-full-name "Joe" "Silver")

;; Deconstructor Template:
;; full-name-fn: FullName -> ???
#; (define (full-name-fn full-name)
     ... (full-name-first full-name) ...
     ... (full-name-surname full-name) ...)


;; A 3NNInt is a NonNegInteger that has 3 digits, e.g., 000, 767
;; A 4NNInt is a NonNegInteger that has 4 digits, e.g., 0000, 3767

(define-struct phone-number (area office-code subscriber))
;; Constructor Template:
;; A PhoneNumber is a (make-phone-Number 3NNInt 3NNInt 4NNInt)
;; INTERP: represents a US Phone number with area code, office code and
;;         subscriber code

;; Examples
;; (make-phone-number 555 626 8187)

;; Deconstructor Template
;; phone-number-fn: PhoneNumber -> ???
#; (define (phone-number-fn phone-num)
     ... (phone-number-area phone-num) ...
     ... (phone-number-office-code phone-num) ...
     ... (phone-number-subscriber phone-num) ...)


(define-struct contact (name phone nick))
;; Constructor Template:
;; A (make-contact FullName PhoneNumber String)
;; INTERP: represents a contact with their full name, phone number and short name (nickname)


;; Examples
;; (make-contact (make-full-name "Mary" "Andrews")
;;               (make-phone-number 666 777 8787)
;;               "Mary")
;;
;;

;; Deconstructor Template:
;; contact-fn: Contact -> ???
#; (define (contact-fn contact)
     ... (full-name-fn (contact-name contact)) ...
     ... (phone-number-fn (contact-phone contact)) ...
     ... (contact-nick contact) ... )

The data definitions for FullName and PhoneNumber are what we would expect.

Writing examples for your data definitions is crucial now that we can have deep and complex nesting of structures and later mixed itemizations and structures.

Observer the data definition of Contact. Contact now has a FullName, a PhoneNumber and a String as its members. We have a structure within a structure. This is another form of a dependency.

Also observe that the deconstructor template for Contact now depends on the deconstructor template for FullName and the deconstructor template for PhoneNumber.

Recall our Design Rules!

Duplicate contacts

Now that we have done our data design let’s try to design a function that will take as input 2 contacts and return true if the contacts are duplicates. We will consider a contact to be a duplicate if they have the same name and number. We will ignore the nick name.

;;;; Signature:
;; contact-duplicate?: Contact Contact -> Boolean
;;;; Purpose
;; GIVEN: two contacts
;; RETURNS: true if the contacts are duplicates, false otherwise.
;;          we ignore nick names when checking for duplicates.

(define MARY (make-full-name "Mary" "Gold"))
(define PHONE1 (make-phone-number 555 222 3434))
(define JOE (make-full-name "Joe" "Stiles"))
(define PHONE2 (make-phone-number 666 777 8787))
(define MARY-CONTACT (make-contact MARY PHONE1 "Mary"))
(define JOE-CONTACT (make-contact JOE PHONE2 "Joe"))
(define JOEY-CONTACT (make-contact JOE PHONE2 "Joey"))

;;;; Examples
;; (contact-duplicates? JOE-CONTACT JOEY-CONTACT) => #true
;; (contact-duplicates? MARY-CONTACT MARY-CONTACT) => #true
;; (contact-duplicates? MARY-CONTACT JOE-CONTACT) => #false


;;;; Function Definition
(define (contact-duplicates? contact1 contact2)
     (and (full-name-duplicates? (contact-name contact1)
                                (contact-name contact2))
          (phone-number-duplicates? (contact-phone contact1)
                                   (contact-phone contact2))))


;;;; Tests
(check-expect (contact-duplicates? JOE-CONTACT JOEY-CONTACT) #true)
(check-expect (contact-duplicates? MARY-CONTACT MARY-CONTACT) #true)
(check-expect (contact-duplicates? MARY-CONTACT JOE-CONTACT) #false)

;;;; Data Definition: none

;;;; Signature:
;; full-name-duplicates?: FullName FullName -> Boolean
;;;; Purpose
;; GIVEN: two contact's full names
;; RETURNS: true if the names are identical, false otherwise

;;;; Examples
;; (full-name-duplicates? JOE JOE) => #true
;; (full-name-duplicates? JOE MARY) => #false

;;;; Function Definition
(define (full-name-duplicates? name1 name2)
  (and (string=? (full-name-first name1)
                 (full-name-first name2))
       (string=? (full-name-surname name1)
                 (full-name-surname name2))))

;;;; Tests
(check-expect (full-name-duplicates? JOE JOE) #true)
(check-expect (full-name-duplicates? JOE MARY) #false)


;;;; Data Definition: none

;;;; Signature:
;; phone-number-duplicates?: PhoneNumber PhoneNumber -> Boolean
;;;; Purpose
;; GIVEN: two contact's phone numbers
;; RETURNS: true if the numbers are identical, false otherwise

;;;; Examples
;; (phone-number-duplicates? PHONE1 PHONE1) => #true
;; (phone-number-duplicates? PHONE1 PHONE2) => #false

;;;; Function Definition
(define (phone-number-duplicates? phone-num1 phone-num2)
  (and   (= (phone-number-area phone-num1)
            (phone-number-area phone-num2))
         (= (phone-number-office-code phone-num1)
            (phone-number-office-code phone-num2))
         (= (phone-number-subscriber phone-num1)
            (phone-number-subscriber phone-num2))))


;;;; Tests
(check-expect (phone-number-duplicates? PHONE1 PHONE1) #true)
(check-expect (phone-number-duplicates? PHONE1 PHONE2) #false)

Observe, again, that our function to check for duplicates contact-duplicates? depends on full-name-duplicates? and phone-number-duplicates?. We use the Design Recipe for all functions involved and the structure or our code follows the structure of our data.

Mixing it up, itemizations with structure and structure with itemizations

Keeping with our example, we know are informed that what we have designed to capture a person’s full name needs to be extended. Full names can sometimes have a middle name. So a full name is one of

  • a first and surname

  • a first a middle and a surname

What we have here is an itemization (a full name is one of …​) but each element of the itemization is going to be a structure.

;;;; Data Definition
(define-struct contact (name phone nick))
;; Constructor Template:
;; A (make-contact FullName PhoneNumber String)
;; INTERP: represents a contact with their full name, phone number and short name (nickname)

;; Examples
;; (make-contact (make-full-name "Mary" "Andrews)
;;               (make-phone-number 666 777 8787)
;;               "Mary")
;;
;;

;; Deconstructor Template:
;; contact-fn: Contact -> ???
#; (define (contact-fn contact)
     ... (name-fn (contact-name contact)) ...
     ... (phone-number-fn (contact-phone contact)) ...
     ... (contact-nick contact) ... )


;; A Name is one of
;; - FullName
;; - FullMiddleName
;; INTERP: represents a person's name with or without a middle name

(define-struct full-name (first surname))
;; Constructor Template:
;; A FullName is a (make-full-name String String)
;; INTERP: represents a person full name as first and surname

;; Examples
;; (make-full-name "Joe" "Silver")

;; Deconstructor Template:
;; full-name-fn: FullName -> ???
#; (define (full-name-fn full-name)
     ... (full-name-first full-name) ...
     ... (full-name-surname full-name) ...)


(define-struct full-middle-name (first middle surname))
;; Constructor Template:
;; A FullName is a (make-full-middle-name String String String)
;; INTERP: represents a person full name as first, middle and surname

;; Examples
;; (make-full-middle-name "Joe" "Chris" "Silver")

;; Deconstructor Template:
;; full-middle-name-fn: FullMiddleName -> ???
#; (define (full-middle-name-fn full-middle-name)
     ... (full-middle-name-first full-middle-name) ...
     ... (full-middle-name-middle full-middle-name) ...
     ... (full-middle-name-surname full-middle-name) ...)

;; name-fn: Name -> ???
#; (define (name-fn name)
     (cond
       [(full-name? name) ... (full-name-fn name) ...]
       [(full-middle-name? name) ... (full-middle-name-fn name) ...]))


;; A 3NNInt is a NonNegInteger that has 3 digits, e.g., 000, 767
;; A 4NNInt is a NonNegInteger that has 4 digits, e.g., 0000, 3767

(define-struct phone-number (area office-code subscriber))
;; Constructor Template:
;; A PhoneNumber is a (make-phone-Number 3NNInt 3NNInt 4NNInt)
;; INTERP: represents a US Phone number with area code, office code and
;;         subscriber code

;; Examples
;; (make-phone-number 555 626 8187)

;; Deconstructor Template
;; phone-number-fn: PhoneNumber -> ???
#; (define (phone-number-fn phone-num)
     ... (phone-number-area phone-num) ...
     ... (phone-number-office-code phone-num) ...
     ... (phone-number-subscriber phone-num) ...)


;;;; Signature:
;; contact-duplicate?: Contact Contact -> Boolean
;;;; Purpose
;; GIVEN: two contacts
;; RETURNS: true if the contacts are duplicates, false otherwise.
;;          we ignore nick names when checking for duplicates.

(define MARY (make-full-name "Mary" "Gold"))
(define MARY-MIDDLE (make-full-middle-name "Mary" "Louise" "Gold"))

(define PHONE1 (make-phone-number 555 222 3434))
(define JOE (make-full-name "Joe" "Stiles"))
(define JOE-MIDDLE (make-full-middle-name "Joe" "Mike" "Stiles"))

(define PHONE2 (make-phone-number 666 777 8787))
(define MARY-CONTACT (make-contact MARY PHONE1 "Mary"))
(define JOE-CONTACT (make-contact JOE PHONE2 "Joe"))
(define JOEY-CONTACT (make-contact JOE PHONE2 "Joey"))

;;;; Examples
;; (contact-duplicates? JOE-CONTACT JOEY-CONTACT) => #true
;; (contact-duplicates? MARY-CONTACT MARY-CONTACT) => #true
;; (contact-duplicates? MARY-CONTACT JOE-CONTACT) => #false


;;;; Function Definition
(define (contact-duplicates? contact1 contact2)
     (and (name-duplicates? (contact-name contact1)
                            (contact-name contact2))
          (phone-number-duplicates? (contact-phone contact1)
                                    (contact-phone contact2))))


;;;; Tests
(check-expect (contact-duplicates? JOE-CONTACT JOEY-CONTACT) #true)
(check-expect (contact-duplicates? MARY-CONTACT MARY-CONTACT) #true)
(check-expect (contact-duplicates? MARY-CONTACT JOE-CONTACT) #false)


;;;; Data Definition: none

;;;; Signature:
;; name-duplicates?: Name Name -> Boolean
;;;; Purpose
;; GIVEN: two contact's  names
;; RETURNS: true if the names are identical, false otherwise

;;;; Examples
;; (name-duplicates? JOE JOE) => #true
;; (name-duplicates? JOE MARY) => #false

;;;; Function Definition
(define (name-duplicates? name1 name2)
    (cond
       [(and (full-name? name1)
             (full-name? name2))
        (full-name-duplicates? name1 name2)]
       [(and (full-middle-name? name1)
             (full-middle-name? name2))
        (full-middle-name-duplicates? name1 name2)]
       [else #false]))

;;;; Tests
(check-expect (name-duplicates? JOE JOE) #true)
(check-expect (name-duplicates? JOE-MIDDLE JOE-MIDDLE) #true)
(check-expect (name-duplicates? JOE MARY) #false)
(check-expect (name-duplicates? JOE MARY-MIDDLE) #false)


;;;; Data Definition: none

;;;; Signature:
;; full-middle-name-duplicates?: FullMiddleName FullMiddleName -> Boolean
;;;; Purpose
;; GIVEN: two contact's names that includes a middle name
;; RETURNS: true if the names are identical, false otherwise

;;;; Examples
;; (full-middle-name-duplicates? JOE JOE) => #true
;; (full-middle-name-duplicates? JOE MARY) => #false

;;;; Function Definition
(define (full-middle-name-duplicates? name1 name2)
  (and (string=? (full-middle-name-first name1)
                 (full-middle-name-first name2))
       (string=? (full-middle-name-middle name1)
                 (full-middle-name-middle name2))
       (string=? (full-middle-name-surname name1)
                 (full-middle-name-surname name2))))

;;;; Tests
(check-expect (full-middle-name-duplicates? JOE-MIDDLE JOE-MIDDLE) #true)
(check-expect (full-middle-name-duplicates? JOE-MIDDLE MARY-MIDDLE) #false)



;;;; Data Definition: none

;;;; Signature:
;; full-name-duplicates?: FullName FullName -> Boolean
;;;; Purpose
;; GIVEN: two contact's full names
;; RETURNS: true if the names are identical, false otherwise

;;;; Examples
;; (full-name-duplicates? JOE JOE) => #true
;; (full-name-duplicates? JOE MARY) => #false

;;;; Function Definition
(define (full-name-duplicates? name1 name2)
  (and (string=? (full-name-first name1)
                 (full-name-first name2))
       (string=? (full-name-surname name1)
                 (full-name-surname name2))))

;;;; Tests
(check-expect (full-name-duplicates? JOE JOE) #true)
(check-expect (full-name-duplicates? JOE MARY) #false)


;;;; Data Definition: none

;;;; Signature:
;; phone-number-duplicates?: PhoneNumber PhoneNumber -> Boolean
;;;; Purpose
;; GIVEN: two contact's phone numbers
;; RETURNS: true if the numbers are identical, false otherwise

;;;; Examples
;; (phone-number-duplicates? PHONE1 PHONE1) => #true
;; (phone-number-duplicates? PHONE1 PHONE2) => #false

;;;; Function Definition
(define (phone-number-duplicates? phone-num1 phone-num2)
  (and   (= (phone-number-area phone-num1)
            (phone-number-area phone-num2))
         (= (phone-number-office-code phone-num1)
            (phone-number-office-code phone-num2))
         (= (phone-number-subscriber phone-num1)
            (phone-number-subscriber phone-num2))))


;;;; Tests
(check-expect (phone-number-duplicates? PHONE1 PHONE1) #true)
(check-expect (phone-number-duplicates? PHONE1 PHONE2) #false)

1. We will review this decision later.