Hello ECS Students
This is a bunch of notes taken from the classes if you want to read through them again
There is a sidebar that is on the left or accessible using the top left button in the header
All notes are written by Zachary Kohnen, the Student Assistant for Orange block, so all of the dates are for the orange block that those notes were taken on
If you have any questions about the content or improvements, you can check out the source on
GitHub or email me at 21kohnenz@wpsraiders.org
Unions and data design (October 13, 2020)
Jump to TL;DR (summary)
Code for this lesson on wescheme
Lab 2 Problem 2
Design a function message-to-employee which takes in the kind of data you defined above and produces a message about the employee’s paycheck. For example if the employee receives a paycheck every week you should produce “Here is your weekly paycheck!”. Is the template useful here? Why or why not?
Define what the data is
Paycheck can be weekly, biweekly, or monthly.
What does it represent?
Paycheck represents how often an employee is paid.
Now that we have it all in our head, we should put it down in a comment:
; A Paycheck is one of:
; - "weekly"
; - "biweekly"
; - "monthly"
; and represents how often an employee is paid
Once you have the idea, define some constants (if there are a finite amount).
(define PC-W "weekly")
(define PC-B "biweekly")
(define PC-M "monthly")
Once you have the idea and the data, create a template:
(define (paycheck-template pc)
(cond [(string=? pc PC-W) ...]
[(string=? pc PC-B) ...]
[(string=? pc PC-M) ...]))
The
...
s are not valid scheme, they just tell us that we will come back to them later.
Now we can move onto creating a function to tell the employee about their paycheck. Always start by laying out the function you want.
Name: message-to-employee
Input: Paycheck
Output: String
Description: Message an employee about when their paycheck arrives.
Now that you have it in your head, put it in a comment.
; message-to-employee : Paycheck -> String
; Message an employee about when their paycheck arrives
Once you have the layout of your function, ALWAYS write out (at least) 2 check-expect tests.
(check-expect
(message-to-employee PC-W)
"Here is your weekly paycheck!")
(check-expect
(message-to-employee PC-B)
"Here is your biweekly paycheck!")
(check-expect
(message-to-employee PC-M)
"Here is your monthly paycheck!")
In this case, we create 3 tests since there are 3 variants of our type.
Now that we have it all layed out, we can create a function by pasting our template.
(define (message-to-employee pc)
(cond [(string=? pc PC-W) ...]
[(string=? pc PC-B) ...]
[(string=? pc PC-M) ...]))
Now we need to change the ...
s to something more useful.
In our case, the ...
s would all become the same thing:
(define (message-to-employee pc)
(cond [(string=? pc PC-W) (string-append "Here is your " pc " paycheck!")]
[(string=? pc PC-B) (string-append "Here is your " pc " paycheck!")]
[(string=? pc PC-M) (string-append "Here is your " pc " paycheck!")]))
Since they are all the same, we can simplify the function down to just one line.
(define (message-to-employee pc)
(string-append "Here is your " pc " paycheck!"))
In the end, our template and custom type were not used since they were more or less extrenious, we did not need to create a custom type for this since there is one code path for every paycheck type
Lab 2 Problem 3
Design a function calculate-annual which takes in a Number, representing the amount an employee makes on a single paycheck, and the data describing how often they receive the paycheck. It produces a Number with an estimate of their annual wages. For example, if the employee gets their paycheck once per month you will need to multiply the input number by 12 since there are 12 months in a year. Is the template useful here? Why or why not?
As always, start your function definition with its signature
Name: calculate-annual
Input: Number and Paycheck
Output: Number
Description: Estimates how much an employee will earn by the end of the year
Now that you have it all out, put it in a comment:
; calculate-annual : Number Paycheck -> Number
; Estimates how much an employee will earn by the end of the year
Check expect time!
(check-expect (calculate-annual 4 PC-M) 48)
(check-expect (calculate-annual 34 PC-W) (* 34 52))
(check-expect (calculate-annual 450 PC-B) (* 450 26))
Now that we have the check-expects all out, its time to make the function:
(define (calculate-annual single-paycheck pc)
(cond [(string=? pc PC-W) (* single-paycheck 52)]
[(string=? pc PC-B) (* single-paycheck 26)]
[(string=? pc PC-M) (* single-paycheck 12)]
In this case, the custom data type is useful since we are able to use the seperate paths to calculate the paycheck differently
Lab 2 problem 4
Design data to represent the name of someone who has donated to a charity. Your data should account for the fact that the donor may wish to remain anonymous. Why can’t we use a String to represent the anonymous donor? What can we use instead? Don’t forget to follow all 4 steps of the design recipe for data.
The first thing we need to do when defining a data type is to define what it can be:
; A DonorName is one of:
; - String
; - false
; and represents the name of someone donating to charity
Now that we have our data type all layed out for us, we should now create (at least) 2 examples of the data type.
(define DONORNAME1 "Paul")
(define DONORNAME2 false)
Once we have some examples we can make a template:
(define (donor-template dn)
(cond [(string? dn) ...]
[(boolean? dn) ...]))
Remember, the ...s are there for us to replace later when we copy this template
Lab 2 problem 5
Design a function email-donor which takes in the type of data you defined above. If the donor is not anonymous it produces a message to send via email such as “Thank you, Bob!” if the donor’s name was Bob. If the donor was anonymous it produces false. What kind of data do you need to design in order to write the signature for this function?
As always, start your function definition with its signature:
Name: email-donor
Input: DonorName
Output: String or false
(we could make another data type, but to save time I will not)
Description: To send a message to the given donor if they are not anonymous
Now that you know it, put it in a comment:
; email-donor : DonorName -> String or false
; To send a message to the given donor if they are not anonymous
Now that we have the signature, we can write some check expects
(check-expect (email-donor DONORNAME1) "Thank you, Paul!")
(check-expect (email-donor DONORNAME2) false)
And finally, we can write the function itself, copying the template and replacing the ...
s
(define (email-donor dn)
(cond [(string? dn) (string-append "Thank you, " dn "!")]
[(boolean? dn) false]))
Too Long; Didn't Read
- Unions are useful for if you need to hold 2 different data types in the same spot
- Unions can be one data type or the other, but never both
- Unions are invisible to scheme, so scheme cannot enforce the types for us
- Unions can be separated using the
type?
functions, such asstring?
ornumber?
- Cond is very powerful for using unions. ie. the above function
- ALWAYS define at least 2 examples of the union if it has an infinite amount of possibilities see Lab 2 Problem 4
- If Possible define all variants of the union (only for non infinite ones) see Lab 2 Problem 2
Text Editor v2 (October 14, 2020)
Jump to TL;DR (summary)
Code for this lesson on wescheme
In our previous text editor, we only used the text as the world state, limiting us to not be able to move the cursor around the screen
Currently, we can only store data one at a time, it could be a union (Such as a String or a Number) but it could never store both at the same time.
To hold more than 1 data at a time, we can use structures
Designing structures
An address
When creating a structure, you should figure out what data you want to store.
For our example, we will create a structure to store an address. In an address,
we want to store a house-number
, city
, street
, and zipcode
.
As with all definitions, we want to start out with a comment describing it:
; An Address is a (make-address Nat String String String Nat)
Nat in this case is used to tell us that it is a natural number
In the code snippet, we specify the name, and then a function make-address
to create
an address, now to define it in scheme to actually use, we can use the define-struct
tool
(define-struct address [house st city state zip])
Now, once we have define-struct
, we can create one using the automatically created make-address
function.
(make-address false "hello" 2 -7 true)
and that would return
(address false "hello" 2 -7 true)
Notice that this does not match our comment, but scheme cant read our comment, so it doesn't know better.
To make sure that we match our address definition, we can make some samples and a more detailed description of the structure.
The more detailed description looks like:
; and represents someone's address
; - where house is the house number
; - st s the name of the street
; - city is the name of the city
; - state is the abbreviation for the state
; - and zip is the zip code
and would be placed after the structure definition.
Now that we have a good description, we can make some samples for reference
(define ADDRESS1 (make-address 50 "Rice St" "Wellesley" "MA" 02482))
(define ADDRESS2 (make-address 1 "a" "b" "c" 7))
Now that we have the structure defined, we need a way to access the different members of it. Lucky for us, Scheme creates a bunch of helper functions for us:
(address-<field> address)
where <field>
is replaced by the name of the field defined above. ie.
(address-house ADDRESS1)
would return 50, since it is the value we put in the structure when we made it or "constructed it"
We can put all of these helper methods together into a template for future use
(define (address-template a)
(... (address-house a)
(address-st a)
(address-city a)
(address-state a)
(address-zip a) ...))
Remember, the ...s are there for us to replace later when we copy the template function.
Another helper function created for us is the address?
function which lets us check if
a type is an address.
(address? ADDRESS1) ; true
(address? (make-address false false false false false)) ; also true
(address? 3) ; false
The second example returned true because scheme does not know about our capitol A Address type and does not know that we want the fields to be specific types. Because of that, as long as we made a structure using the
make-address
function scheme will see it as a structure
Making a structure that uses another structure
Now that we have the address structure created, we can make a student structure that uses our address structure as one of the fields. To start making our student structure, which will hold a name, year of graduation, and an address we start with the comment to describe it:
; A Student is a (make-stdnt String Nat Address)
We are going to name our structure
stdnt
to help show that the name of the structure can be whatever you want
Once we have the comment, we need to tell scheme about it, using the define-struct
tool
(define-struct stdnt [name gradyr house])
Remember the names can be anything we want, but we need to use the same ones defined here later in our project, NOT the data type
Once we have the structure defined for scheme, we now need to describe each field
; and represents a student
; - where name is the student's full name
; - grad-yr is the student's graduation year
; - and house is the address where the student lives
Now, we define 2 sample students:
(define STUDENT1 (make-stdnt "Sheev" 4 ADDRESS1))
(define STUDENT2 (make-stdnt "Alice" 1234567789 ADDRESS2))
As always, after setting up the struct and creating the samples, we should create a template for us to use later.
(define (student-template s)
(... (stdnt-name s)
(stdnt-grad-yr s)
(address-template (stdnt-house s)) ...))
Notice how in this template, we also use the address-template. That is because the house member of the
student
structure is anaddress
structure, so we need to use the tools from theaddress-template
to get the data out of the address
Using a structure in a function
What if we want to use our new fandangled structure in a function? Well, lets try it out! As always, we can start by making a function, say one that adds one to a student's year-of-graduation (holds them back)
Holding a student back
As always, start by laying out the function you want.
Name: stay-back
Input: Student
Output: Student
Description: Adds 1 to a student's graduation year.
And now we can put that into a nice comment for us to read:
; stay-back : Student -> Student
; Adds 1 to a Student's graduation year
Once we have the comment for the function, we can create (at least) 2 check-expect tests to verify that it is working how we expect.
(check-expect
(stay-back STUDENT1)
(make-stdnt "Sheev" 5 ADDRESS1))
(check-expect
(stay-back STUDENT2)
(make-stdnt "Alice" 123456790 ADDRESS2))
Okay, that all looks great, now we have to define the function:
(define (stay-back s)
(+ 1 (stdnt-gradyr s)))
This above code with not work, remember you cannot modify a structure. In order to make a change, you have to create a whole new one, created from the old one with the new value in place of the old.
Heres how you would do that:
(define (stay-back s)
(make-stdnt (stdnt-name s) (+ 1 (stdnt-gradyr s)) (stdnt-house s)))
Notice that we have to re-create the structure with
make-stdnt
but with the difference being that the gradyr is now one more than the given
Changing the zip code
As always, start by laying out the function you want.
I know it can get repetitive, but your later self with thank you
Name: update-zip-for-student
Input: Student and Nat
Output: Student
Description: Update a student's zip code to be a new, given a new zip code
And now as a nice concise comment
; update-zip-for-student : Student Nat -> Student
; Update a student's zip code to be a new, given a new zip code
Now that we have the function all layed out, we can start with some check-expects
(check-expect
(update-zip-for-student STUDENT1 12345)
(make-stdnt "Sheev" 4
(make-address 50 "Rice St" "Wellesley" "MA" 12345)))
(check-expect
(update-zip-for-student STUDENT2 98765)
(make-stdnt "Alice" 123456789
(make-address 1 "a" "b" "c" 98765)))
Looks normal, and it is, all that changes is the zip code. This should be easy, right?
Well yes, but we have a lot of writing to do. We can define our function, copying the student template, but, we cant just update the zip by replacing the last parameter with a new zip code, cause it takes in a whole structure. We need to make another function that can update the zip in the structure for us.
Whenever you write a function, try to keep it limited to using only one data type at a time. In our case, we only use the student structure in this function, and we will make another
update-zip
function to handle the address structure
(define (update-zip-for-student s new-zip)
(make-stdnt (stdnt-name s)
(stdnt-grad-yr s)
(update-zip (stdnt-house s) new-zip)))
Now, its time to make the update-zip function. Ill go quick with this one, assuming you have gotten faster with them aswell.
; update-zip : Address Nat -> Address
; Update the zip code for this address to the given #
Bam, theres our signature and description
(check-expect
(update-zip ADDRESS1 02020)
(make-address 50 "Rice St" "Wellesley" "MA" 02020))
(check-expect
(update-zip ADDRESS2 13579)
(make-address 1 "a" "b" "c" 13579))
Whamo, we have some check expects
And then we can, very simply, just create the update-zip
function
(define (update-zip a new-zip)
(make-address
(address-house a)
(address-st a)
(address-city a)
(address-state a)
new-zip))
It may look like a lot of code, but if you break it down, all it does is create a new address, give it the house, st, city, and state of the old address, and just replace the zip with the new zip
Tying it together into a text editor
Now that we have learned to use structures, we can put them to good use. We can use them in our text editor. Remember when we had our text editor, it could type and delete, but you could not move the cursor around. That was because our world state was a single string: The text that was in the editor. We had no way of storing the cursor position. Well, using these new hot structures that we have learned about, that is all about to change.
So. first thing you do with any data type, function, or struct is a simple, one line comment
describing it. In our case, we will make a structure called TextEditor
or te
for short with the
text and cursor position as members. We can write this out in a comment like so:
; A TextEditor is a (make-te String Nat)
Now that we know about the text editor, we need to tell scheme about it. (remember, anything after a
;
is invisible to scheme and is only for us to read).
We can define the struct as such:
(define-struct te [text cursor])
And now we can describe the types in the text editor further
; and represents a text editor
; - where text is the text you have typed
; - and cursor is the index of the cursor
All together they will look as such:
; A TextEditor is a (make-te String Nat)
(define-struct te [text cursor])
; and represents a text editor
; - where text is the text you have typed
; - and cursor is the index of the cursor
Now that we have the struct created and layed out. We need to make 2 samples:
(define TE1 (make-te "hello" 1))
(define TE2 (make-te "ECS" 0))
Although we don't have time to implement it into our text editor but we can make a simple
function that we may be able to use as the on key handler for our big-bang program. Since it
will handle key events, it take in our world state, in this case the TextEditor
structure and the
key event.
; insert-text : TextEditor KeyEvent -> TextEditor
; Insert text the user typed at the cursor's location
An example check expect could exist as such:
(check-expect (insert-text TE1 "a")
(make-te "haello" 2))
Too Long; Didn't Read
-
Structures are a way to store more than one data piece in the same place
-
Structures can be created like so:
(make-struct my-struct-name [field1-name field2-name ...])
-
When using structures, you can use the generated function
<name-of-structure>-<name-of-field>
where<name-of-structure>
is replaced with the name of your structure. (ex:address
) and<name-of-field>
is replaced with the name of the field that you want to read. (ex:st
). So to access an address's st(reet), we can use the helper function(address-st a)
-
When using structures in a function, only access one data type at a time, make different functions for deeply nested data, such as the address inside of the student see: Changing the zip code
-
ALL DATA IN SCHEME IS IMMUTABLE (unable to be mutated (changed))
- To be able to modify a structure, you have to create a whole new one with the new data see: Holding a student back
-
Scheme is very lax and does not enforce the types you want in your structures, you have to be weary of that and enforce it yourself
Text Editor v2 contd (October 19, 2020)
Jump to TL;DR (summary)
Jump to Table Of Contents
Starting where we left off last week
Below is the code we have written for the text editor last week:
; A TextEditor is a (make-te String Nat)
(define-struct te [text cursor])
; and represents a text editor
; - where text is the text you have typed
; - and cursor is the index of the cursor
(define TE-EMPTY (make-te "" 0))
(define TE1 (make-te "hello" 1))
(define TE2 (make-te "ECS" 0))
; insert-text : TextEditor KeyEvent -> TextEditor
; Insert text the user typed at the cursor's location
(check-expect (insert-text TE1 "a")l
(make-te "haello" 2))
(check-expect (insert-text TE2 "right")
(make-te "ECS" 1))
We had created a simple structure for the text editor which can hold both the text and the cursor position.
We have accidentally skipped a step between the TextEditor
structure and the
insert-text
function. We need to make a template.
TextEditor template
We are now going to design the template.
See ECS Help Sheet's Designing Templates
section.
(define (te-template te)
(... (te-text te)
(te-cursor te)
...))
Since both of these are primitives
or things that we have not defined ourselves,
we do not have to call or make any more templates, we can just use them directly.
Figuring out what stays the same
See: ECS Help Sheet's
Designing Programs
section for more information on designing a program
Lets make some constants. We know that the background will stay the same, so lets make that
(define BACKGROUND (rectangle 600 200 "solid" "light blue"))
We also know that the font color, font size, and an image for the cursor will stay the same:
(define FONT-COLOR "black")
(define FONT-SIZE 20)
(define CURSOR-IMAGE (rectangle 2 50 "solid" "black"))
Remember, we can always come back and define more constants, these are just the ones we can think of right now. And remember, putting things in constants will help a lot since you only have to change them in one spot, and not all over your program if you want to change their value
Figuring out what clauses we need
Now that we have some constants, we can move to step 2. We need to figure out what
clauses we need.
We will probably need to-draw
so that we can draw the world, we want on-mouse
so that we can
click and move the cursor around, and lastly we want on-key
so we can capture the user's typing
and display it.
Our main function
Finally, we are here!!1! We can design our main function. Lets call this microsoft-word
(this might violate tons of trademark law, but we are too cool for trademark law). We also need to
figure out what goes in and what comes out. With most all main functions, they will take in the
WorldState
and return a WorldState
. And now, we can put it all together with a nice description
as well
; microsoft-word : TextEditor -> TextEditor
; Starts up a text editor where you can move the cursor
(define (microsoft-word initial-editor)
(big-bang initial-editor
[to-draw draw-text-editor]
[on-mouse curse-of-the-cursor] ; Remember, these can be named whatever we want, as
; long as we know what they mean
[on-key insert-text]))
Now that we have the main function and a wishlist of all the functions we want, we can make their function signatures so we know what we want later when we design them.
Function signatures
Lets define our draw-text-editor
signature. Since it is the to-draw
handler, it takes in the
WorldState
and outputs the drawn Image
. We can put it in a nice old comment with a description
; draw-text-editor : TextEditor -> Image
; Draws the text with the cursor at a certain position
Now that we have draw-text-editor
, we can define the curse-of-the-cursor
. This function will be
in the clause on-mouse
. Because of that, it gives us some special things. As always, it will give
us the WorldState
and it will also give us the x and y coordinate of the mouse and a MouseEvent
defining what the mouse did, and it returns a new modified (or not) WorldState
. We can plop that
into a handy comment for us.
; curse-of-the-cursor : TextEditor Number Number MouseEvent -> TextEditor
; Change the position of the cursor when you click
And we had already created the insert-text
function last week:
; insert-text : TextEditor KeyEvent -> TextEditor
; Insert text the user typed at the cursor's location
Defining functions
Now that we have all of the signatures in our wish list, we can start writing the functions.
We can start with the draw-text-editor
function. As always, we need to start with some check
expects.
(check-expect
(draw-text-editor TE-EMPTY)
(overlay (beside CURSOR_IMAGE (text " " FONT-SIZE FONT-COLOR)) ; This will create the blank
; image with just a cursor
BACKGROUND))
(check-expect
(draw-text-editor TE1)
(overlay
(beside (text "h" FONT-SIZE FONT-COLOR)
CURSOR_IMAGE
(text "ello" FONT-SIZE FONT-COLOR)) ; Here we put the cursor between the `h` and
; `ello` since it is a cursor pos 1
BACKGROUND))
Now we can go and define the draw-text-editor
function body.
So, we want to have 2 states, either there is no text in the editor, or there is some text.
we can check for such using a cond
.
(define (draw-text-editor editor)
(cond [...]
[...]))
Now we need to somehow get the text out of the editor structure. We can do so by using our template. Lets paste it down here so we can use it.
(define (te-template te)
(... (te-text te)
(te-cursor te)
...))
To access the text, we can use the te-text
helper function from our template.
So we can paste it into the first cond. We need to check it to make sure that its length is > 0,
meaning it is not empty
(define (draw-text-editor editor)
(cond [(> (string-length (te-text editor)) 0)] ; new
[...]))
Now we can define a simple template of what we will do. Remember, we want to write some text, the cursor, and then the rest of the text
(define (draw-text-editor editor)
(cond [(> (string-length (te-text editor)) 0)
(overlay (beside (text ... FONT-SIZE FONT-COLOR) ; new
CURSOR-IMAGE ; new
(text ... FONT-SIZE FONT-COLOR)) ; new
BACKGROUND)] ; new
[...]))
Now, we need to figure out how to draw the text in front of the cursor. We can get a slice of
a string by using the substring
function, and use the cursor position to chop it. We want to get
the substring
of the te-text
from 0 up until te-cursor
(define (draw-text-editor editor)
(cond [(> (string-length (te-text editor)) 0)
(overlay (beside (text (substring (te-text editor) 0 ; new
(te-cursor)) ; new
FONT-SIZE FONT-COLOR)
CURSOR-IMAGE
(text ...))
BACKGROUND)]
[...]))
And now that we have the first text, we can do the second text very much the same, except this time,
we don't need to give substring
an ending point, because in doing so, substring
will just go
until the end.
(define (draw-text-editor editor)
(cond [(> (string-length (te-text editor)) 0)
(overlay (beside (text (substring (te-text editor) 0
(te-cursor))
FONT-SIZE FONT-COLOR)
CURSOR-IMAGE
(text (substring (te-text editor) ; new
(te-cursor)) ; new
FONT-SIZE FONT-COLOR))
BACKGROUND)]
[...]))
Now that we have the text having state, we can work on the section of the function when there is no text to draw. Since we will draw the same thing every time you have no text, we can extract it into a constant. We can copy it from our first check expect
(define BLANK-EDITOR-IMAGE
(overlay (beside CURSOR_IMAGE (text " " FONT-SIZE FONT-COLOR)) ; This will create the blank
; image with just a cursor
BACKGROUND))
and then update our check expect from
(check-expect
(draw-text-editor TE-EMPTY)
(overlay (beside CURSOR_IMAGE (text " " FONT-SIZE FONT-COLOR)) ; This will create the blank
; image with just a cursor
BACKGROUND))
to
(check-expect
(draw-text-editor TE-EMPTY)
BLANK-EDITOR-IMAGE) ; changed
and we can add that into our new function
(define (draw-text-editor editor)
(cond [(> (string-length (te-text editor)) 0)
(overlay (beside (text (substring (te-text editor) 0
(te-cursor))
FONT-SIZE FONT-COLOR)
CURSOR-IMAGE
(text (substring (te-text editor)
(te-cursor))
FONT-SIZE FONT-COLOR))
BACKGROUND)]
[else BLANK-EDITOR-IMAGE])) ; new
Too Long; Didn't Read
- USE YOUR ECS Help Sheet. It will walk you through it all.
- Make constants! they will help you keep duplicated code to a minimum, and will save you when you need to change them.
- Complex programs can be broken down into simpler parts and are a lot easier when you do so
- WRITE COMMENTS. Since we were unable to finish the text editor today, we will thank ourselves later when we have to come back to it, that we have left comments telling us about what we still need to do. (Your memory can only serve you so well)
Recursion and balloon game (December 1, 2020)
Jump to TL;DR (summary)
Starting code for this lesson on wescheme
Starting with the balloon game
We left off writing a game where the user can make balloons which will fly upwards. Our game worked and was great, but there is a slight problem, we can only have 2 balloons at a time, and that sucks. We want all the balloons!!!
Currently our Balloon game type is as follows:
; A BalloonGame is one of:
; - false (no balloons)
; - Posn (balloon)
; - (make-twothings Posn Posn)
; and represents either a screen with no balloons, a screen with a balloon,
; or a screen with two balloons
As we can see, there are the 3 variants, no balloons, one balloon and 2 balloons. If we wanted 3 balloons what could we do? One option would be to make a 4th variant and make it something like:
; - (make-threethings Posn Posn Posn)
But that method has a problem. First off, we need to make another whole data type threethings
and making data types is so not lit. Another problem with this method is every single time
we want to add another balloon, we have to make another variant, and a whole new data type.
UGH.
What if I told you that you lived in a simulation that there is a way to make a data definition
that can hole infinite amounts of data.
Recursive data structures
You saw this coming if you read the title, but what you might not have known is what in the gosh darn is this fandangled recursion thing? Well, I can hit you with what my friend Merriam Webster says:
recursion (noun)
re·cur·sion | ri-ˈkər-zhən
a computer programming technique involving the use of a procedure,
subroutine, function, or algorithm that calls itself one or more
times until a specified condition is met at which time the rest
of each repetition is processed from the last one called to the
first
Ok webster, that's cool and all, but like HUH? what do all those words mean. Procedures? Subroutines?
It looks like Mr. Webster is a bit too verbose for us, let be break it down.
Recursion is when some thing does itself multiple times. In terms of scheme and our programming knowledge, there are 2 places where we can use recursion. Functions and structures.
A data structure is recursive when one of the parts of it is the data structure itself. A way to visualize it is to think of russian nesting dolls (also called Matryoshka):
Click To Expand Images
Each doll has its outer shell with the paint and the nice artwork, but if you look inside of them, they have a whole new doll inside.
This outer doll can be thought of as the structure and the paint could be a property of it, much
like the positions in our twothings
structure and the doll on the inside would be a whole new two
things struct. Lets try to change our BalloonGame
into a recursive structure so that we can have
infinite balloons and it may help to see a concrete example.
Making a recursive structure
Back to our BalloonGame
structure:
; A BalloonGame is one of:
; - false (no balloons)
; - Posn (balloon)
; - (make-twothings Posn Posn)
; and represents either a screen with no balloons, a screen with a balloon,
; or a screen with two balloons
We can join the single and double data structure variants into one recursive data variant.
; A BalloonGame is one of:
; - false (no balloons)
; - (make-twothings Posn BalloonGame)
; and represents either a screen with no balloons, or a screen with at least one balloon
In the above definition, we replaced the second Posn in the twothings
struct with a Balloon,
but what does that entail exactly? Well, now chain
our Balloon Gme
s together.
We can start with the inside most BalloonGame
, the false variant.
(define NO-BALLOONS false)
Since the false variant of our data type has no BalloonGame
inside of it, it is the end of
the recursion, the center most Matryoshka. But we can now put another doll around it:
(define ONE-BALLOON (make-twothings (make-posn 10 20) NO-BALLOONS))
Now our ONE-BALLOON
constant is a BalloonGame
with one balloon. If we unravel it, or replace
the constants with their actual value so its a bit easier to picture, it will look as such:
(define ONE-BALLOON (make-twothings (make-posn 10 20) false))
The false at the end signals that that is the end of our recursion. Now if we have 2 balloons, we can show that like so:
(define TWO-BALLOONS (make-twothings (make-posn 250 93) ONE-BALLOON))
or unwrapped:
(define TWO-BALLOONS (make-twothings (make-posn 250 93) (make-twothings (make-posn 10 20) false)))
Once again, we have that false at the end to signal that our recursion is done.
Recursion is a lot to wrap your head around, so this could take multiple reads through
(and definitely should), but here are a few more examples of the BalloonGame
data structure
for you to gander at
(define NO-BALLOONS false)
(define ONE-BALLOON (make-twothings (make-posn 10 20) NO-BALLOONS))
(define TWO-BALLOONS (make-twothings (make-posn 250 93) ONE-BALLOON))
(define THREE-BALLOONS (make-twothings (make-posn 200 200) TWO-BALLOONS))
(define FOUR-BALLOONS (make-twothings (make-posn 350 26) THREE-BALLOONS))
And them "unraveled"
(define NO-BALLOONS false)
(define ONE-BALLOON
(make-twothings
(make-posn 10 20)
false))
(define TWO-BALLOONS
(make-twothings
(make-posn 250 93)
(make-twothings
(make-posn 10 20)
false)))
(define THREE-BALLOONS
(make-twothings
(make-posn 200 200)
(make-twothings
(make-posn 250 93)
(make-twothings
(make-posn 10 20)
false))))
(define FOUR-BALLOONS
(make-twothings
(make-posn 350 26)
(make-twothings
(make-posn 200 200)
(make-twothings
(make-posn 250 93)
(make-twothings
(make-posn 10 20)
false)))))
In the "unraveled" version — which by the way you should almost never type out by hand, always use the method shown above it, where you have each nested one get defined before it — it becomes a bit clearer to see the "nesting" of the structure inside of itself. It is worthy to note that all of them end in false. If there not a second, non recursive variant of our data type, we would never to be able to stop recursing, but since we have that second false variant, we are allowed to stop our data type with the false variant.
Since we have now updated the data definition and its samples (the NO-BALLOONS - FOUR-BALLOONS above) we can move to the final step of the data defining process, which is to make our template. In our case, we just need to update our template.
Currently we have this:
; BalloonGame -> ???
(define (balloon-game-template game)
(cond [(boolean? game) ...]
[(posn? game) (posn-template game)]
[(twothings? game)
(... (posn-template (twothings-thing1 game))
(posn-template (twothings-thing2 game)))]))
And that template worked for our 3 variants, the no balloons (false), the one Posn, or the 2 Posns.
Since we no longer have 3 variants we have to change the template. We can begin by removing the one
posn template, since although we do use only 1 posn, we still use the twothings
struct.
; BalloonGame -> ???
(define (balloon-game-template game)
(cond [(boolean? game) ...]
- [(posn? game) (posn-template game)]
[(twothings? game)
(... (posn-template (twothings-thing1 game))
(posn-template (twothings-thing2 game)))]))
Now its all fine and dandy? Right? Well not yet... We also changed the data that is stored in the two things structure. Since we changed our definition, we also need to change the code that uses it, or the implementation.
In our new data definition for the two things variant, we changed from 2 Posns to a
Posn and another BalloonGame. In our above template, we call the posn-template
on the
thing1
and the thing2
from twothings since they both used to be posns, Now the second
one is another BalloonGame
so we have to call this same baloon-game-template
on that
baloon game
; BalloonGame -> ???
(define (balloon-game-template game)
(cond [(boolean? game) ...]
[(twothings? game)
(... (posn-template (twothings-thing1 game))
- (posn-template (twothings-thing2 game)))]))
+ (balloon-game-template (twothings-thing2 game)))]))
As you can see, we now have our template calling that same template. This is very similar to our recursive data structure in a lot of ways, I wonder what it would be called?
Recursive functions
Recursive functions are very much the same as recursive data definitions. But instead of containing themselves, as recursive structures do, they call (or run) themselves.
We can see that in practice in our new and updated balloon-game-template
:
; BalloonGame -> ???
(define (balloon-game-template game)
(cond [(boolean? game) ...]
[(twothings? game)
(... (posn-template (twothings-thing1 game))
(balloon-game-template (twothings-thing2 game)))]))
Since the function calls itself, it needs a path to take that could not call itself or else it would be stuck calling itself which would call itself which would....
Just like how a recursive structure needs a second variant a recursive function needs a cond or if with one or more of the paths not calling itself.
This escape can be seen in our template with the boolean? cond case which will run when we get to the end of our "nested dolls".
# TODO: FUNCTION HANDLING THEM # TODO: REST
Too Long; Didn't Read
- Final Product
- Recursion is the method of nesting something inside of another of that same thing
- We can have recursive data structures (A structure that contains itself) (think of a russian nesting doll)
- We can have recursive functions (A function that calls itself)
- With all recursive things, you MUST have an 'escape'
- Recursive data structures must have a variant to signal that the recursion is finished (our false case above)
- Recursive functions must have a way to not call themselves again, so they must contain a cond/if with at least one branch not calling themselves
- If you do not have this escape from recursion
- Your data structure would be infinitely large since it would forever contain 1 more of itself which would contain 1 more of itself which ....
- You function would never be done running since it would forever call itself which would call itself which would call itself which would ....
Introduction
Welcome to the wonderful world of python. Python is a very different language than scheme/racket which we have used so far. Almost all of the syntax (or the structure of the language) will be different than how we have done previous.
Below is a more in depth dive into each aspect of the language
Math
Unlike scheme which uses prefix notation for mathematical operations such as (+ 6 8)
which would
add 6 and 8 together. Python on the other hand uses infix notation such as 6 + 8
which is much more similar to the mathematical notation you have learned before.
To add, subtract, multiply, and divide in python you would do the following:
# Addition
1 + 2 # = 3
2 + 4 # = 6
# Subtraction
2 - 3 # = -1
3 - 2 # = 1
# Multiplication
-1 * 2 # = -2
3 * 4 # = 12
# Division
2 / 6 # = .33333
6 / 2 # = 3
As well as the usual operators, python has a few others.
In math class or when using a calculator, it is common to represent an exponent with the ^
character ex: 2^6
would represent 2 to the power of 6, but that is not the case in python. The ^
character already has a meaning as the XOR operator, which does not matter for this class yet, but
is important to note that python will not throw out code that uses the ^
character but will just
produce a weird value. Instead, python uses 2 multiplication symbols to denote exponentiation: **
.
Some more examples are below.
2 ** 6 # = 64
3 ** 3 # = 27
Sometimes when you divide, you might not care about the decimal or fractional portion of the result
and for that use case, python has the double division //
operator for "integer division" or
division without decimals
2 // 16 # = 0
10 // 8 # = 1
And when dividing you might only care about the remainder and for that, python has the modulo %
operator.
6 % 3 # = 0
7 % 3 # = 1
More Math
Sometimes you need more than just the simple operations and for that, python has the math library. The documentation for the math library can be found on the official python docs but python is a very popular language and is very easily google-able. Do not hesitate to use the internet whenever you need to reference some docs or look something up.
In order to use libraries, you need to import
them. To import the math library you would do as
such:
import math
Functions from a library can be used by putting the library name, a dot, and then the name of the
function. ex: math.sin()
would be the sin function in the math library
Here are a few useful but non-exhaustive list of the functions available from the math library.
# Trig functions
math.sin(math.pi) # = 0
math.cos(math.pi) # = -1
math.tan(math.pi) # = 0
# Rounding functions
math.floor(10.2) # = 10
math.ceil(10.2) # = 11
# Absolute value
math.fabs(-100) # = 100
# Factorial
math.factorial(4) # = 1 * 2 * 3 * 4 = 24
# Logarithms
math.log(4) # = ln(4) = 1.3862943611
math.log(100, 10) # = log_10(100) = 2
math.log10(100) # = log_10(100) = 2
math.log(100, 5) # = log_5(100) = 2.8613531161
Once again, the docs have an exhaustive list of all methods available from the library.
Booleans
Just like in scheme, we can also deal with true/false conditions or booleans. These are also used in python, although their names are capitalized:
True
False
Just like in scheme, we can also do logical operations with the booleans, such as and
and or
which are used much like they are in scheme, just in infix notation instead of prefix notation.
# Logical and
True and True # = True
True and False # = False
False and True # = False
False and False # = False
# Logical or
True or True # = True
True or False # = True
False or True # = True
False or False # = False
# Logical not
not True # = False
not False # = True
There is also a less common logical operation called xor
or eXclusive OR
which you may never
have to use, but is good to know. It will return True
if one and only one of the inputs is True
which can be seen below.
True ^ True # = False
True ^ False # = True
False ^ True # = True
False ^ False # = False
Conditionals
When dealing with numbers, we may want to check if some condition holds true. There are 2 different types of conditionals, equality and inequality. You may remember these from your math class, but if not here is a refresher.
Equality is the comparison of 2 exact numbers, such as _ is equal to _
or _ is not equal to _
.
Equality can be expressed in python as such:
1 == 2 # = False
6 == 6 # = True
6 != 9 # = True
12 != 12 # = False
Inequality is checking the relation of 1 number with respect to another or _ is greater than _
or
_ is less than _
. These operations can be expressed in python as such:
1 > 2 # = False
6 > 6 # = False
6 >= 9 # = False
12 >= 12 # = True
6 < 9 # = True
12 < 12 # = False
1 <= 2 # = True
6 <= 6 # = True
Strings
Similarly to scheme, strings, which are just text, can be created using quotation marks ("
) aka
double quotes. But unlike scheme python also lets you use apostrophes ('
) aka single quotes to
surround your text. Both act exactly the same.
"Strings can be defined using double quotes"
'or using single quotes'
When using double quotes, you are allowed to use apostrophes in the text as usual
"they're late"
And when using single quotes, you can use double quotes inside of the text with no problem
'And then he said "Oh no you did not!"'
When you want to use both in one string, things get a bit more complex. The quotes that you write around your string, signal to the language that everything inside of it is text, but when the text contains the same quote you used to tell the language about the text, the language will mistakenly see it as you ending the string.
You can see this in the syntax highlighting (coloring) of the code below changing after that quote, meaning that python would not read this how we wanted.
"They're having a lot of fun and yelling "yippee""
'They're having a lot of fun and yelling "yippee"'
To get around this issue, we can use what is called "escaping quotes". If you want to use the
character that you used to make a string (ie. '
inside a single quoted string or "
inside a
double quoted string) you can put a backslash \
character in front of it to tell the language to
treat it as part of the string.
'They\'re having a lot of fun and yelling "yippee"'
"They're having a lot of fun and yelling \"yippee\""
Note the colors staying the same throughout the string. Both strings above are exactly equal, the only difference is what quote we are using.
Sometimes it is useful to combine 2 strings, and python allows us to do so using the +
operator.
"Hello" + "world" # = "Helloworld"
We may also want to create a string that has multiple lines of text. We can do so using a multiline strings which are defined using 3 double quotes
"""Hello there
this string
spans across
multiple lines :D"""
You can also use the newline character \n
instead to denote multiple lines in a string
"Hello there\nthis string\nspans across\nmultiple lines :D"
The above 2 strings are exactly equal, they are just different ways of writing them
Comments
A lot of the time, code is complicated. Reading code is 2x harder than writing that same code. To help ourselves with our programming, programming languages have comments, or lines/sections of code that are not read by the language, but instead by the programmer. I have been using comments in these notes with notes and information on what is in the code that I am showing you. These comments are denoted by a different, dimmer color than the rest of the code.
Comments in python are denoted using the pound sign #
aka hash or hashtag.
# Cool single line comment
Comments can appear after code, but any text after the #
is interpreted as a comment.
2 + 5 # Cool comment after code
If you want a comment that spans multiple lines, you can use a multiline comment which will just get ignored when you run the program since you do nothing with it
"""
Weird whacky multiline string acting as a
multiline comment
"""
But it is probably better to just use multiple comments, one per line like a sane person
# Or just use
# Multiple lines of these
# Like a sane human being
Turtle
You cant have a programming lesson without a bit of fun, so here is our fun for this lesson. We get to draw! yaay woot woot.... Just me? ok...
In python, we can use the turtle library. The documentation for the turtle library can be found
on the official python docs Just like with the math library, we have to
import
the library to use it.
import turtle
With all good art, we need color, so we can go ahead and set the color of the turtle with the color method
turtle.color('red', 'yellow')
To move the turtle, we have 2 options. We can either move in a direction, ie forward
, backward
,
or we could move to an exact position.
# Turtle movement
turtle.forward(10)
turtle.left(120)
# Turtle teleportation
turtle.setx(-100)
turtle.sety(100)
The coordinates of the canvas that we can draw on are on the x axis, -180 to 180 and on the y axis, -200 to 200. The center of the screen being 0,0.
We can also stop the turtle from drawing a path, or change its color mid stroke using the functions below.
# Turtle pen control
turtle.penup()
turtle.pencolor('red')
turtle.pendown()
Once again, the docs have an exhaustive list of all methods available from the library.
Remember, python is very google-able, please feel free to google to your heart's content.
Too Long; Didn't Read
If you want an abridged version, you can look at just the code that I have gone over here
Constants and Functions
Constants
Starting with some code from the introduction that draws a square
import turtle
turtle.forward(150)
turtle.left(90)
turtle.forward(150)
turtle.left(90)
turtle.forward(150)
turtle.left(90)
turtle.forward(150)
turtle.done()
We can see that our code is very repetitive. We call turtle.forward
4 times and turtle.left
3
times. We also give them all the same values to the functions over and over. We can start by getting
rid of the same value over and over and over again using constants, much like we have with scheme.
Constants in python are defined with all caps names and our constants would look something like
SIZE = 150
ANGLE = 90
Just like in scheme, we use all caps for our constants to show that they will never change. If we wanted to have a constant with spaces in its name, we would write it like such
THIS_IS_MY_CONSTANT = "hello"
And now that we have these constants defined, we can replace the numbers in our code with these constants
turtle.forward(SIZE)
turtle.left(ANGLE)
turtle.forward(SIZE)
turtle.left(ANGLE)
turtle.forward(SIZE)
turtle.left(ANGLE)
turtle.forward(SIZE)
turtle.done()
Now we just have one spot to change our size and angle if we want to draw a different shape or to draw a different size. The code is still repetitive though. If we wanted to draw 2 squares, we would have 14 lines of code. bruh :(. Instead, we can use
Functions
Functions, which you are hopefully familiar with from scheme, are tools that allow us to group blocks of code together which we can run and even give values to. In python, we follow the same steps as in scheme when defining a function.
- Signature
- Purpose/Description
Tests- Actual Function Body
Ok, we do all of them except tests. I know, it is so sad. They were my favorite part of programming too.
Now that we have that out of the way, we can move our code into a function. We start with the
signature. Since this function is drawing a square, I think we should name it draw_triangle
. No?
Ok... I guess we can call it draw_square
. And we want our function to take in the size of the
square so that we can use it for small and big squares. Our function's signature
would look something like
# draw_square : Number -> Image
Although, yes, this function does not return
an image per se, we will still put it down as such,
to clarify that the function will create an image on the screen. We will get into what it means for
a function to actually return a value later.
And now we can add our very, very complex description or purpose statement whatever you prefer.
# draw_square : Number -> Image
# Draws a square of the given size
Now that we have the documentation out of the way, we can move onto the actual body of the function. The function will basically be a copy and paste of our above code with some parts added to tell python that it is a function.
# draw_square : Number -> Image
# Draws a square of the given size
def draw_square(size):
turtle.forward(size)
turtle.left(ANGLE)
turtle.forward(size)
turtle.left(ANGLE)
turtle.forward(size)
turtle.left(ANGLE)
turtle.forward(size)
Here is a breakdown of the parts of a python function
def # Tells python about the function definition
draw_square # The name of our function
(size) # Tells python that our function takes in one input, and we name it size
: # Tells python that everything after this is the content of the function
If we were to take in 2 inputs, we would put a comma in between them in the parenthesis
def two_inputs(a, b):
Note that in the function, we have replaced SIZE
with size
since we are no longer using the
constant size that we defined before and instead are allowing us to specify the size of the square
when we call the function
Python is what is called a "whitespace language" meaning that what the code means is heavily dependant on the whitespace in the code.
Whitespace is a character that you can not see such as space or tab
because of this, the indentation of your code matters. To specify that the code above is part of the function, it needed to be indented one time. If we now wanted to write more code after, but not inside of the function, we would have to un-indent the code which would look like so
# draw_square : Number -> Image
# Draws a square of the given size
def draw_square(size):
turtle.forward(size)
turtle.left(ANGLE)
turtle.forward(size)
turtle.left(ANGLE)
turtle.forward(size)
turtle.left(ANGLE)
turtle.forward(size)
turtle.done() # This code is now not inside of the above function
To use our function that we have defined, we can now call it as such
draw_square(150)
draw_square(300)
turtle.done()
Unlike in scheme where you can call/use a function from anywhere, in python, you must define a function before you are able to use it, much like variables or constants. So the above code would have to appear below the function we have made
Designing a square root function
Now that we have designed a simple function, we get to do another one! yay!
This function we will define will find the square root of an inputted value
Lets walk down the steps of function definitions
- Signature
# square_root : Number -> Number
- Purpose/Description
# Find the square root of a Number
Tests
No tests needed
- Actual Function Body
To make the function, we need to remember how to take a square root. If you remember from math
class, a square root is the same as raising something to the power of 1/2
, we can express that in
python with the following
def square_root(n):
n**(1/2)
If you remember back to the introduction, the **
operator raises the number on the left to the
exponent on the right, so in this case, we are raising n to the power of 1/2
where n is the
inputted value to our function
Unlike scheme, python does not return anything from the function unless you explicitly tell it to
return
the value. That is because in python, you can run multiple lines of code per function where
as scheme had you nest all of the function calls inside each other.
To be able to return the value, we need to add return
before the value we want to return. Below is
the fully complete function
# square_root : Number -> Number
# Find the square root of a Number
def square_root(n):
return n**(1/2) # n^.5 = sqrt(n)
Now we can use this function
square_root(100) # = 10.0
And we can assign the result to a variable as well
x = square_root(25) # = 5.0
But how to we show out this information to us? Im not too sure, but I have heard a bit about
Printing Values to the Console
Python provides a function to us called print that will print out all of its arguments. This function is a bit different than the ones that we have wrote so far in that it can take 0 or 100 inputs. Print will print out all of the inputs given to it. We use it like any other function that we have before
print("hello")
print("hello", 7, False, 32)
print(square_root(100))
Watch out! Although python is very google-able, there are 2 different versions of python: Python 2 and Python 3. There are a few syntax and other changes between these versions, one of these is the layout of the print function. In Python 2, you would write
print "hello"
to print the string hello. In the newer Python 3 that we use, we need to put parenthesis around the inputs to all functions, so we would writeprint("hello")
. If you ever see the old way of printing, look around for a newer source since the source you are using may be outdated, or just replace the old style of prints with the new style.
Returning Nothing
When writing a function, we may want to not return anything from it since we might print to the
console or draw using a turtle. In that case, we need to mark the function as returning (void)
.
We will use a simple function that prints a greeting to the name that is given ie.
print_greeting("Zach")
would print Hello Zach
to the console as an example for this.
The function signature and usage would look as follows
# print_greeting : String -> (void)
# Prints a greeting to the given person
def print_greeting(name):
print("Hello " + name)
print_greeting("Alice") # prints "Hello Alice"
Make note of the un-indentation of the last line, meaning that it is not inside of the function above it.
The main change we have made is the return type being (void)
to tell us that this function returns
nothing.
Since this function returns nothing, we cannot get a value from it/assign it to a variable
greeting = print_greeting("Alice")
print(greeting) # prints "None"
If we instead wanted to be able to use and manipulate the greeting before printing it out, we would
need to make our function return the greeting instead of printing it. Since it now returns the
greeting instead of printing it, we can rename it to get_greeting
.
# get_greeting : String -> String
# Get a greeting to the given person
def get_greeting(name):
return "Hello " + name
get_greeting("Alice") # = "Hello Alice"
Note that the last line does not print anything to the console, and instead just will return the greeting for use to use later in our program. We can use the value like so
greeting = get_greeting("Alice")
print(greeting + "'s dog") # prints "Hello Alice's dog"
Mutation
This is a very common feature that scheme intentionally did not let us use, and that is because it has a lot of quirks, but can also be very useful.
We can define variables like such
x = 7
And in python, we can now change that variable.
x = 7
x = 10
print(x)
So in the console, we will see the number 10, since x
was changed from 7 to 10
Sometimes we don't want to change a variable to an exact value, but instead change it relatively such as adding 5 to it. Python lets us to so in 2 ways. The obvious way would be to do something like
x = x + 3
But python gives us the tool to do it a bit more concisely using
x += 3
Python lets us do this with all of the operators gone over in the introduction, here is an example of a few of them.
x -= 3 # subtracts 3 from the value x
x *= 3 # multiplies 3 to the value x
x /= 3 # divides 3 from the value x
Weirdly, we can use these with strings too
s = "Hello"
s += "!" # Adds a "!" at the end of the string s
s *= 3 # Duplicates the string s 3 times
print(s) # prints "Hello!Hello!Hello!"
Mutation Madness
Say we have this function
# change_number : Number -> (void)
# Add 3 to the given Number
def change_number(x):
x += 3
And this code that deals with the function
x = 10
change_number(x)
print(x)
What do you think this would print?
Click for the answer
This would print out 10.
The x in the function and the x in the code outside of the function are not the same variable. Yes
they have the same name, but the one inside of the function could just as well be named n
or
jeff
.
The variables that are passed to a function cannot be modified by the function. But they can modify the data in the function themselves.
If we wanted to actually give the changed value back to the code that called this function, we would need to return it back out.
# change_number : Number -> Number
# Add 3 to the given Number
def change_number(x):
x += 3
return x
And when we use the function, we would need to do the following:
x = 10
x = change_number(x) # Move the changed value of x into x so we can use it
print(x)
Exercise
Given these functions
def change1(x):
x += 3
return x
def change2(a):
a -= 5
return a
def change3(s):
s *= 2
And this code that uses the above functions
a = 7
b = "hello"
c = 10
b = change1(a)
change3(b)
c = change2(c)
print(a, b, c)
What would be printed to the console
Solution
# Answer: a: 7, b: 10, c: 5
a = 7
b = "hello"
c = 10
b = change1(a) # = a + 3
change3(b) # NO CHANGE
c = change2(c) # = c - 5
# a: UNCHANGED = 7
# b: a + 3 = 10
# c: c - 5 = 5
print(a, b, c)
Too Long; Didn't Read
If you want an abridged version, you can look at just the code that I have gone over here
- Functions are defined with the
def
keyword and look something like
# square_root : Number -> Number
# Find the square root of a Number
def square_root(n):
return n**(1/2) # n^.5 = sqrt(n)
- Similarly to scheme,
CONSTANTS_LOOK_LIKE_THIS
andvariables_look_like_this
the only difference is the underscores_
versus the hyphen-
- Still follow the help-sheet's rules for defining functions, just skip the test step for now
- Variable's values can be changed
x = 7
x = 10
print(x)
- Inside of a function, variables do not modify the variables that were passed in (see Mutation Madness)
Lists
Lists in python are very simple to create. They are specified by a square bracket [
, a comma
separated list of the data you would like, and a closing square bracket ]
. Below are a few
examples of lists.
MY_FIRST_LIST = [0, 1, 2]
MY_SECOND_LIST = ["Joe", "Fred", "Hubert"]
Lists alone are pretty cool, but we sometimes might want to get a single value from a list.
To do so, we need to use some more square brackets. This time, our square brackets go after the name
of the list, and within them is the index
of the value we want.
If I had a list MY_LIST
and wanted to get the 2nd element, I would write MY_LIST[1]
. And yes,
that is not a typo, you use the number 1 to refer to the 2nd element. Why is that? Well it has to do
with a whole lot of how computers work, but the main thing to remember is that 1
is not the first
element, 0
is.
Lists in python are what are called 0 indexed lists, or their first value has the index of 0
Here is another example of getting data from a list
FRUIT_WORDS = ["apple", "bananna", "cherry"]
print(FRUIT_WORDS[0]) # Prints "apple"
print(FRUIT_WORDS[1]) # Prints "bananna"
print(FRUIT_WORDS[2]) # Prints "cherry"
Mutation
Just having a list of items is cool, but what if we want to change what is in the list? Well that is where list mutation comes into play.
There are a few ways we can mutate a list. We can change the value stored in one of the indexes, like so
fruit_words = ["apple", "bananna", "cherry"]
FRUIT_WORDS[1] = "berries" # Changes "bananna" to "berries"
print(FRUIT_WORDS[1]) # Prints "berries"
Note how the list's name is
lower_snake_case
instead ofUPPER_SNAKE_CASE
since it is mutable or not constant
Or, we can add items to the end of the list, with the append
method
my_list = ["a", "b", "c"]
my_list.append("d")
print(my_list) # Prints "['a', 'b', 'c', 'd']"
As well as adding items to lists, we can remove items.
my_list.remove("b")
print(my_list) # Prints "['a', 'c', 'd']"
WATCH OUT
remove
takes in the value to remove, not the index
Length
We all know the size of your list is not what matters, but instead how you use it. Sometimes it is
useful to know its length every once and a while though. Luckily for us, python provides a handy
dandy len
function, the same one we have been using to get the length of a string.
FRUIT_WORDS = ["apple", "bananna", "cherry"]
print(len(FRUIT_WORDS)) # Prints "3"
Looping
Sometimes, just getting one element is not enough for us, and instead we want to do something with or to the whole list. To do so, we can use a loop to go over each element of the list, and do something. The simplest thing to do is to just print out each element in a list.
FRUIT_WORDS = ["apple", "bananna", "cherry"]
for fruit in FRUIT_WORDS:
print(fruit) # Will print each name in the list
We might also want to produce a single value based on all the items in the list, such as a sum. Let's make a function to calculate the sum of a list of numbers. Its signature would look as such:
# sum : ListOfNumbers -> Number
# Sums up all the numbers in a list of numbers
def sum(list_of_numbers):
Since we need a place to store our sum, we will create a variable and call it result
result = 0 # We start with a sum of 0
And then we can loop over every element
for n in list_of_numbers: # Loop over every number `n` in the list
result += n # Add the current number to the sum
And then finally, we can return the result, leaving us with
# sum : ListOfNumbers -> Number
# Sums up all the numbers in a list of numbers
def sum(list_of_numbers):
result = 0 # We start with a sum of 0
for n in list_of_numbers: # Loop over every number `n` in the list
result += n # Add the current number to the sum
# Note the un-indentation, since this return is outside of the for loop
# If the return was inside of the for loop, the function would return the
# sum after only adding the first value, basically returning the first value
return result # Give the result to the caller
If we were to use our function, we could do so as such:
sum([0, 2, 3, 4]) # = 9
sum([6]) # = 6
sum([15, 21, 55]) # = 91
Example: Counting
# count_big_strings : ListOfStrings -> Nat
# Count the number of strings that have at least 10 letters
def count_big_strings(list_of_strings):
count = 0 # We have 0 big strings by default
for s in list_of_strings: # Loop over every string `s` in the list
if len(s) >= 10: # This will check if the condition is true
count += 1 # And run this code if it is true, hence the name
return count # Return the result, outside of the for and if statement
Too Long; Didn't Read
If you want an abridged version, you can look at just the code that I have gone over here
Files
This page is incomplete... If you need the information that would normally be on
this page, please email me at 21kohnenz@wpsraiders.org
Too Long; Didn't Read
If you want an abridged version, you can look at just the code that I have gone over here
Loops
This page is incomplete... If you need the information that would normally be on
this page, please email me at 21kohnenz@wpsraiders.org
Too Long; Didn't Read
If you want an abridged version, you can look at just the code that I have gone over here
User input and randomness
Sometimes we want to be able to read in input from the user, and python makes that nice and easy for us.
input("Enter a number: ") # Prints "Enter a number: " and waits for user input
This page is incomplete... If you need the information that would normally be on
this page, please email me at 21kohnenz@wpsraiders.org
Too Long; Didn't Read
If you want an abridged version, you can look at just the code that I have gone over here
Files
This page is incomplete... If you need the information that would normally be on
this page, please email me at 21kohnenz@wpsraiders.org
Too Long; Didn't Read
If you want an abridged version, you can look at just the code that I have gone over here
ECS Help Sheet
Designing Data | Designing Functions |
---|---|
Data Definition - what kind of data is allowed | Signature - what kind of data goes in and what kind of data comes out? |
Interpretation - what does this data mean? | Purpose - what does this function do? |
Examples (at least 2) | Testing - examples of how the function works (using check-expect) |
Template (see Designing Templates) | Code - using the template |
Designing Templates
- How many cases of data are there? If there is more than one option, you will need a conditional expression (this happens when you have union data)
- How can you tell the cases apart? This will determine the question you ask in each clause of your conditional expression. You can skip this step if you are not working with union data.
- What can you pull out of the data in each case? If it is structured data you must call the selectors here.
- Is the data you pilled out complex with its own data definition? If you are referencing another data definition that you made, you need to call the template for that data type on the selected data.
Designing Programs
- What changes and what stays the same? Things that stay the same are constants and things that change are your WorldState (you may need to design your own data here).
- Which clauses do we need? Your options are:
to-draw
,on-tick
,on-mouse
,on-key
andstop-when
. - Design your main function (the one that calls
big-bang
). This is the only function you cannot write tests for. Make a wishlist of the functions you need to design. - Design the functions on your wishlist. Be sure to follow all the steps of the function design recipe when you do so.