UNB/ CS/ David Bremner/ teaching/ cs2613/ labs/ CS2613 Labs

Rubric

Criteria Excellent (6) Good (5) OK (3-4) Needs improvement (0-2)
Content Good, plus at least one of the answers demonstrates careful thought or genuine insight. Good answers to most or all of the questions from the journal page. Answers to most or all of the questions from the journal page. Answers few or none of the questions from the journal page.
Technical Skills / Presentation

Good, plus use of markdown / scribble to enhance the visual effects, or interesting/useful use of hyperlinks.

No spelling or grammar problems. Writing style is appropriate.

Pushed to git, builds, has reasonable git commit messages, markdown or scribble formatting is not obviously broken.

No spelling mistakes or obvious grammar problems.

Builds, in a sane location, pushed to git. Does not build and or / not pushed to git.

Approximate translation of scores

11 - 12
A+
10
A
8 - 9
B
7
C
6
D
1-5
F

Labs

Lab 1

Before the lab

Before every lab in this course, you will be given tasks to complete. These will generally be easy tasks like watching videos, but you need to complete them in order to keep up with the class.

Command Line Familiarity Check

  1. In an FCS linux lab (remote or locally) log in, and open a terminal.

  2. Make a directory *

  3. Create a file in that directory using one of the available text editors

  4. Now clean up, removing the file and the directory.

If any of this was new to you, then please take the time to go through parts 1 to 5 of the Learning the Shell Tutorial.

Read the course syllabus.

The Course Syllabus is available on line. Please read it, and bring any questions you have about it to the first lab.

Background Reading

For every lab there will be some related reading. I'll point these out as we go through the lab, but I'll also collect them at the start of the lab in case you want to get a head start (or refer back to them later).


About the course

Time
10 minutes
Activity
Q&A, discuss course syllabus.
  1. What's similar to other CS courses you've taken?

  2. What's different?

  3. Key point, how are labs evaluated?

Software Setup

Time
10 minutes
Activity
Group work
Summary
Install software needed to work with frog
  1. Open a terminal.

  2. Install frog and the other tools we need via the following command (throughout the course, $ at the beginning of a line will indicate a shell prompt, you don't need to type it.):

      $ raco pkg install --auto unb-cs2613
    
  3. Install the python library pygments, used by frog for syntax highlighting

     $ python -m ensurepip --upgrade
     $ python -m pip install pygments
    

There are an unfortunate number of warnings here, but we can ignore them for now.

Getting Started with Frog

Time
30 minutes
Activity
Individual work
Summary
Getting started with frog

We'll be using frog frog to keep a journal of what we learn in this course.

  1. Open a terminal.

  2. Make a directory called cs2613 that will keep all of your work (note that case and spaces matter in Linux and this is not the same as CS2613 or CS 2613. For the rest of this course we will assume it is directly under your home directory. The shortcut ~/cs2613 will refer to this directory in the lab texts and in the shell.

  3. Make a directory journal inside ~/cs2613. Inside ~/cs2613/journal, run

     $ raco frog --init
    
  4. Try viewing the newly created blog with

     $ raco frog -bp
    
  5. Start a new blog page for today's lab, and delete the fake entry created by the frog setup command. Note that you may have to refresh the browser after running raco frog -bp.


Setting up a git repo

Time
30 minutes
Activity
Individual work
Summary
This is where we create the git repository used for the rest of the term to hand things in.
  1. Change to ~/cs2613. This directory should have one subdirectory called journal, which has the results of your experiments above with frog.

  2. Create the git repository

     $ git init -b main
    

    Git will reply with something like

     Initialized empty Git repository in /home1/ugrads/$username/cs2613/.git/
    

    You’ve now initialized the working directory — you may notice a new directory created, named ".git". You should mentally replace "$username" with whatever the login name is that you use to log into the FCS linux machines.

  3. Read the git-quickref page, and follow the initial configuration steps there.

    • note that the "--wait" option for gedit is important here.
  4. Next, tell Git to take a snapshot of the contents of all files under the journal, with git add:

    $ git add journal
    
Notes
Many revision control systems provide an add command that tells the system to start tracking changes to a new file. Git’s add command does something simpler and more powerful: git add is used both for new and newly modified files, and in both cases it takes a snapshot of the given files and stages that content in the index, ready for inclusion in the next commit.

This snapshot is now stored in a temporary staging area which Git calls the "index". You can permanently store the contents of the index in the repository with git commit:

   $ git commit

This will open and editor and prompt you for a commit message. Enter one and exit the editor. You’ve now stored the first version of your project in Git. See §5.2 of Pro Git for some hints about writing good commit messages. As mentioned in the class git policy, you will be marked on your commit messages, so you may as well get started with good habits.

Pushing to a central repo

Summary
Learn how to upload your work to a server
Time
20 minutes
Activity
Individual work
Notes
You absolutely have to understand this before continuing in the course, since all marks in the course will be based on work pushed to the coursegit repos.

Since we are using the FCS git repositories there is an existing repository for all students who registered early enough. If it turns out there is no repository for you, you may need to do the last step later.


Before next lab

  • Make sure you can push to coursegit from an FCS linux machine. A good way to test this is to push a (work in progress) journal entry for Lab 1.
  • Test that your push was successful by cloning the repo, per the instructions in faq. After cloning, change directory to journal/ and run raco frog -bp
  • Read about how to write a good commit message in §5.2 of Pro Git
  • Read Section 2 of FICS

Lab 2

Before the lab

  • Make sure you can push to coursegit from an FCS linux machine. A good way to test this is to push a (work in progress) journal entry for Lab 1.
  • Test that your push was successful by cloning the repo, per the instructions in faq. After cloning, change directory to journal/ and run raco frog -bp
  • Read about how to write a good commit message in §5.2 of Pro Git
  • Read Section 2 of FICS

Background


Git Tutorial Continued

Making Changes in git

Time
20 minutes
Activity
Individual work
Summary
Get some practice commiting your changes to git.

Having at look at our test post from Lab 1, we can observe the post template adds a bunch of stuff related to social media. Let's suppose that for our cs2613 journal we want a more minimal look.

Start by finding the right files to edit with

$ git grep disqus

git grep is a very useful (and fast!) tool to find occurrences of strings in your git repository. Notice in the output there are some files created by frog; we will clean those up later. Find the template file (under _src) and edit it to remove the undesired social media links. Check the results with

$ raco frog -bp

You might notice one more link to twitter in a different template file. Feel free to remove that one as well.

Use git add to stage your changes:

$ git add file1 file2 file3

You are now ready to commit. You can see what is about to be committed using git diff with the --cached option:

$ git diff --cached

(Without --cached, git diff will show you any changes that you’ve made but not yet added to the index.) You can also get a brief summary of the situation with git status:

$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   file1
#       modified:   file2
#       modified:   file3
#

It’s a good idea to begin the commit message with a single short (less than 50 character) line summarizing the change, followed by a blank line and then a more thorough description. The text up to the first blank line in a commit message is treated as the commit title, and that title is used throughout Git.

If you need to make any further adjustments, do so now, and then add any newly modified content to the index. Finally, commit your changes with:

$ git commit

This will again prompt you for a message describing the change, and then record a new version of the project.

Alternatively, instead of running git add beforehand, you can use

$ git commit -a

which will automatically notice any modified (but not new) files, add them to the index, and commit, all in one step. Keep in mind that you will be marked on the logical structure of your git commits, so you are better off using git add to explicitely choose what changes to commit.

Cleaning up generated files

Time
15 minutes
Activity
Individual work
Summary
Get some practice commiting your changes to git.

A common phenomenon in software development is the existence of generated files. These files are created by some tool, typically based on some source files. In general it is a bad idea to track generated files in version control because they introduce spurious changes into history. We'll look at this more later, but for now let's try to clean up. We can find out what files are generated .e.g. by consulting the frog docs. Let's first try a shortcut. Run

$ cd ~/cs2613/journal
$ raco frog --clean

To find out what changed, run

$ git diff --stat

All going well, you will see a bunch of deletions. We can tell git to make those deletions permanent in several ways. It turns out that there is a handy option to git add that tells git to stage all of the changes in the working tree. Try to figure out which option it is.

When you are satisfied with the changes, run git commit.

It will turn out that this is not all of the generated files; we can use git rm to clean up further as we find more.

Make sure you run

$ raco frog -bp

To make sure the the blog still works after your deletions.

Viewing project history

Time
15 minutes
Activity
Small group discussion, presenting work to the group, peer feedback
Summary
Reinforce idea of commit message quality
  1. At any point you can view the history of your changes using

     $ git log
    

    Use this command to verify that all the changes you expected to be pushed to the server really were.

    If you also want to see complete diffs at each step, use

     $ git log -p
    

    Often the overview of the changes is useful to get a feel for each step

     $ git log --stat --summary
    
  2. In most projects, you have to share commit messages to (at least) the same people who view your source code. Share your "best commit" message with one or more of your neighbours.

  3. Find something positive to say about the other commit messages you are reading.

  4. Find a constructive improvement with one of the other messages. Don't be mean, people have varying levels of experience with this.


Getting started with racket

Hello Racket World

Time
15 Minutes
Activity
Group walkthrough
#lang htdp/bsl
"hello world"
(* 6 7)

Command Line

DrRacket: A Racket Specific IDE

Racket Expressions.

Time
20 minutes
Activity
Small groups
#lang htdp/bsl
(define y 18)
(define (t1 x)
  (or (= x 0) (< 0 (/ y x))))
(define (t2 x)
  (or (< 0 (/ y x)) (= x 0)))
(define (t3 x)
  (and (= x 0) (< 0 (/ y x))))
(define (t4 x)
  (or (< 0 (/ y x)) (not (= x 0))))

Racket functions

Time
20 minutes
Activity
Individual work
(check-expect (middle-of-three 1 2 3) 2)
(check-expect (middle-of-three 2 1 3) 2)
(check-expect (middle-of-three 1 3 2) 2)

Before Next Lab

Reading

Git Practice: Working with multiple git repos

Time
15-30 minutes
Activity
Individual work, outside scheduled lab time.

Cloning

  1. Open a terminal.

  2. Make a directory lab2-scratch somewhere outside the cs2613 git repository you created earlier.

  3. Now move to the lab2-scratch directory, and make a clone of the central repo.

     $ git clone -b main https://$username@vcs.cs.unb.ca/git/cs2613-$username cs2613-clone
    

    This creates a new directory "cs2613-clone" containing a clone of your repository. Notice that in general this is a good way of checking that your work is properly submitted. The TA and Prof will do exactly this cloning step in order to mark your work. The clone is on an equal footing with the original project, possessing its own copy of the original project’s history.

Sharing changes with a central repo

Optional, but useful if you plan to run git on your own computer

  1. Open a terminal

  2. Navigate to the ~/lab2-scratch/cs2613-clone/journal directory.

  3. create a new blog entry, and commit it.

  4. Push your changes back to the central repository.

     $ git push origin main
    
  5. Change directory to your original directory ~/cs2613. Bring in the changes you made

     $ git pull origin main
    

This merges the changes from the central copy of "main" branch into the current branch. If you made other changes in the meantime, then you may need to manually fix any conflicts.

The "pull" command thus performs two operations: it fetches changes from a remote branch, then merges them into the current branch.

Questions

Here are some questions we will be discussing at the beginning of L03.

  • What is a remote?
  • What is merging?
  • What is a conflict?

Git next steps

Congratulations, you now know enough git to finish this course.

There is lots more to learn, particularly about branching and collaboration. If you want to get a big(ger) picture view, a good place to start is Git Concepts Simplified.

Lab 3

Before the lab

Reading

Git Practice: Working with multiple git repos

Time
15-30 minutes
Activity
Individual work, outside scheduled lab time.

Cloning

  1. Open a terminal.

  2. Make a directory lab2-scratch somewhere outside the cs2613 git repository you created earlier.

  3. Now move to the lab2-scratch directory, and make a clone of the central repo.

     $ git clone -b main https://$username@vcs.cs.unb.ca/git/cs2613-$username cs2613-clone
    

    This creates a new directory "cs2613-clone" containing a clone of your repository. Notice that in general this is a good way of checking that your work is properly submitted. The TA and Prof will do exactly this cloning step in order to mark your work. The clone is on an equal footing with the original project, possessing its own copy of the original project’s history.

Sharing changes with a central repo

Optional, but useful if you plan to run git on your own computer

  1. Open a terminal

  2. Navigate to the ~/lab2-scratch/cs2613-clone/journal directory.

  3. create a new blog entry, and commit it.

  4. Push your changes back to the central repository.

     $ git push origin main
    
  5. Change directory to your original directory ~/cs2613. Bring in the changes you made

     $ git pull origin main
    

This merges the changes from the central copy of "main" branch into the current branch. If you made other changes in the meantime, then you may need to manually fix any conflicts.

The "pull" command thus performs two operations: it fetches changes from a remote branch, then merges them into the current branch.

Questions

Here are some questions we will be discussing at the beginning of L03.

  • What is a remote?
  • What is merging?
  • What is a conflict?

Git next steps

Congratulations, you now know enough git to finish this course.

There is lots more to learn, particularly about branching and collaboration. If you want to get a big(ger) picture view, a good place to start is Git Concepts Simplified.


Git Questions

Time
10 Minutes
Activity
Group discussion

Some questions from before:

Setup

The DrRacket stepper

Time
30 min
Activity
Individual work

Semantics

Time
25 min
Activity
Small Groups
Summary
new evaluation rules for and and or

As you read in FICS unit 3, we can understand evaluation ("running") of Racket programs as a sequence of "reductions" or "substitutions". These rules are similar to the reduction steps in the DrRacket stepper.

The stepper uses the following rules for and and or (notice that these rules enforce short circuit evaluation)

(and true exp2 ...) => (and exp2 ...)
(and false exp2 ...) => false
(or true exp2 ...) => true
(or false exp2 ...) => (or exp2 ...)
(and) => true
(or) => false

Following Exercise 7, write a new set of rules that requires at least two arguments for and and or. The rules are for human consumption; you can write them as comments in DrRacket. You can write "exp1 exp2 ..." to mean at least 2 expressions.

Discuss your answers with a your group, and try a couple evaluation small examples by hand using your rules.

Test Coverage

Time
25 min
Activity
Individual work

Unit testing is an important part of programming, and has inspired something called test driven development.

If you have extra time


Before Next Lab

Reading for next lab

On your own

Time
20 min
Activity
Independent research

See if you can come up with answers to the following questions for next time.

  • The programming languages we will study this term are all dynamically typed. This means that not only the value but also the type of variables can change at runtime. Why does this make testing even more important?

  • What kind of software problems is testing not well suited to find?

  • Why might mutable state (e.g. instance variables in Java) make writing unit tests harder?

Lab 4

Before the lab

Reading for next lab

On your own

Time
20 min
Activity
Independent research

See if you can come up with answers to the following questions for next time.

  • The programming languages we will study this term are all dynamically typed. This means that not only the value but also the type of variables can change at runtime. Why does this make testing even more important?

  • What kind of software problems is testing not well suited to find?

  • Why might mutable state (e.g. instance variables in Java) make writing unit tests harder?


Questions from last time

Time
10 Minutes
Activity
Group discussion

Setup

Simulating Natural Numbers I

Time
30 minutes
Activity
Individual work
Summary
Learn about structures and recursion.

Write the times function from Exercise 11 in FICS. You can (and should) use the following code from the linked discussion

#lang htdp/bsl
(define-struct Z ())
(define-struct S (pred))
(define (pred nat)
  (cond
    [(Z? nat) (error "can't apply pred to Z")]
    [(S? nat) (S-pred nat)]))

(define (plus nat1 nat2)
  (cond
    [(Z? nat1) nat2]
    [(S? nat1) (make-S (plus (S-pred nat1) nat2))]))

Here is the template for structural recursion on (simulated) natural numbers. See the linked text (or the plus function just above) for how to add a second "carried-along" parameter.

(define (my-nat-fn nat)
  (cond
    [(Z? nat) ...]
    [(S? nat) ... (my-nat-fn (S-pred nat)) ...]))

Here are some tests to get you started. As always, try to have complete test coverage.

;; 0 * 0 = 0
(check-expect (times (make-Z) (make-Z)) (make-Z))
;; 0 * 1 = 0
(check-expect (times (make-Z) (make-S (make-Z))) (make-Z))
;; 2 * 1 = 2
(check-expect (times (make-S (make-S (make-Z)))
                       (make-S (make-Z)))
              (make-S (make-S (make-Z))))

You may find it helpful to refer back to your solution from L03.

Simulating Natural Numbers II

Time
30 minutes
Activity
Individual work
Summary
Learn about structures and recursion.

Write the compare function from Exercise 11 in FICS. Your function compare should use the struct definitions Z and S, and pass the following tests

;; 0 = 0
(check-expect (compare (make-Z) (make-Z)) 'equal)
;; 0 < 1
(check-expect (compare (make-Z) (make-S (make-Z))) 'less)
;; 1 > 0
(check-expect (compare (make-S (make-Z)) (make-Z)) 'greater)
;; 2 > 1
(check-expect (compare (make-S (make-S (make-Z)))
                       (make-S (make-Z))) 'greater)

Structural recursion, on numbers

Time
20 minutes
Activity
Individual work
Summary
Learn about structural recursion.

Use structural (note that structural here is only indirectly related to Racket structs) recursion on natural numbers (not the simulated ones from above, but regular Racket numbers like 1, 42, and 1337) to define a function (sum-factors n max-factor) that sums all factors of n (including 1) no larger than max-factor

Recall the template for structural recursion on natural numbers:

(define (my-nat-fn n)
  (cond
    [(zero? n) ...]
    [(positive? n) ... (my-nat-fn (sub1 n)) ...]))

Try to use only one recursive call to sum-factors like in the template. Keep in mind that the n in the template might be a different parameter of your function. You can use the builtin function remainder to test for divisibility.

The following tests should pass

;; 1+2+3 = 6
(check-expect (sum-factors 6 5) 6)
;; 1+2+4+7+14 = 28
(check-expect (sum-factors 28 27) 28)

Before Next Lab

Lab 5 (Cancelled)

Lab 5 was cancelled due to Campus closure. Content was moved to Lab 6.

Lab 6

Before the lab


Setup

Substitute in a list

Time
25 minutes
Activity
Individual work
Summary
Learn about lists and recursion.

Complete FICS Exercise 13.

Note the template for structural recursion.

(define (my-list-fn lst)
  (cond
    [(empty? lst) ...]
    [(cons? lst) ... (first lst) ...
                 ... (my-list-fn (rest lst)) ...]))

Here is a test case based on the linked test

(check-expect
 (substitute 3 "three" (list "four" 3 4 "three" 3))
 (list "four" "three" 4 "three" "three"))

Uniquifying lists

Time
25 minutes
Activity
Individual work
Summary
Learn about lists and recursion.

Complete FICS Exercise 15. Although probably not intended by the original question, use the built-in BSL function member? to simplify your solution. Otherwise use only the constructs specified in Exercise 14, and structural recursion. My solution contains an auxilary function (also using structural recursion) to remove all copies of a particular value from a list. Here is one test case derived from the linked text.

(check-expect
 (unique-right
  (list 1 4 2 1 5 4))
 (list 2 1 5 4))

Add an element to all lists

Time
25 minutes
Activity
Individual work
Summary
Learn about lists and recursion.

Recall the template for structural recursion on lists

(define (my-list-fn lst)
  (cond
    [(empty? lst) ...]
    [(cons? lst) ... (first lst) ...
                 ... (my-list-fn (rest lst)) ...])

Use this template to write a function cons-all that adds a given element to the front of each given sublist. This is related to FICS Exercises 23-25 (but you only need to write cons-all, not do those exercises).

The following test case illustrates the use of cons-all. You will need to use the language #lang htdp/bsl+ or "Beginning Student with List Abbreviations" or replace the use of ' in the following.

(check-expect (cons-all 3 '((2 4) () (5)))
              '((3 2 4) (3) (3 5)))

Before next lab

Lab 7

Before the lab


Questions

Time
5 Minutes
Activity
Group discussion / Announcments

Setup

Structural recursion on two parameters

Time
35 minutes
Activity
Small groups
Summary
Learn about lists and recursion.

Intersection of two ordered lists

Time
35 minutes
Activity
Individual work
Summary
Learn about lists and recursion.

Recall the template for structural recursion on two lists

(define (my-list-fn lst1 lst2)
  (cond
    [(empty? lst1) ... lst2 ...]
    [(empty? lst2) ... lst1 ...]
    [(cons? lst1)
        ... (first lst1) ... (first lst2) ...
        ... (my-list-fn (rest lst1) lst2) ...
        ... (my-list-fn lst1 (rest lst2)) ...
        ... (my-list-fn (rest lst1) (rest lst2)) ...]))

Use this template to complete FICS Exercise 20 You may find it helpful to refer to the function o-union defined in Section 5.4. Note that the fact that the lists are sorted is key an efficient solution here; you do not have to check that property.

Here are some test cases for the function intersection

(check-expect (intersection (list 1 2 3 4 5) (list 2 4 6 7))
              (list 2 4))

(check-expect (intersection (list 2 4 11) (list 1 2 3 4 5))
              (list 2 4))

Generating all subsets of fixed size

Time
35 minutes
Activity
Individual work
Summary
Learn about lists and recursion.

Start with the template from the first exercise and complete FICS Exercise 24. If your template needs updating, compare with cases in the two list template.

Use the built-in function append and the function cons-all you wrote in Lab 5.

Here are some test cases to illustrate the use of the function comb

(check-expect (comb '(a) 1) '((a)))
(check-expect (comb '(1 2) 1) '((1) (2)))
(check-expect (comb '(1 2 3) 2) '((1 2) (1 3) (2 3)))

Before next lab

Lab 8

Before the lab


Questions

Time
5 Minutes
Activity
Group discussion / Announcments

Setup

Introducing foldr

Time
25 minutes
Activity
Small groups
Summary
foldr, functional_abstraction, contracts
(define (Foldr combine base lst)
  (cond
    [(empty? lst) base]
    [else (combine
            (first lst)
            (Foldr combine base (rest lst)))]))

Guess the function

Time
25 minutes
Activity
Individual work
Summary
foldr, functional_abstraction, lists

This part of the lab is based on FICS Exercise 29

Fill in the two instances of ... in the following template with a builtin function so that the tests pass. Try some other values for mylist, mylist1, mylist2 to help you guess.

#lang htdp/isl+
(define mylist '(1 2 buckle my shoe))
(check-expect (foldr cons empty mylist) (... mylist))
(define mylist1 '(3 4 5))
(define mylist2 '(1 2))
(check-expect (foldr cons mylist1 mylist2) (... mylist2 mylist1))

Filter in terms of foldr

Time
40 minutes
Activity
Individual work
Summary
foldr, functional_abstraction, lists

On your own, complete FICS Exercise 28

(check-expect (Filter (lambda (x) #f) '(1 2 3)) '())

(check-expect (Filter odd? '(0 1 2 3 4 5 6 7 8 9))
              (list 1 3 5 7 9))

Before next lab

Lab 9

Before the Lab

Background

Getting started

Questions

Time
5 Minutes
Activity
Group discussion / Announcments

Running JavaScript

Time
15 minutes
Activity
Demo

In a file

    console.log("Hello world");

Running a script

In a Browser

Note
Examples involving "prompt" and "alert" will only work in a browser.

In the REPL

JavaScript equality and type coercion

Time
10 Minutes
Activity
Demo

From Eloquent JavaScript Chapter 1, and the WAT talk by Gary Bernhardt, we know that type coercion is an important part of evaluating JS expressions. This is sometimes useful

> 42 + 'is a big number'

But just as often a source of errors and confusion.

> "" + 1
> x=""
> x++

One operator where type coercion can be particularly surprising is the standard equality test ==. Not only does type coercion apply:

> "" == 0
> false == 0
> "" == 0

but special rules apply to "undefined" and "null"

> false == undefined
> undefined == null
> undefined == undefined

even though they would normally be considered falsy (considered false in boolean contexts).

   > if (undefined) { console.log("truthy") } else { console.log("falsey") }

NaN is another falsy value not == to the other falsy values, not even itself:

   > NaN == undefined
   > NaN == NaN

To avoid this twisty maze of "helpful" type coercion, you can use the "strict equality" checker ===

   > "" === 0
   > false === 0
   > "" === 0
   > false === undefined
   > undefined === null
   > undefined === undefined

Javascript functions

Time
20 minutes
Activity
Individual

Reference for this section is JavaScript functions Like Racket, JavaScript has two different ways of defining functions. The first way is by assigning an anonymous function to a variable.

    (define square (lambda (x) (* x x)))
    let square = x => x*x;
    let square2 = function (x) {return x*x};

The more compact way of defining functions in both Racket and JavaScript combines the binding and creation of a function/lambda

    (define (square x) (* x x))
    function square(x) { return x*x }
const plus = (a,b) => {
    for (let i=0; i < a; i++){
        b++;
    }
    return b;
}

const mult = function (a,b) {
    sum=0;



    return sum;
}

Node test runner

Time
10 Minutes
Activity
Demo

In order to provide similar functionality to check-expect in racket, we will use the nodejs test framework.

In order to use the test runner, we need to add two lines to the beginning of our javascript files.

const test=require("node:test");
const assert=require("assert");

We'll talk more about both const and require later.

A test is considered to pass if it does not throw an exception. assert provides several functions useful for this kind of test. We will concentrate on assert.strictEqual for now, which corresponds to the === test discussed above.

Here is a complete example with one passing test and one failing one

const test=require("node:test");
const assert=require("assert");

function add1(n) {
    return n+1;
}

test('one plus one is two', (t)=>assert.strictEqual(add1(1),2));
test('one plus one is three', (t)=>assert.strictEqual(add1(1),3));

Test coverage

We will use an experimental feature of node v20 in order to make sure we have test coverage (analogous to how we set up DrRacket for visual test coverage).

Getting a coverage report

Time
15 minutes
Activity
Group activity
ℹ start of coverage report
ℹ ---------------------------------------------------------
ℹ file      | line % | branch % | funcs % | uncovered lin…
ℹ ---------------------------------------------------------
ℹ sample.js | 100.00 |   100.00 |  100.00 |
ℹ ---------------------------------------------------------
ℹ all files | 100.00 |   100.00 |  100.00 |
ℹ ---------------------------------------------------------
ℹ end of coverage report

Writing tests

Time
25 minutes
Activity
Individual

Before the next lab

Lab 10

Before the lab

Questions

Time
5 Minutes
Activity
Group discussion / Announcments

Deep Comparison

Time
40 minutes
Activity
Exercise from book

Follow the Deep Comparison Exercise to write a function deepEqual that passes the following tests. assert uses something very similar for the deepStrictEqual test used below. Your function should pass the following tests.

let obj = {here: {is: "an"}, object: 2};
test("self", (t)=> assert.strictEqual(deepEqual(obj,obj),true));
test("null", (t)=> assert.strictEqual(deepEqual(null,null),true));
test("!null", (t)=> assert.strictEqual(deepEqual(null,obj),false));
test("different", (t)=>
    assert.strictEqual(deepEqual(obj, {here: 1, object: 2}),false));
test("equivalent", (t)=>
    assert.strictEqual(deepEqual(obj, {here: {is: "an"}, object: 2}),true));

Remember from L09 that you need the following lines to use the test framework

const test=require("node:test");
const assert=require("assert");

JavaScript Arrays

JavaScript supports arrays. They are actually implemented as objects, which leads to some slightly surprising behaviour:

> x=[]
> x[10]=1
> x
> x["bob"] = 2
> x

Generating arrays

Time
10 minutes
Activity
Group programming / demo

Follow the (first part of the) Sum of a Range Exercise to write a range function that passes the following tests.

test("empty", (t)=>assert.deepStrictEqual(range(2,1),[]));
test("single", (t)=> assert.deepStrictEqual(range(2,2),[2]));
test("multiple", (t)=>
    assert.deepStrictEqual(range(42,50),[42,43,44,45,46,47,48,49,50]));

Arrays Continued

Time
20 minutes
Activity
Exercise from book

Follow the (second part of the) Sum of a Range Exercise to write a sum function that passes the following tests.

test("empty", (t)=> assert.strictEqual(sum([]),0));
test("single", (t)=> assert.strictEqual(sum([2]),2));
test("multiple", (t)=> assert.strictEqual(sum(range(1,10)),55));

Adding step argument to range.

Time
20 minutes
Activity
Individual

Add an optional step argument to the range function so that the following tests pass. The original tests should keep passsing, that's a big part of the point of having unit tests.

test("step 2", (t)=> assert.deepStrictEqual(range(1,10,2),[1,3,5,7,9]));
test("step -1", (t)=> assert.deepStrictEqual(range(5,2,-1),[5,4,3,2]));

Before next lab

Lab 11

Instructions

  1. The real quiz is individual, so do this one on your own.

  2. Save your answers to a directory labs/L11/ in your cs2613 git repository. Commit and push to https://vcs.cs.unb.ca before 10:20AM. Try to complete the practice quiz in under 1h, to get practice working under time pressure.

  3. This practice quiz is open book. You can use

    1. The local mirror of A Functional Introduction Computer Science (FICS), linked from multiple labs or directly from

      https://www.cs.unb.ca/~bremner/teaching/cs2613/books/FICS/

    2. The local Racket documentation (run raco docs, or the drracket help menu).

    3. Other parts of the course web site

    4. Your own notes and work from previous labs and assignments.

Questions

Write a function steamroller that returns a list of the numbers, symbols and strings in a given list in order. In particular your function should pass the following tests.

(check-expect (steamroller empty) empty)
(check-expect (steamroller '(() ())) empty)
(check-expect (steamroller '((a) (b))) '(a b))
(check-expect (steamroller '(((a 1) (b 1)) ((c 2) (d 2) )))
              '(a 1 b 1 c 2 d 2))

Notice the behaviour in the last test is to completely flatten the list. Start with the following template for structural recursion on lists

(define (steamroller lst)
  (cond
    [(empty? lst) ...]
    [(cons? lst) ... (first lst) ...
                 ... (steamroller (rest lst)) ...]))

Good solution

To get 7 marks (roughly a “B”), your solution must

Full marks

For full marks

Lab 12 (racket quiz)
No Lab, University closed

There is no lab on February 17, 2025.

Lab 13

Before the lab

A Vector class

Time
25 minutes
Activity
Exercise from book.

Do the Vector Type exercise.

test("addition", (t) =>
    assert.deepStrictEqual(new Vec(1, 2).plus(new Vec(2, 3)),
                           new Vec(3,5)));
test("subtraction", (t)=>
    assert.deepStrictEqual(new Vec(1, 2).minus(new Vec(2, 3)),
                           new Vec(-1,-1)));
test("length", (t) =>
    assert.strictEqual(new Vec(3, 4).length,5));

A Set ("Group") class

Time
30 minutes
Activity
Exercise from book.

Do the Group exercise. Instead of the suggested array, use an object to store the elements of the "Group". The simplest way to implement from is to loop over the iterable argument and repeatedly call add.

const test=require("node:test");
const assert=require("assert");
class Group {
    constructor() {
        this.elements={}
    }
    add(element) {
        this.elements[element]=true;
    }
    has(element) {
    }
    delete(element) {
    }
    static from(iterable) {
    }
}
// move this inside tests to avoid race conditions
let group = Group.from([10, 20]);
test("has yes",(t)=>assert.strictEqual(group.has(10),true));
test("has no",(t)=>assert.strictEqual(group.has(30),false));
test("add existing is not error", (t)=>group.add(10));
test("delete existing", (t) => {
    group.delete(10);
    assert.strictEqual(group.has(10),false);
});

Symbols

Time
10 minutes
Activity
Demo

You might remember symbols from Racket. It turns out JavaScript has something similar (but more confusing). Just like strings, Symbols can be used as keys (e.g. method names for objects). Here we rely on a predefined value to be able to call a method. In the last part of the lab, you will use the global Symbol.iterator as a method name.

const test=require("node:test");
const assert=require("assert");

let sym=Symbol("my cool symbol name");

test("uniqueness",
     (t)=>assert.notEqual(sym, Symbol("my cool symbol name")));

class Thing {
    constructor() {
        this.count=0;
    }
    // note the syntax to define indirect method names
    [sym]() {
        return this.count++;
    }
}

test("call",
     (t)=> {
         let x = new Thing();
         assert.strictEqual(x[sym](),0);
         assert.strictEqual(x[sym](),1);
     });

Iterable class

Time
40 minutes
Activity
Exercise from book.

Do the Iterable Group exercise.

Start by adding the following two methods to your Group class.

    // Utility function for iterator class
    keys() {
        return Object.keys(this.elements);
    }
    // Mark this class as iterable.
    [Symbol.iterator]() {
        return new GroupIterator(this);
    }

Next create a GroupIterator class.

class GroupIterator {
    constructor(group) {
        this.index=0;
        this.group=group;
    }
    next() {
    }
}

Refer to the MatrixIterator class for inspiration, but note that you should always return an object with a value property. When you are done, the following test should pass.

test("iterate", (t) => {
    let vals=[];
    for (let value of Group.from(["a", "b", "c"])) {
        vals.push(value+value);
    }
    assert.deepStrictEqual(vals,["aa","bb","cc"]);
});

Before next lab

Read the followings sections from Chapter 11 of Eloquent Javascript:

From the MDN JavaScript Guide, read Using Promises

Lab 14

Before the lab

Read the followings sections from Chapter 11 of Eloquent Javascript:

From the MDN JavaScript Guide, read Using Promises

Background

Callback Chaining

Time
25 minutes
Activity
Code puzzle.

The recommended way to get maximimum performance from the node.js filesystem API is to use callbacks, i.e. functions to call when the I/O operation completes.

Replace the ALL-CAPITALS words in the following code sample with defined identifiers to make this callback chaining example work.

const fs=require("node:fs");

function log(string, next) {
    let date = new Date();
    function finish(err) {
        if (err) throw err;
        console.timeEnd("write");
        CALLBACK();
    }
    function write(err, fd) {
        if (err) throw err;
        fs.write(VALUE, date.toISOString() + ": " + string, CALLBACK);
    }
    console.time("write");
    fs.open("log.txt", "a", CALLBACK)
}

console.time("log");
log("first\n",
    () => {
        log("second\n",
            () => console.log("done"));
    });
console.timeEnd("log");

When you get the code working, the file log.txt should contain lines like the following (the exact date and time may differ):

2024-10-25T17:31:56.626Z: first
2024-10-25T17:31:56.629Z: second

Questions for your journal:

Promises

Time
25 minutes
Activity
Code puzzle, reading documentation

In the Promises API file system methods return a Promise object that automates the chaining of callbacks. These Promise objects can be chained together using then. Fill in the appropriate API calls (they should be very similar to the first part, without the explicit callbacks).

const fs=require("node:fs/promises");

function log(string) {
    let date = new Date();
    return new Promise(next=> {
        console.time("write");
        API_CALL
            .then(fd=>API_CALL)
            .then(()=>console.timeEnd("write"))
            .then(CALLBACK)
            .catch((err) => console.log(err));
    });
}

console.time("log");
log("first\n")
    .then(()=>log("second\n"))
    .catch((err) => console.log(err));
console.timeEnd("log");

Questions for your journal:


Async Functions

Time
25 minutes
Activity
Code puzzle, reading book

Async Functions provide some nice features for programming with Promises, essentially eliminating some of the boilerplate from our previous example. They also introduce one very important keyword.

Replace the ALL-CAPITALS parts of the following program to duplicate the functionality of the previous versions.

const fs=require("node:fs/promises");

async function log(string) {
    console.time("write")
    const fd = MAGIC_KEYWORD API_CALL()
    let date = new Date();

    const promise=API_CALL();
    console.timeEnd("write");
    return promise;
}

console.time("log");
log("first\n")
    .then(()=>log("second\n"))
    .catch((err) => console.log(err));
console.timeEnd("log");

Before Next Lab

Lab 15

Before the Lab

Questions

Time
5 Minutes
Activity
Group discussion

Getting Started

Mortgage Calculator

Time
20 Minutes
Activity
Modify given program

Do exercises 1.7 and 1.8 from Python Numbers

Introduction to Python strings

Time
20 Minutes
Activity
Experiment in Python REPL

Do exercises 1.13, 1.14, 1.15 from Python Strings


Pytest

Time
10 minutes
Activity
Demo
symbols = 'HPQ,AAPL,IBM,MSFT,YHOO,DOA,GOOG'
symlist = symbols.split(',')

def test_lookup0():
    assert symlist[0] == 'HPQ'

def test_lookup1():
    assert symlist[1] == 'AAPL'
[student@id414m22 L14]$ pytest listex.py
=================== test session starts ===================
platform linux -- Python 3.9.18, pytest-7.4.3, pluggy-1.3.0
rootdir: /home1/ugrad/student/cs2613/labs/L14
plugins: pylama-8.4.1, cov-4.1.0
collected 2 items

listex.py ..                                        [100%]

==================== 2 passed in 0.02s ====================

Lists and Pytest

Time
20 minutes
Activity
Individual programming from template

Functions and coverage

Time
20 minutes
Activity
Individual programming, modify previous solution

We have already been using python functions for pytest, without really thinking about how they work. In Part 1.7 of Practical Python, functions are explained.

def sumcount(n):
    '''
    Returns the sum of the first n integers
    '''
    total = 0
    while n > 0:
        total += n
        n -= 1
    return total

Before next lab

No Labs, Reading Week

There is no labs the week of March 3, 2025.

Lab 16

Before the Lab


Getting Started

Files

Time
35 minutes
Activity
Individual programming, synthesis

Data types

Time
25 minutes
Activity
Convert REPL session to script with tests
def test_cost():
    d = parse_row(row)
    cost = compute_cost (d)
    assert cost == pytest.approx(3220.000,abs=0.00000001)

def test_d():
    d = parse_row(row)
    update_dict(d)
    assert d == {'name': 'AA', 'shares': 75, 'price':32.2, 'date': (6, 11, 2007),
                 'account': 12345}

Containers: read_portfolio

Time
25 minutes
Activity
Write function based on template
def test_read():
    portfolio = read_portfolio('Data/portfolio.csv')
    assert portfolio == \
        [('AA', 100, 32.2), ('IBM', 50, 91.1),
         ('CAT', 150, 83.44), ('MSFT', 200, 51.23),
         ('GE', 95, 40.37), ('MSFT', 50, 65.1), ('IBM', 100, 70.44)]

def test_total():
    portfolio = read_portfolio('Data/portfolio.csv')
    total = 0.0
    for name, shares, price in portfolio:
        total += shares*price
    assert total == pytest.approx(44671.15,abs=0.001)
def test_read():
    portfolio = read_portfolio('Data/portfolio.csv')
    assert portfolio == \
        [{'name': 'AA', 'shares': 100, 'price': 32.2}, {'name': 'IBM', 'shares': 50, 'price': 91.1},
         {'name': 'CAT', 'shares': 150, 'price': 83.44}, {'name': 'MSFT', 'shares': 200, 'price': 51.23},
         {'name': 'GE', 'shares': 95, 'price': 40.37}, {'name': 'MSFT', 'shares': 50, 'price': 65.1},
         {'name': 'IBM', 'shares': 100, 'price': 70.44}]

def test_total():
    portfolio = read_portfolio('Data/portfolio.csv')

    total = 0.0
    for s in portfolio:
        total += s['shares']*s['price']
    assert total == pytest.approx(44671.15,abs=0.001)

Sequences

Time
25 minutes
Activity
Convert REPL session to tests
def test_items():
    assert prices.items() == \
        [('GOOG', 490.1), ('AA', 23.45), ('IBM', 91.1), ('MSFT', 34.23)]
def test_zip():
    assert pricelist == \
        [(490.1, 'GOOG'), (23.45, 'AA'), (91.1, 'IBM'), (34.23, 'MSFT')]

def test_min_max():
    assert min(pricelist) == (23.45, 'AA')
    assert max(pricelist) == (490.1, 'GOOG')

List Comprehensions

Time
25 minutes
Activity
Write tests based on REPL session
import pytest
from report2 import read_portfolio
prices = {
        'GOOG' : 490.1,
        'AA' : 23.45,
        'CAT': 35.46,
        'IBM' : 91.1,
        'MSFT' : 34.23,
        'GE': 13.48,
    }

Note that the value test will need to be adjusted for a value of approximately 31167.10, since our price list is different from the one used in the book.


Before next lab

Lab 17

Before the Lab


Getting Started

Higher order functions

Time
20 minutes
Activity
Assemble given pieces
def test_portfolio():
    portfolio = parse_csv('Data/portfolio.csv', types=[str, int, float])
    assert portfolio == [{'price': 32.2, 'name': 'AA', 'shares': 100},
                         {'price': 91.1, 'name': 'IBM', 'shares': 50},
                         {'price': 83.44, 'name': 'CAT', 'shares': 150},
                         {'price': 51.23, 'name': 'MSFT', 'shares': 200},
                         {'price': 40.37, 'name': 'GE', 'shares': 95},
                         {'price': 65.1, 'name': 'MSFT', 'shares': 50},
                         {'price': 70.44, 'name': 'IBM', 'shares': 100}]

def test_shares():
    shares_held = parse_csv('Data/portfolio.csv', select=['name', 'shares'], types=[str, int])
    assert shares_held == [{'name': 'AA', 'shares': 100}, {'name': 'IBM', 'shares': 50},
                           {'name': 'CAT', 'shares': 150}, {'name': 'MSFT', 'shares': 200},
                           {'name': 'GE', 'shares': 95}, {'name': 'MSFT', 'shares': 50},
                           {'name': 'IBM', 'shares': 100}]

Refactoring a function

Time
30 minutes
Activity
Refactor function to add feature
def test_tuple():
    prices = parse_csv('Data/prices.csv', types=[str,float], has_headers=False)
    assert prices == [('AA', 9.22), ('AXP', 24.85), ('BA', 44.85), ('BAC', 11.27),
                      ('C', 3.72), ('CAT', 35.46), ('CVX', 66.67), ('DD', 28.47),
                      ('DIS', 24.22), ('GE', 13.48), ('GM', 0.75), ('HD', 23.16),
                      ('HPQ', 34.35), ('IBM', 106.28), ('INTC', 15.72), ('JNJ', 55.16),
                      ('JPM', 36.9), ('KFT', 26.11), ('KO', 49.16), ('MCD', 58.99),
                      ('MMM', 57.1), ('MRK', 27.58), ('MSFT', 20.89), ('PFE', 15.19),
                      ('PG', 51.94), ('T', 24.79), ('UTX', 52.61), ('VZ', 29.26),
                      ('WMT', 49.74), ('XOM', 69.35)]

JavaScript Quiz

The second half of the lab will be a quiz on JavaScript

Before next lab

Lab 18

Before the Lab

Getting started


Methods

Time
25 minutes
Activity
Write accessor and mutator methods.

def test_cost2():
    s = Stock('GOOG', 100, 490.10)
    assert s.cost() == pytest.approx(49010.0,0.001)

def test_sell():
    s = Stock('GOOG', 100, 490.10)
    s.sell(25)
    assert s.shares == 75
    assert s.cost() == pytest.approx(36757.5, 0.001)

Special Methods

Time
25 minutes
Activity
Code to test, learn about "dunder" methods.
def test_repr():
    goog = Stock('GOOG', 100, 490.1)
    assert repr(goog) == "Stock('GOOG', 100, 490.1)"

Static methods

Time
25 minutes
Activity
Transform code, use template for static methods
    @staticmethod
    def read_portfolio(filename):
        # code from 4.3 goes here

Your completed static method should pass

def test_read_portfolio():
    portfolio = Stock.read_portfolio('Data/portfolio.csv')
    assert repr(portfolio[0:3]) == \
        "[Stock('AA', 100, 32.2), Stock('IBM', 50, 91.1), Stock('CAT', 150, 83.44)]"

Inheritance

Time
25 minutes
Activity
Refactor given code, work with class hierarchy

Start with following class hierarchy based on Exercises 4.5 and 4.6

class TableFormatter:
    def headings(self, headers):
        '''
        Emit the table headings.
        '''
        raise NotImplementedError()

    def row(self, rowdata):
        '''
        Emit a single row of table data.
        '''
        raise NotImplementedError()

class TextTableFormatter(TableFormatter):
    '''
    Emit a table in plain-text format
    '''
    def headings(self, headers):
        
        output = ''
        for h in headers:
            output += f'{h:>10s} '
        output+='\n'
        output+=(('-'*10 + ' ')*len(headers))
        output += '\n'
        return output
    
    def row(self, rowdata):
        output = ''
        for d in rowdata:
            output+=f'{d:>10s} '
        output += '\n'
        return output

def test_text_2():
    portfolio=stock.Stock.read_portfolio('Data/portfolio.csv')
    formatter= TextTableFormatter()
    output= formatter.headings(['Name','Shares','Price', 'Cost'])
    for obj in portfolio[0:3]:
        output +=formatter.row([obj.name,f'{obj.shares}',
                                f'{obj.price:0.2f}',f'{obj.cost():0.2f}'])

    assert '\n' + output == '''
      Name     Shares      Price       Cost 
---------- ---------- ---------- ---------- 
        AA        100      32.20    3220.00 
       IBM         50      91.10    4555.00 
       CAT        150      83.44   12516.00 
'''
 
def test_string_1():
    portfolio=stock.Stock.read_portfolio('Data/portfolio.csv')
    formatter= StockTableFormatter()
    output= formatter.headings(['Name','Shares','Price', 'Cost'])
    for obj in portfolio[0:3]:
        output +=formatter.row(obj)

    assert '\n' + output == '''
      Name     Shares      Price       Cost 
---------- ---------- ---------- ---------- 
        AA        100      32.20    3220.00 
       IBM         50      91.10    4555.00 
       CAT        150      83.44   12516.00 
'''

Before next lab

Lab 19

Before the lab

Getting started

Generators and iterator classes

Time
25 minutes
Activity
individual

Consider the countdown generator from Section 6.2

def countdown(n):
    print('Counting down from', n)
    while n > 0:
        yield n
        n -= 1

Referring to Section 6.1 an equivalent iterator class. Here we will implement the entire protocol with one class, but multiple classes like we did for JavaScript is also reasonable. Modify only the __next__ method of the following skeleton class so that given tests pass.

def countdown(n):
    yield 'starting'
    while n > 0:
        yield n
        n -= 1

def test_generator():
    counter = countdown(3)
    assert [ t for t in counter ] == \
        ['starting', 3,  2,  1]

class Counter:
    "Iterator class simulating countdown"
    def __init__(self,n):
        self.n = n
        self.first = True

    def __iter__(self):
        return self
    
    def __next__(self):
        # insert code here
        self.n -= 1
        return self.n+1

def test_class():
    counter = Counter(3)
    assert [ t for t in counter ] == \
        ['starting', 3,  2,  1]

What is __iter__ for?

Time
25 minutes
Activity
individual

Update the __iter__ method of your previous solution so that the following additional test passes

def test_twice():
    counter = Counter(3)
    assert [ t for t in counter ] == \
        ['starting', 3,  2,  1]
    assert [ t for t in counter ] == \
        ['starting', 3,  2,  1]

Using Generators I

Time
25 minutes
Activity
individual

Do Exercises 6.5 and 6.6.

Note that 6.5 just asks you to run some code, but you do have to be in the correct directory when running it.

For 6.6, you essentially need to split the given code so that the first part is the new generator follow.

Using Generators II

Time
25 minutes
Activity
individual

Start with the following stock related classes:

portfolio.py

fileparse.py

tableformat.py

stock.py

report.py

Complete Exercise 6.7. I suggest you make a new file follow2.py so that you preserve solutions for 6.6 and 6.7.

Lab 20

Before the lab

Linear Algebra

One of the main features of Octave we will discuss is vectorization. To understand it, we need some background material on Linear Algebra. If you've taken a linear algebra course recently, this should be easy, otherwise you will probably need to review

Background

We will mainly rely on the Octave Interpreter Reference. A more tutorial style guide is the Gnu Octave Beginner's Guide, which is available in ebook form from the UNB library.

We'll refer to the online text by Robert Beezer for linear algebra background. We can happily ignore the stuff about complex numbers.


Running Octave

Time
5 minutes
Activity
Demo / discussion

Recursive Fibonacci

Time
10 minutes
Activity
Demo/Group programming.

Here is a JavaScript recursive function for Fibonacci:

function fib(n) {
    if (n<=0)
        return 0;
    if (n<=2)
        return 1;
    else
        return fib(n-1)+fib(n-2);
}

Let's translate this line by line into an Octave function.

Save the following in ~/cs2613/labs/L20/recfib.m; the name of the file must match the name of the function.

function ret = recfib(n)
  if (n <= 0)
    ret = 0;
  elseif (n <= 2)
    ret = 1;
  else
    ret = recfib(n-1) + recfib(n-2);
  endif
endfunction

Like the other programming languages we covered this term, there is a built in unit-test facility that we will use. Add the following to your function

%!assert (recfib(0) == 0);
%!assert (recfib(1) == 1);
%!assert (recfib(2) == 1);
%!assert (recfib(3) == 2);
%!assert (recfib(4) == 3);
%!assert (recfib(5) == 5);

Questions for your journal

Table based Fibonacci

Time
25 minutes
Activity
Programming puzzle

A powerful technique for making recursive A more problem specific approach (sometimes called dynamic programming) is to fill in values in a table.

Save the following in ~/cs2613/labs/L20/tabfib.m. Complete the missing line by comparing with the recursive version, and thinking about the array indexing.

function ret = tabfib(n)
  table = [0,1];
  for i = 3:(n+1)
    table(i)=
  endfor
  ret = table(n+1);
endfunction

%!assert (tabfib(0) == 0);
%!assert (tabfib(1) == 1);
%!assert (tabfib(2) == 1);
%!assert (tabfib(3) == 2);
%!assert (tabfib(4) == 3);
%!assert (tabfib(5) == 5);

Questions for your journal

Performance comparison

Time
10 minutes
Activity
Demo / discussion

Let's measure how much of a speedup we get by using a table.

Of course, the first rule of performance tuning is to carefully test any proposed improvement. The following code gives an extensible way to run simple timing tests, in a manner analogous to the Python timeit method, whose name it borrows.

# Based on an example from the Julia microbenchmark suite.

function timeit(func, argument, reps)
    times = zeros(reps, 1);

    for i=1:reps
      tic(); func(argument); times(i) = toc();
    end

    times = sort(times);
    fprintf ('%s\tmedian=%.3fms mean=%.3fms total=%.3fms\n',func2str(func), median(times)*1000,
             mean(times)*1000, sum(times)*1000);
endfunction

We can either use timeit from the octave command line, or build a little utility function like

function bench
  timeit(@recfib, 25, 10)
  timeit(@tabfib, 25, 10)
endfunction

Questions for your journal


Using a constant amount of storage.

Time
25 minutes
Activity
Programming puzzle

As a general principle we may want to reduce the amount of storage used so that it doesn't depend on the input. This way we are not vulnerable to e.g. a bad input crashing our interpreter by running out of memory. We can improve tabfib by observing that we never look more than 2 back in the table. Fill in each blank in the following with one variable (remember, ret is a variable too).

function ret = abfib(n)
  a = 0;
  b = 1;
  for i=0:n
    ret = _ ;
    a = _ ;
    b = _  + b ;
  endfor
endfunction

%!assert (abfib(0) == 0);
%!assert (abfib(1) == 1);
%!assert (abfib(2) == 1);
%!assert (abfib(3) == 2);
%!assert (abfib(4) == 3);
%!assert (abfib(5) == 5);
function bench2
  timeit(@tabfib, 42, 10000)
  timeit(@abfib, 42, 10000)
endfunction

Fibonaccci as matrix multiplication

Time
25 minutes
Activity
Individual

The following is a well known identity about the Fibonacci numbers F(i).

[ 1, 1;
  1, 0 ]^n = [ F(n+1), F(n);
               F(n),   F(n-1) ]

This can be proven by induction; the key step is to consider the matrix product

 [ F(n+1), F(n);   F(n),   F(n-1) ] * [ 1, 1; 1, 0 ]

Since matrix exponentiation is built-in to octave, this is particularly easy to implement the formula above in octave

Save the following as ~/cs2613/labs/L20/matfib.m, fill in the two matrix operations needed to complete the algorithm

function ret = matfib(n)
  A = [1,1; 1,0];


endfunction

%!assert (matfib(0) == 0);
%!assert (matfib(1) == 1);
%!assert (matfib(2) == 1);
%!assert (matfib(3) == 2);
%!assert (matfib(4) == 3);
%!assert (matfib(5) == 5);
%!assert (matfib(6) == 8);
%!assert (matfib(25) == 75025);

We can expect the second Fibonacci implementation to be faster for two distinct reasons

function bench3
  timeit(@tabfib, 42, 10000)
  timeit(@matfib, 42, 10000)
endfunction

Before next lab

Lab 21

Before the lab


Broadcasting

Time
25 minutes
Activity
Demo/Discussion

In Octave we can multiply every element of a matrix by a scalar using the .* operator

A=[1,2,3;
   4,5,6];
B=A.*2

In general .* supports any two arguments of the same size.

C=A .* [2,2,2; 2,2,2]

It turns out these are actually the same operation, since Octave converts the first into the second via broadcasting

Quoting from the Octave docs, for element-wise binary operators and functions

The rule is that corresponding array dimensions must either be equal, or one of them must be 1.

In the case where one if the dimensions is 1, the smaller matrix is tiled to match the dimensions of the larger matrix.

Here's another example you can try.

x = [1 2 3;
     4 5 6;
     7 8 9];

y = [10 20 30];

x + y

Reshaping arrays

One potentially surprising aspect of Octave arrays is that the number of dimensions is independent from the number of elements. We can add as many dimensions as we like, as long as the only possible index in those dimensions is 1. This can be particularly useful when trying to broadcast with higher dimensional arrays.

     ones(3,3,3) .* reshape([1,2,3],[1,1,3])
     ones(3,3,3) .* reshape([1,2,3],[1,3,1])

Scaling layers of arrays

Time
25 minutes
Activity
Individual

Complete the following function. You may want to copy the definitions of A and B into the REPL to understand the use of cat.

## usage: scale_layers(array, weights)
##
## multiply each layer of a 3D array by the corresponding weight
function out = scale_layers(array, weights)
  out =
endfunction

%!test
%! onez = ones(3,3);
%! A=cat(3,onez, 2*onez, 3*onez);
%! B=cat(3,onez, 6*onez, 15*onez);
%! assert(scale_layers(A,[1;3;5]),B)

Scaling a colour channel

Save the image above left as ~/cs2613/labs/L21/paris.jpg (make sure you get the full resolution image, and not the thumbnail).

Run the following demo code; you can change the weight vector for different colourization.

paris=imread("paris.jpg");
sepia=scale_layers(paris,[0.9,0.62,0.34]);
imshow(sepia);

You should get something like the following


The second half of the lab will be a quiz on Python.


Before next lab

Lab 22

Before the lab


Converting to monochrome

Time
25 minutes
Activity
Individual

Either for creative reasons, or as part of a more complex image processing task, it is common to convert images to monochrome.

Save the following image as ~/cs2613/labs/L21/owl.jpg (make sure you get the full resolution image, and not the thumbnail).

Complete the following function using scale layers

function out = monochrome(in, weights=[0.21,0.72,0.07])
  out =
endfunction

Run the monochrome function on owl.jpg, you should get something like the following.

Try a few different values of weight vectors, see if you can generalize some rules from your experiments.

Gradient

Time
25 minutes
Activity
Individual

One useful operation on images is to detect how they are changing locally. Roughly speaking the gradient can be thought about as specifying both how much the intensity is changing, and what direction that change is happening in. Octave returns that information as an (x,y) vector in the usual parallel array way.

A = [0,0,0,0,0,1;
     0,1,1,1,0,0;
     0,1,1,1,0,1;
     0,1,1,1,1,0;
     0,1,1,1,1,0;
     0,0,0,0,0,0];

[Dx, Dy] = gradient(A);

imshow(A);
figure;
imshow(Dx);
figure;
imshow(Dy);

Complete the follow function, by combining the Dx and Dy arrays into a single array such that normimg(i,j) = norm([Dx(i,j),Dy(i,j)]). Don't use a loop. The demo should produce an image somewhat like the one below.

function normimg = normgrad(img)
  [Dx, Dy] = gradient(img);
  normimg = 
endfunction

%!demo
%! owl=imread("owl.jpg");
%! monowl=monochrome(owl);
%! ng = normgrad(monowl);
%! imshow(ng*2, [0,20]);


Thresholding: overexposure detection

Time
25 minutes
Activity
Individual

One common feature of photo editing software (or camera firmware) is to highlight overexposed or underexposed pixels. In this exercise we will use our solutions for scale layers (from L21) and monochrome in order to mark the pixels above a certain level.

Download the following image (make sure you download the actual full resolution image) and save it as ~/cs2613/labs/L22/leaf.jpg.

Use the builtin function find to complete the threshold function to find all pixel positions with value higher than a given threshold. Note that this use of find depends on broadcasting. In all of the calls to plot in this lab, you may want to adjust markersize to better see the selected pixels (it depends a bit on screen resolution and image size).

function [rows, cols] = threshold(in, lower=100)
  [rows,cols] =
endfunction

%!test
%! A=[1,2,3; 4,5,6];
%!  [rows,cols]=threshold(A,4);
%!  assert(rows==[2;2]);
%!  assert(cols==[2;3]);

%!demo
%! leaf=imread("leaf.jpg");
%! monoleaf=monochrome(leaf);
%! [rows, cols] = threshold(monoleaf,200);
%! clf
%! hold on;
%! imshow(leaf);
%! plot(cols,rows,".","markersize",1);

The output of demo threshold should be something like the following.

Thresholding gradient

Time
20 minutes
Activity
Individual

In order to extract a description of a scene from an image, an early step is to detect the boundaries between various objects in the scene via edge detection. There are many different methods, but one approach is to find pixels where the gradient is particularly high. Use =monochrome= and =normgrad= functions from above to complete the following script.

leaf=imread("leaf.jpg");
monoleaf=
ng =
[rows, cols] = threshold(ng, __ );

clf
hold on;
imshow(leaf);
plot(cols,rows,".","markersize",1);

Try different values for the threshold. The output of this script should be something like the following; it's interesting as far as detecting texture, but not very helpful for dividing the scene into objects. There's also a fair amount of isolated "edge" pixels in the background. Both of these problems can be addressed via smoothing in the next lab.

Before next lab

Lab 23

Before the lab


Convolution and smoothing I

Time
25 minutes
Activity
individual

In order to smooth out some of the noise (or just isolated bright pixels), we can replace the value of each pixel with a weighted average of it's neighbourhood. The simplest approach is the so called box blur, where each pixel in the neighbourhood is added up, and the result divided by the number of pixels. The Octave function conv2(A,B) provides an easy way to apply a convolution or kernel to a matrix. This just means that B specifies the weights in averaging the neighbourhood.

Fill in the appropriate matrix (formula) B to make the following smoothing demo work.

A = [0,0,0,0,0,1;
     0,1,1,1,0,0;
     0,1,1,1,0,1;
     0,1,1,1,1,0;
     0,1,1,1,1,0;
     0,0,0,0,0,0];


for j=2:12
  B=
  AB = conv2(A,B);
  imshow(AB,[]);
  pause(1);
endfor

As the variable j increases, the images should look smoother but also less detailed.


Convolution and Smoothing II

Time
25 minutes
Activity
individual

Generalize the demo above to make a smoothing function. For variety we're going to use the Paris image image from a previous lab for the demo.

function S=smooth(in, side=3)
    S =
endfunction

%!test
%! A = [0,0,0,1;
%!      1,1,0,0;
%!      1,1,0,1;
%!      1,1,1,0];
%! B= [0.50, 0.25, 0.25, 0.25;
%!     1.00, 0.50, 0.25, 0.25;
%!     1.00, 0.75, 0.50, 0.25;
%!     0.50, 0.50, 0.25, 0.00];
%! assert(smooth(A,2),B,eps);

%!demo
%! paris=imread("paris.jpg");
%! monoparis=monochrome(paris,[1/3,1/3,1/3]);
%! imshow(monoparis,[])
%! pause(1);
%! imshow(smooth(monoparis),[])
%! pause(1);
%! imshow(smooth(monoparis,10),[])
%! pause(1);
%! imshow(smooth(monoparis,20),[])

Use your new function to complete the following script. Adjust the smoothing and threshold parameters to approximate the image below. You will need to download the leaf image from last lab.

leaf=imread("leaf.jpg");
monoleaf=monochrome(leaf);
smoothed=
ng = normgrad(smoothed)
[rows, cols] =

clf
hold on;
imshow(leaf,[]);
plot(cols,rows,".","markersize",1);


Smoothing and gradient via convolution

Time
25 minutes
Activity
Individual

As you probably saw, the second argument to conv2 can be much more general than just uniform weights. In particular some of the weights can be negative, e.g. to approximate a gradient calculation.

One example is the Soebel Operator which combines gradiant and smoothing operations.

Refering to the Wikipedia page, complete the function to calculuate the Soebel operator. You will also need to define function norm2, which can be copied from the definition of normgrad (without the gradient part).

function [Gx,Gy]=soebel(in)


  Gx =
  Gy =
endfunction

%!demo
%! leaf=imread("leaf.jpg");
%! monoleaf=monochrome(leaf);
%! [Dx, Dy] = soebel(monoleaf);
%! ns = norm2(Dx,Dy);
%! [rows,cols] = threshold(ns,150);
%! clf
%! hold on
%! imshow(leaf);
%! plot(cols,rows,".","markersize",10);

In the resulting figure the size of the detected pixels is exaggerated to make them easier to spot.

Before next lab

Lab 24

Before the lab

K nearest neighbour

Time
25 minutes
Activity
Individual

The k-Nearest neighbour algorithm (KNN) is a famous machine learning or pattern classification algorithm. In this algorithm you are given a training set of numerical measurement vectors (e.g. lab attendance and height) each labelled with a class (e.g. final grade). Given a new, unlabelled measurement, the algorithm predicts the corresponding class by looking at the k-closest training vectors, and voting.

In this lab we will work with a famous set of measurements of iris flowers, where the "class" is the species of flower.

You need to know the following definition from linear algebra

distance(X,Y) = norm(X-Y)

Complete the following function which returns the row indices of the k closest rows of data to v. In our data sets, we will assume the first column is always the class label, and should ignored in distance calculations.

function out = nearest(v, k, data)
  for i=1:rows(data)
    dist(i)=
  endfor
  [sorted, indices]=sort(dist);
  out = sort(indices(1:k));
endfunction

%!test
%! v = [0,1,1]
%! data = [0,0,0; 0,0,1; 0,1,0; 1,1,1]
%! assert(nearest(v,1,data) == 4)
%! assert(nearest(v,3,data) == [2,3,4])

You can time your nearest neighbour query with the following:

load training
load testing
timeit(1000,@nearest,testing(1,:), 3, training);

K nearest neighbours continued

Time
10 minutes
Activity
Demo

In practice the function nearest would probably be combined with something like the following; they are split out here to help with debugging and testing. Use the parameter fun here rather than calling nearest directly, so we can swap in a different query function later.

function out=findclass(v, k, data, fun=@nearest)
    selection=
    out = mode(data(selection,1));
endfunction

%!test
%! v = [0,1,1];
%! data = [0,0,0; 0,0,1; 0,1,0; 1,1,1];
%! assert(findclass(v,1,data) == 1)
%! assert(findclass(v,3,data) == 0)

Here is a for-loop based version of a kNN classifier. The return value is the success rate of the classifier (i.e. the fraction of the time it gets the right answer on the test set).

function ratio=knn(k,training, testing, output=0)

  correct = 0;
  for i=1:rows(testing)
    row=testing(i,:);
    if findclass(row,k,training) == row(1);
      correct += 1;
    endif
  endfor

  ratio = correct/rows(testing);
endfunction

You can try out the classifier with

load testing
load training
knn(3,testing,training)

Vectorizing nearest

Time
25 minutes
Activity
individual

We need a couple of ideas from basic linear algebra to make this vectorization work. We can make many copies of the input vector v by multiplying it by a ones column vector on the left (no loop required). Try this in octave

ones(10,1)*[1,2,3,4]

As a convenience, we can also sort by the square of the norm, which is simply the dot product of a vector with itself.

function out = nearest2(v, k, data)

  diff = data(       ) - ones(          ,1)*v(2:end);
  normsq = dot(            )
  [sorted, indices]=sort(normsq);
  out = sort(transpose(indices(1:k)));
endfunction

%!test
%! v = [0,1,1];
%! data = [0,0,0; 0,0,1; 0,1,0; 1,1,1];
%! assert(nearest2(v,1,data) == 4)
%! assert(nearest2(v,3,data) == [2,3,4])

Finally, check the speedup with this second knn frunction

function ratio=knn2(k,training, testing, output=0)

  correct = 0;
  for i=1:rows(testing)
    row=testing(i,:);
    if findclass(row,k,training,@nearest2) == row(1);
      correct += 1;
    endif
  endfor

  ratio = correct/rows(testing);
endfunction

You can use the follow lines in bench.m

timeit(10,@knn,3,training,testing);
timeit(10,@knn2,3,training,testing);