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 as string? or number?
  • 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 an address structure, so we need to use the tools from the address-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))

  • 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

Image 1 Image 2

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 Gmes 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

Jump to TL;DR (summary)

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

Jump to TL;DR (summary)

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 write print("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 and variables_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

Jump to TL;DR (summary)

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 of UPPER_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']"

print can print out whole lists for us which is useful for making sure the list has what we think it has in it

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

Jump to TL;DR (summary)

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

Jump to TL;DR (summary)

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

Jump to TL;DR (summary)

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

Jump to TL;DR (summary)

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 DataDesigning Functions
Data Definition - what kind of data is allowedSignature - 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

  1. 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)
  2. 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.
  3. What can you pull out of the data in each case? If it is structured data you must call the selectors here.
  4. 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

  1. 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).
  2. Which clauses do we need? Your options are: to-draw, on-tick, on-mouse, on-key and stop-when.
  3. 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.
  4. Design the functions on your wishlist. Be sure to follow all the steps of the function design recipe when you do so.