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
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
-
The contact’s full name
-
The contact’s telephone number
-
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,
-
One for the contact’s full name
-
One for the contact’s telephone number
-
One for the contact’s short name
As a first attempt we will map these pieces of information to data as follows.
(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
-
the first member is called
full-name
-
the second member is called
phone
-
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 ofcontact
. Takes as many inputs as there are structure members -
PREDICATE :
contact?
, takes one input and returnstrue
if the input is an instance (create usingmake-contact
) andfalse
otherwise -
SELECTORS
-
contact-full-name
, takes an instance ofcontact
and returns the value stored under the member namefull-name
-
contact-phone
, takes an instance ofcontact
and returns the value stored under the member namephone
-
contact-nick
, takes an instance ofcontact
and returns the value stored under the member namenick
-
The names for these auto-generated functions follow a pattern
-
Constructor names are created by prepending the string
make-
to the structure name, e.g.,make-contact
-
Predicate names are created by appending the string
?
to the structure name, e.g.,contact?
-
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.
|
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
-
Uses a
String
for the first member calledfull-name
-
Uses a
NonNegInteger
for the second member calledphone
-
Uses a
String
for the third member callednick
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)