Design Recipe (Recap)
-
Data Design
-
Signature and Purpose
-
Examples and Tests
-
Function Definition
-
Review
Kinds of Data
We have seen different data definitions thus far, e.g.,
-
Scalars -
Number
,String
,Boolean
etc. -
Non-scalar (Complex Data)
-
Itemizations
-
Structures
-
Mixed
-
Itemizations with Structures
-
Structures that contain Itemizations
-
-
Data Definition Step and Kinds of Data
Unless you are using one of the predefined Data Definitions found on the class web page you must define your data definitions
Scalar
We typically reuse the existing Scalar Data Definitions or define our own in order to
-
provide a more meaningful name for our Data Definition that better maps to the information in the problem statement
-
Good Examples
-
A FileName is a String
-
An Amount is a NonNegInteger
-
-
Bad Examples
-
A Boolean1 is a Boolean
-
A Result-Boolean is a Boolean
-
-
-
impose a restriction to an existing Scalar Data Definition that we require to accurately capture information in our problem statement
Itemizations
An Itemization Data Definition consists of a fixed list of mutually exclusive variants.
For an Itemization Data Definition we must provide a deconstructor template. The Deconstructor Template’s goal is to show, using partial code, how we could take an instance of our Itemized Data Definition and deconstruct it, i.e., detect which variant we are given.
Structures
A Structure Data Definition describes data that are represented using define-struct
. A structure consists
of a fixed number of sub-components.
For a Structure Data Definition we must provide
-
A Constructor Template that describes the kinds of data that we can use for the structure’s sub-parts
-
A Deconstructor Template that shows, using in partial code, how we could take an instance of our Structure Data Definition and deconstruct it, i.e., access each of the structure’s sub-parts.
Questions to ask while writing your Deconstructor Template
-
Is the value a Scalar?
-
Are there more sup-parts/variants that we should consider?
-
For each sub-part/variant
-
is the value a scalar? If so we are done.
-
is the value a non-scalar (i.e., complex)? Then we need to keep deconstructing our data by either
-
if there is already a deconstructor template for this kind of data, call it, else
-
create a new deconstructor template for that kind of data and call it
-
-
Recursive Data Definitions
Recursive Data definitions are data definitions that refer to themselves.
(define-struct succ (val))
;; Constructor Template:
;; A Succ is a (make-succ Peano)
;; INTERP: represents a successor of val, the next number after val, or val + 1
;; A Peano is one of
;; - 'zero
;; - Succ
;; INTERP: represents a Peano number
Even though the Peano
data definition uses a structure and an itemization that we have seen before
there is something new here.
The data definition of Peano
depends on Succ
and the data definition
of Succ
depends on Peano
.
Let’s re-write the data definition but this time lets inline the data definition of Succ
with Peano
(define-struct succ (val))
;; A Peano is one of
;; - 'zero
;;
;; - (make-succ Peano)
;; A Succ is a (make-succ Peano)
;; INTERP: represents a successor of val, the next number after val; (val + 1)
;;
;; INTERP: represents a Peano number
In our re-write of Peano
we see that the definition of Peano
depends on Peano
.
This is an example of a recursive data definition, the name we are defining Peano
depends
on itself. There is a self loop in the data definition!
We will be using our second data definition for Peano numbers.
Let’s write some examples of Peano
numbers to help us better understand Peano numbers
;;;; Examples
(define PZERO 'zero) ;; 0 in Peano
(define PONE (make-succ 'zero)) ;; 1 in Peano
(define PTWO (make-succ (make-succ 'zero))) ;; 2 in Peano
Let’s try and write the deconstructor template for Peano
;;;; Signature
;; is-zero? : Any -> Boolean
;;;; Purpose
;; GIVEN: any Racket value
;; RETURNS: true if the value is the symbol 'zero, false otherwise
;;;; Examples
;; (is-zero? 1) => #false
;; (is-zero? 'zero) => #true
;;;; Function Definition
(define (is-zero? val)
(and (symbol? val)
(symbol=? val 'zero)))
;;;; Tests
(check-expect (is-zero? 1) #false)
(check-expect (is-zero? 'zero) #true)
;; Deconstructor Template:
;; peano-fn: Peano -> ???
#; (define (peano-fn peano)
(cond
[(is-zero? peano) ...]
[(succ? peano) ... (peano-fn (succ-val peano)) ... ])) (1)
1 | In the case where peano is an instance of Succ we must decompose Succ to its sub-components. Succ has one sub-component val .
val contains a value that is a Peano itself and we know that Peano is not a Scalar. Do we have a deconstructor template to further
deconstruct a Peano value? Yes, the deconstructor template that we are currently writing peano-fn , so call that deconstructor template! |
Recall our slogan …
Our Peano
Data Definition has a loop, and, our Deconstructor template
for Peano
also has a loop in exactly the same location, 2nd variant of the itemization!
Great, reading Peano
numbers is rather cumbersome so lets write a function that given a Peano
number will return the corresponding NonNegativeInteger
.
;;;; Signature
;; peano->integer : Peano -> NonNegativeInteger
;;;; Purpose
;; GIVEN: a peano number
;; RETURNS: the corresponding integer value
;;;; Examples
;; (peano->integer PZERO) => 0
;; (peano->integer PONE) => 1
;; (peano->integer PTWO) => 2
;;;; Function Definition
(define (peano->integer peano)
(cond
[(is-zero? peano) 0] (1)
[(succ? peano) (+ 1 (peano->integer (succ-val peano)))])) (2)
;;;; Tests
(check-expect (peano->integer PZERO) 0)
(check-expect (peano->integer PONE) 1)
(check-expect (peano->integer PTWO) 2)
1 | When we are given 'zero then we immediately return 0 , that was easy |
2 | When we are given a Succ then according to our deconstructor
template we need to get the value inside the succ structure and
call the same function again. What will this recursive call to
peano→integer return? It will return the non-negative integer of
the value inside succ . We want the number for the whole succ
structure which is whatever our recursive call returns + 1 . So lets
add 1 to the result of the recursive call. |
Recall our slogan …
The function peano→integer
has the same loop as the Peano
Data Definition!
Lists
Structures allow us to define and create data that contain a fixed number of components. We know that we have situations where the information that we need to capture and manipulate allow for an arbitrary number of components, for example, consider the scenario were we would like to capture the names of all students in a class, or, capture the number grades for an assignment given to our class, or, in the case of on online store, the number of items that a customer has selected to purchase.
These kinds of data can be of arbitrary length and for these kinds of information we can map them to lists.
Cons cells, the universal struct
Racket is a descendant of Scheme and Lisp. Lists in these languages play a central role and for that reason there is a special structure with special
-
constructors
-
selectors
-
and predicates
For creating, extracting values and checking for lists.
The empty list
Our BSL language has a special value to denote the empty list. The value is empty
.
Along with the value empty
there is also a predicate that we can use that detects if a Racket value is the empty list or not.
The predicate is called empty?
.
Non-empty list
Our BSL language allows us to add an element to a list by using the constructor called cons
and passing in
-
the element to add to the list
-
an existing list to which we are adding our element
Along with cons
we also get a predicate that allows us to check if a Racket value is a non-empty list or not. The predicate’s
name is cons?
Let’s make some examples
> empty
'() (1)
> (empty? empty)
#true
> (empty? 1)
#false
1 | Racket displays an empty list as '() in your interactions window. |
> (cons 1 empty)
(cons 1 '())
> (cons 2 (cons 1 empty)) (1)
(cons 2 (cons 1 '()))
> (cons "a" (cons 1 (cons 'a empty))) (2)
(cons "a" (cons 1 (cons 'a '())))
> (cons? "a")
#false
> (cons? empty)
#false
> (cons? (cons 1 empty))
#true
1 | we can create an arbitrary size list by adding elements using cons |
2 | cons can add any kind of data to your lists. We will use our data definitions to
restrict the kids of data in our lists to be the ones that we want. |
BSL also provides selectors for non-empty list.
-
first
takes a non-empty list and returns the first element of the list e.g.,> (first (cons 1 (cons 2 empty))) 1 > (define SAMPLE-LIST (cons 10 (cons 20 (cons 30 empty)))) > (first SAMPLE-LIST) 10 > (first empty) first: expects a non-empty list; given: '() > (first 2) first: expects a non-empty list; given: 2
-
rest
takes a non-empty list and returns the tail of the list; takes off the first element and returns the remaining elements as a list> (rest (cons 1 (cons 2 empty))) (cons 2 '()) > (rest SAMPLE-LIST) (cons 20 (cons 30 '())) > (rest empty) rest: expects a non-empty list; given: '() > (rest 1) rest: expects a non-empty list; given: 1
Now that we have know the values and functions in our BSL that help us built and use lists let’s continue with our example and how to use our Design Recipe with lists.
List of Scalars
So let’s start with a simple example
We would like to capture the number grades for an assignment for a class.
Let’s start with capturing homework grades
;;;; Data Definitions
;; A Grade is a NonNegativeInteger
;; WHERE 0 <= Grade <= 100 (1)
;; INTERP: represents a homework grade
1 | We can use a WHERE clause with our data definitions to define restrictions or conditions
that we would like to impose on our data |
A Grade
is a scalar, a non-negative integer within the range [0,100].
Let’s define a list of Grade
s (LoG)
;; A ListOfGrades (LoG) is one of
;; - empty
;; - (cons Grade LoG)
;; INTERP: represents a list of homework grades
;;;; Examples
(define MT-GRADES empty)
(define ONE-GRADE (cons 70 empty))
(define CLASS-GRADE (cons 90
(cons 80
(cons 70 empty))))
A list of grades LoG
is either
-
the empty list
empty
, or, -
a non empty list (because of the
cons
) that contains aGrade
attached to another list of grades (that might be empty or not).
The second variant of the itemization is recursive and allows us to create an arbitrary sized list of grades!
Observe that there is a self loop in our data definition of our LoG
.
Let’s try our deconstructor template
;; Deconstructor Template
;; log-fn: LoG -> ???
#; (define (log-fn log)
(cond
[(empty? log) ...] (1)
[(cons? log) ... (first log) ... (2)
... (log-fn (rest log)) ...])) (3)
1 | when we are given an empty list of grades, then there is really nothing to deconstruct further |
2 | when we are given a non-empty list, then we can get to the first element of the list. In this case the first element
is a Grade which is a Scalar, so there is nothing more to deconstruct. But we are not done! We can also get to the
tail of the list. |
3 | The tail of the list we can access using rest . rest will return back an LoG . LoG is a non-scalar data and we can
deconstruct it further. Do we have a deconstructor template that deals with LoG ? Yes, the one we are currently writing. So we call
our deconstructor template again only this time with the tail of our current list. |
Recall our slogan …
Great. Let’s now calculate the average for out list of grades. To do so we need to know
-
the sum of all the grades
-
the total number of grades in our list (the list’s size)
Once we have these two pieces of data we then simply divide the sum of all grades by the size of our list.
Size of LoG
;;;; Signature:
;; log-size: LoG -> NonNegInteger
;;;; Purpose:
;; GIVEN: a list of grades
;; RETURNS: the size of the list
;;;; Examples:
;; (log-size empty) => 0
;; (log-size (cons 1 empty)) => 1
;; (log-size (cons 1 (cons 1 (cons 1 (cons 1 empty))))) => 4
;;;; Function Definition:
(define (log-size log)
(cond
[(empty? log) 0] (1)
[(cons? log) (+ 1 (2)
(log-size (rest log)))])) (3)
;;;; Tests:
(check-expect (log-size empty) 0)
(check-expect (log-size (cons 1 empty)) 1)
(check-expect (log-size (cons 1 (cons 1 (cons 1 (cons 1 empty))))) 4)
1 | if the list is empty then the total number of elements in the list is 0 |
2 | if the list is not empty, then from our deconstructor template we know that we have an element and then the rest of the list. Then we can add 1 to the size of the rest of the list |
3 | recursively calling our function log-size on the result of (rest log) gives us the size of the rest of the list. |
Recall our slogan …
Sum of LoG
;;;; Signature:
;; log-sum: LoG -> NonNegInteger
;;;; Purpose:
;; GIVEN: a list of grades
;; RETURNS: the sum of all the grades in the list
;;;; Example:
;; (log-sum empty) => 0
;; (log-sum (cons 1 empty)) => 1
;; (log-sum (cons 30 (cons 20 (cons 20 empty)))) => 70
;;;; Function Definition:
(define (log-sum log)
(cond
[(empty? log) 0] (1)
[(cons? log) (+ (first log) (2)
(log-sum (rest log)))])) (3)
;;;; Tests:
(check-expect (log-sum empty) 0)
(check-expect (log-sum (cons 1 empty)) 1)
(check-expect (log-sum (cons 30 (cons 20 (cons 20 empty)))) 70)
1 | if we are given the empty list then the sum of all grades is 0 since there are not grades |
2 | if we are given the non-empty list, then from our deconstructor template, we want to add the first grade in the list to the sum of the rest of the grades in the list |
3 | we obtain the sum of the rest of the grades in the list by recursively calling log-sum on (rest log) |
Recall our slogan …
Average of LoG
;;;; Signature:
;; log-average: LoG -> NonNegativeReal
;;;; Purpose:
;; GIVEN: a list of grades
;; RETURNS: the average grade
;;;; Examples:
;; (log-average empty) => 0
;; (log-average (cons 50 empty)) => 50
;; (log-average (cons 50 (cons 70 (cons 90 empty)))) => 70
;;;; Function Definition:
(define (log-average log)
(cond
[(= 0 (log-sum log)) 0]
[else (/ (log-sum log)
(log-size log))]))
;;;; Tests:
(check-expect (log-average empty) 0)
(check-expect (log-average (cons 50 empty)) 50)
(check-expect (log-average (cons 50 (cons 70 (cons 90 empty)))) 70)
List of Itemizations
Lists can hold any kind of data including itemizations. So let’s extend our LoG
example. The teacher would now like to have a grade be either a non-negative integer for the homeworks that got
submitted and graded but also have a special marker that indicates that the homework was not submitted. Here is our new data definition for grade that accommodates for non-submissions.
MaybeGrade
and ListOfMaybeGrade
(LoMG
);;;; Data Definitions
;; A MaybeGrade is one of
;; - 'none
;; - Grade
;; INTERP: represents a possible grade for a homework
;; Deconstructor Template:
;; maybe-grade-fn: MaybeGrade -> ???
#; (define (maybe-grade-fn maybe-grade)
(cond
[(none? maybe-grade) ...]
[(number? maybe-grade) ... ]))
;;;; Signature:
;; none?: Any -> Boolean
;;;; Purpose:
;; GIVEN: any Racket value
;; RETURNS: true if the value is 'none, false otherwise
;;;; Examples:
;; (none? 1) => #false
;; (none? 'none) => #true
;;;; Function Definition:
(define (none? value)
(and (symbol? value)
(symbol=? value 'none)))
;;;; Tests:
(check-expect (none? 1) #false)
(check-expect (none? 'none) #true)
;; A Grade is a NonNegativeInteger g
;; WHERE 0 <= g <= 100
;; INTERP: represents a homework grade
;; A ListOfMaybeGrades (LoMG) is one of
;; - empty
;; - (cons MaybeGrade LoMG) (1)
;; INTERP: repersents a list of homework grades
;;;; Examples
(define MT-GRADES empty)
(define ONE-GRADE (cons 70 empty))
(define ONE-MGRADE (cons 'none empty))
(define CLASS-GRADES (cons 90
(cons 80
(cons 70 empty))))
(define CLASS-MGRADES (cons 90
(cons 'none
(cons 70 empty))))
;; Deconstructor Template
;; lomg-fn: LoMG -> ???
#; (define (lomg-fn lomg)
(cond
[(empty? lomg) ...]
[(cons? lomg) ... (maybe-grade (first lomg)) ... (2)
... (lomg-fn (rest lomg)) ...]))
1 | Our LoMG data definition has the self loop and a dependency on MaybeGrade |
2 | Out deconstructor template has a call to itself that matches the self loop in the data definition for LoMG and a call to the
deconstructor template maybe-grade-fn matching the dependency to MaybeGrade in the data definition of LoMG . |
Recall our slogan …
Size of LoMG
We would like the size of a LoMG
to only count actual grades.
;;;; Signature:
;; lomg-size: LoMG -> NonNegInteger
;;;; Purpose:
;; GIVEN: a list of maybe grades
;; RETURNS: the number of actual grades in the list
;;;; Examples:
;; (lomg-size MT-GRADES) => 0
;; (lomg-size ONE-GRADE) => 1
;; (lomg-size ONE-MGRADE) => 0
;; (lomg-size CLASS-GRADES) => 3
;; (lomg-size CLASS-MGRADES) => 2
;;;; Function Definition:
(define (lomg-size lomg)
(cond
[(empty? lomg) 0]
[(cons? lomg) (+ (number-of-grades (first lomg)) (1)
(lomg-size (rest lomg)))]))
;;;; Tests:
(check-expect (lomg-size empty) 0)
(check-expect (lomg-size (cons 1 empty)) 1)
(check-expect (lomg-size (cons 1 (cons 1 (cons 1 (cons 1 empty))))) 4)
;; A 0Or1 is one of
;; - 0
;; - 1
;;;; Signature:
;; number-of-grades: MaybeGrade -> 0Or1
;;;; Purpose:
;; GIVEN: a possible grade
;; RETURNS: 0 if there is no grade, 1 if there is a grade
;;; Examples:
;; (number-of-grades 'none) => 0
;; (number-of-grades 80) => 1
;;;; Function Definition:
(define (number-of-grades maybe-grade)
(cond
[(none? maybe-grade) 0]
[(number? maybe-grade) 1]))
;;;; Tests:
(check-expect (number-of-grades 'none) 0)
(check-expect (number-of-grades 80) 1)
1 | following our deconstructor template, our function lomg-size depends on a helper function
to deal with each possible grade in our LoGM and tell us if there is something to count for that element
or not |
Recall our slogan …
Sum of LoMG
We would like the sum of an LoGM
to only consider grades.
;;;; Signature:
;; lomg-sum: LoMG -> NonNegInteger
;;;; Purpose:
;; GIVEN: a list of maybe grades
;; RETURNS: the sum of all the grades in the list
;;;; Example:
;; (lomg-sum MT-GRADES) => 0
;; (lomg-sum ONE-GRADE) => 70
;; (lomg-sum ONE-MGRADE) => 0
;; (lomg-sum CLASS-GRADES) => 240
;; (lomg-sum CLASS-MGRADES) => 160
;;;; Function Definition:
(define (lomg-sum lomg)
(cond
[(empty? lomg) 0]
[(cons? lomg) (+ (maybe-grade->grade (first lomg)) (1)
(lomg-sum (rest lomg)))]))
;;;; Tests:
(check-expect (lomg-sum MT-GRADES) 0)
(check-expect (lomg-sum ONE-GRADE) 70)
(check-expect (lomg-sum ONE-MGRADE) 0)
(check-expect (lomg-sum CLASS-GRADES) 240)
(check-expect (lomg-sum CLASS-MGRADES) 160)
;;;; Signature:
;; maybe-grade->grade: MaybeGrade -> Grade
;;;; Purpose:
;; GIVEN: a possible grade
;; RETURNS: 0 if there is no grade, the grade if there is one
;;;; Examples:
;;(maybe-grade->grade 'none) => 0
;;(maybe-grade->grade 80) => 80
;;;; Function Definition:
(define (maybe-grade->grade maybe-grade)
(cond
[(none? maybe-grade) 0]
[(number? maybe-grade) maybe-grade]))
;;;; Test:
(check-expect (maybe-grade->grade 'none) 0)
(check-expect (maybe-grade->grade 80) 80)
1 | following our deconstructor template, our function lomg-sum depends on a helper function
to deal with each possible grade in our LoGM and tell us if there is a grade to add for that element
or not |
Recall our slogan …
Average of a LoMG
Finally calculating the average
;;;; Signature:
;; lomg-average: LoMG -> NonNegativeReal
;;;; Purpose:
;; GIVEN: a list of possible grades
;; RETURNS: the average grade
;;;; Examples:
;; (lomg-average MT-GRADES) => 0
;; (lomg-average ONE-GRADE) => 70
;; (lomg-average ONE-MGRADE) => 0
;; (lomg-average CLASS-GRADES) => 80
;; (lomg-average CLASS-MGRADES) => 80
;;;; Function Definition:
(define (lomg-average log)
(cond
[(= 0 (lomg-sum log)) 0]
[else (/ (lomg-sum log)
(lomg-size log))]))
;;;; Tests:
(check-expect (lomg-average MT-GRADES) 0)
(check-expect (lomg-average ONE-GRADE) 70)
(check-expect (lomg-average ONE-MGRADE) 0)
(check-expect (lomg-average CLASS-GRADES) 80)
(check-expect (lomg-average CLASS-MGRADES) 80)
List of Structures
Lists can also hold structures.
Consider the case where we are designing a graphics program that keeps a list of coordinates. Part of the graphics program requires us to take in the list of coordinates and move then by a certain amount to the right on the x-axis.
;;;; Data Definition
;; A ListOfPosn (LoP) is one of
;; - empty
;; - (cons Pons LoP) (1)
;; INTERP: represents a list of cartesian points
;;;; Examples:
(define LOP-MT empty)
(define LOP1 (cons (make-posn 1 1) LOP-MT))
(define LOP (cons (make-posn 10 10)
(cons (make-posn 20 20)
LOP1)))
;; Deconstructor Template:
;; lop-fn: LoP -> ???
#; (define (lop-fn lop)
(cond
[(empty? lop) ...]
[(cons? lop) ... (posn-fn (first lop)) ... (2)
... (lop-fn (rest lop) )...]))
1 | Our LoP data definition has the self loop and a dependency on Posn |
2 | Out deconstructor template has a call to itself that matches the self loop in the data definition for LoP and a call to the
deconstructor template posn-fn matching the dependency to Posn in the data definition of LoP . |
Recall our slogan …
So let’s now write the function that will consume a list of points and a value to add to their x-coordinates
;;;; Signature:
;; lop-move-x: LoP Real -> LoP
;;;; Purpose:
;; GIVEN: a list of points and a value
;; RETURNS: a new list with all points moved by value units in the x-axis
;;;; Example:
;; (lop-move-x LOP-MT 100) => empty
;; (lop-move-x LOP1 100) => (cons (make-posn 101 1) empty)
;; (lop-move-x LOP 100) => (cons (make-posn 110 10)
;; (cons (make-posn 120 20)
;; (cons (make-posn 101 1) empty)))
;;;; Function Definition:
(define (lop-move-x lop dx)
(cond
[(empty? lop) empty]
[(cons? lop) (cons (posn-move-x (first lop) dx) (1)
(lop-move-x (rest lop) dx))])) (2)
;;;; Tests:
(check-expect (lop-move-x LOP-MT 100) empty)
(check-expect (lop-move-x LOP1 100) (cons (make-posn 101 1) empty))
(check-expect (lop-move-x LOP 100) (cons (make-posn 110 10)
(cons (make-posn 120 20)
(cons (make-posn 101 1) empty))))
;;;; Signature:
;; posn-move-x: Posn Real -> Posn
;;;; Purpose:
;; GIVEN: a cartesian point and a value
;; RETURNS: a new cartesian point with x coordinate increased by the given value
;;;; Examples:
;; (posn-move-x (make-posn 1 1) 2) => (make-posn 3 1)
;;;; Function Definition:
(define (posn-move-x posn dx)
(make-posn (+ (posn-x posn) dx)
(posn-y posn)))
;;;; Tests:
(check-expect (posn-move-x (make-posn 1 1) 2) (make-posn 3 1))
1 | following are deconstructor template, we need to take each element and add dx to it’s x-coordinate. We delegate
this task to a helper method posn-move-x that takes a posn and the value to add to the x-coordinate dx and returns
a new posn with the x-coordinate updated. We take this new Posn and add it to the result of our recursive call. |
2 | the recursive call takes the rest of our list and updates all remaining Posn s in a new list. |
Recall our slogan …
Filtering out points
Let’s try desiging another program that operates on a list of cartesian points. We would like to design a program that takes a list of cartesian points and filters out all points that have a negative value for the x or the y coordinate.
;;;; Signature:
;; lop-filter-negative: LoP -> LoP
;;;; Purpose:
;; GIVEN: a list of cartesian points
;; RETURNS: a list with only the cartesian points with both x and y being
;; positive
;;;; Examples:
;; (lop-filter-negative LOP-MT) => empty
;; (lop-filter-negative LOP) => LOP
;; (lop-filter-negative LOP-MT) => empty
;; (lop-filter-negative LOP-NEG1) => empty
;; (lop-filter-negative LOP-NEG1-X) => empty
;; (lop-filter-negative LOP-NEG1-Y) => empty
;; (lop-filter-negative LOP-MIXED) =>
;; (cons (make-posn 2 2)
;; (cons (make-posn 10 0)
;; (cons (make-posn 10 10) empty)))
;;;; Function Definition:
(define (lop-filter-negative lop)
(cond
[(empty? lop) empty]
[(cons? lop) (cond (1)
[(posn-positive? (first lop))
(cons (first lop) (2)
(lop-filter-negative (rest lop)))]
[else (lop-filter-negative (rest lop))])])) (3)
;;;; Tests:
(check-expect (lop-filter-negative LOP-MT) empty)
(check-expect (lop-filter-negative LOP) LOP)
(check-expect (lop-filter-negative LOP-MT) empty)
(check-expect (lop-filter-negative LOP-NEG1) empty)
(check-expect (lop-filter-negative LOP-NEG1-X) empty)
(check-expect (lop-filter-negative LOP-NEG1-Y) empty)
(check-expect (lop-filter-negative LOP-MIXED)
(cons (make-posn 2 2)
(cons (make-posn 10 0)
(cons (make-posn 10 10) empty))))
;;;; Signature:
;; posn-positive?: Posn -> Boolean
;;;; Purpose:
;; GIVEN: a cartesian point
;; RETURNS: true if x and y are positive, false otherwise
;;;; Examples:
;; (posn-positive? (make-posn 1 1)) => #true
;; (posn-positive? (make-posn -1 1)) => #false
;; (posn-positive? (make-posn 1 -1)) => #false
;; (posn-positive? (make-posn -1 -1)) => #false
;;;; Function Definition:
(define (posn-positive? posn)
(and (>= (posn-x posn) 0)
(>= (posn-y posn) 0)))
;;;; Tests:
(check-expect (posn-positive? (make-posn 1 1)) #true)
(check-expect (posn-positive? (make-posn -1 1)) #false)
(check-expect (posn-positive? (make-posn 1 -1)) #false)
(check-expect (posn-positive? (make-posn -1 -1)) #false)
1 | if we are given a non-empty list of points then check if the first cartesian point is positive. |
2 | if the first posn is positive then add it to the answer that we are building and the continue processing the rest of the list |
3 | otherwise, the posn is not positive so do not add it to the result but keep processing the rest of the list |
Recall our slogan …
We are deviating a little from our slogan of one task one function with our nested cond
in the implementation of lop-filter-negative
.
We will allow for nested cond
s only if the the clauses in the cond
are simple non-nested expressions , e.g., one function call.