Notes on Exam 1: Scheme Fundamentals

This page may be found online at `http://www.cs.grinnell.edu/~rebelsky/Courses/CS151/2000F/Exams/examnotes.01.html`.

Problems

A. Some Definitions

1. Functional Programming

Find and record three reasonable definitions of functional programming. You might look on the Web, in textbooks (programming languages textbooks are best), or in dictionaries.

For each definition, indicate how the Scheme you've learned to date has and has not matched the definition.

You should make sure to cite the sources you use in appropriate format.

Some Notes

Since the essence of this problem was looking up definitions, no sample answer seems necessary.

2. Recursion

In your own words, explain the concept of recursion.

A Solution

Recursion is a technique used to define things in which the definition of the thing depends, in some part, upon the definition of the thing. Recursive definitions therefore seem somewhat circular. However, they are not circular because of the design of recursion. When you have something that depends on itself, it depends on a "simpler" version of itself. Eventually, the thing is no longer defined in terms of itself.

We can use recursion to define simple sets of values. For example, we might say that every non-negative integer (except 0) is the successor of another non-negative integer (where "successor of" means "add 1 to"). Here, we've defined non-negative integers in terms of non-negative integers. In order for the definition to be non-recursive, we need to add a note that 0 is a non-negative integer.

How do we know that 5 is a non-negative integer? 5 is the successor of 4, so we must show that 4 is a non-negative integer. 4 is the successor of 3, so we must show that 3 is a non-negative integer. 3 is the successor of 2, so we must show that 2 is a non-negative integer. 2 is the successor of 1, so we must show that 1 is a non-negative integer. 1 is the successor of 0, and 0 is a non-negative integer.

We can use recursion to define data structures, such as lists. For example, we say that a list is either (1) the empty list or (2) a pair whose car is some value and whose cdr is a list.

We can use recursion to define procedures, such as factorial. For example, we say that the factorial of 0 is 1 and the factorial of n is n times the factorial of n-1.

B. A CD Database

Sarah and Steven Schemer have decided to use Scheme to record their enormous collection of popular CDs. For each CD, they'd like to record (1) the artist, (2) the title, (3) the genre, and (4) a list of songs on the CD.

General Solution Notes

You can find the sample solutions to this section in

3. The Database Format

a. How might you represent this database in Scheme? Be as clear as you can in your description.

b. Using this representation, show how to represent your five favorite CDs.

A Solution

a. It seems easiest to represent the database as a list of records for each CD. For a CD, we'll use a four-element list in which the first element is the artist (a string), the second is the title (a string), the third is the genre (a string), and the fourth is a list of strings.

```
;;; Information about some CDs in Sam's collection.
;;; Author:
;;;   Samuel A. Rebelsky
;;; Version:
;;;   1.0 of October 2000
;;; Each CD is of the format
;;; (artist      ; a string
;;;  title       ; a string
;;;  genre       ; a string
;;;  songs       ; a list of strings
;;;  )

;;; Here are a few of Sam's CDs
(define samscds
'(("Jess Klein" "Flattery" "Folk"
("Thin Crack" "Sometimes Blues" "Gleam" "Nicks' Song" "Bad Apartment"
"Pain Makes Me Strong" "Sunday" "Everybody's Crazy" "Common Sense"
"Living In Sin" "I Couldn't Say" "The One Who Got Away" "Flirting"))
("Christine Lavin" "One Wild Night" "Folk"
("Oh No" "They Look Alike, They Walk Alike" "National Apology Day"
"Flip Side of Fame" "The Kind of Love You Never Recover From"
"Errol Flynn" "Please Don't Make Me Too Happy"
"The Voice on the Relaxation Tape" "Visiting Dallas" "The Sixth Floor"
"Great Big Bug" "The Wild Blue" "Dream/Summer Song/Gotta Twirl"))
("The Stompers" "Greatest Hits Live" "Rock"
("American Fun" "Shut Down" "One Heart For Sale" "First Kiss" "Good News"
"She's An Angel" "Temptation" "Never Tell An Angel" "Coast To Coast"
"Wipeout/Walk Don't Run/Palisades Park"))
("The Who" "The Who Sell Out" "Rock"
("Armenia City in the Sky" "Heinz Baked Beans"
"Mary Anne With The Shaky Hand"
"Odorono" "Tatoo" "Our Love Was" "I Can See For Miles"
"I Cant Reach You" "Medac" "Relax" "Silas Stingy" "Sunrise"
"Rael 1" "Rael 2" "Glittering Girl" "Melancholia"
"Someones Coming" "Jaguar" "Early Morning Cold Taxi"
"Hall of the Mountain King" "Girls Eyes"
"Mary Anne with the Shaky Hand" "Glow Girl"))
("The Who" "BBC Sessions" "Rock"
("My Generation" "Anyway, Anyhow, Anywhere" "Good Lovin"
"Just You and Me, Darling" "Leaving Here" "My Generation"
"The Good's Gone" "La La La Lies" "Substitute" "More Songs"))
("Somebody Strange" "A Fake Record" "Strange"
("Never Tell An Angel" "Mary Anne with the Shaky Hand"))
("Somebody Strange" "BBC Sessions" "Strange"
("Errol Flynn"))
))

```

4. Searching the Database

The Schemer's would like to be able to search their database so that they can easily look up lists of albums.

Write and test a procedure, ```(search-cd category key database)``` that lets them search by artist, title, or genre and returns all matching records. For example, `(search-cd 'artist "Van Morrison" samscds)` should return all the CDs I have by Van Morrision.

A Solution

Since I have my CDs arranged uniformly, I've decided to write some helper functions to extract parts. (I find lots of `list-ref` and `cadr` commands hard to read.)

```;;; Extract some bit of information about a CD.
;;; Parameters:
;;;   A CD entry in the appropriate format.
;;; Preconditions:
;;;   The CD entry is in the appropriate format.
;;; Postconditions:
;;;   Does not modify the CD.
;;; Return value:
;;;   Some part of the CD record.
(define get-cd-artist
(lambda (cd) (list-ref cd 0)))
(define get-cd-title
(lambda (cd) (list-ref cd 1)))
(define get-cd-genre
(lambda (cd) (list-ref cd 2)))
(define get-cd-songs
(lambda (cd) (list-ref cd 3)))
```

I'll continue with a helper function that lets me extract a particular category from a database entry.

```;;; Extract a specified part of a CD
;;; Parameters:
;;;   The entry for a CD
;;;   A category (one of artist, title, and genre)
;;; Preconditions:
;;;   The CD entry is in the appropriate format
;;;   The category is one of the three symbols artist, title, genre
;;; Postconditions:
;;;   Returns the specified part of the CD.
(define get-category
(lambda (cd category)
(cond ((equal? category 'artist) (get-cd-artist cd))
((equal? category 'title) (get-cd-title cd))
((equal?  category 'genre) (get-cd-genre cd))
(else (error "Invalid category")))))
```

Once we have that, it's a matter of recursing through the list, keeping anything that matches.

```;;; Extract all CDs that match a key for a particular category.
;;; Parameters:
;;;   A category (artist, title, or genre); a symbol.
;;;   A string that represents the key to match.
;;;   A list of CDs.
;;; Returns:
;;;   A list of CDs that match the given criteria.
;;; Preconditions:
;;;   Every CD in the list is in valid CD format.
;;;   The category is one of artist, title, and genre.
;;; Postconditions:
;;;   Does not affect the list of CDs.
(define search-cd
(lambda (category key cds)
(cond
;;; If there are no CDs, there are no matches
((null? cds) null)
;;; If the first CD matches, keep it and recurse
((equal? key (get-category (car cds) category))
(cons (car cds) (search-cd category key (cdr cds))))
;;; Otherwise, just recurse on the rest
(else (search-cd category key (cdr cds))))))
```

5. Searching with Two Keys

Show how to use `search-cd` to find a CD matching a given artist and title.

A Solution

My goal for this problem was to have you reuse `search-cd`. Since the return value from `search-cd` is just another list of CDs, we can use that list as the parameter to another call. Hence, the easy answer is

```(search-cd 'artist artist
(search-cd 'title title database))
```

For example, here's how I'd get the list of all the copies of "The Who Sell Out".

```(search-cd 'artist "The Who"
(search-cd 'title "The Who Sell Out" samscds))
```

Of course, it seems better to write a function that does this for us.

```;;; Extract all the CDs from a database that match a given artist
;;; and title.
;;; Parameters:
;;;   An artist (a string)
;;;   A title (a string)
;;;   A list of CDs
;;; Returns:
;;;   All CDs with the given artist and title.
;;; Preconditions:
;;;   All CDs in the list are in the appropriate format.
;;; Postconditions:
;;;   Does not affect the list.
(define search-artist-title
(lambda (artist title cds)
(search-cd 'artist artist
(search-cd 'title title cds))))
```

I would then do the same search with

```(search-artist-title "The Who" "The Who Sell Out")
```

If I want just one matching CD, I should take the `car` of that list.

```(define sellout
(car (search-artist-title "The Who" "The Who Sell Out")))
```

Of course, this will crash and burn if the CD is not found.

6. Searching for Songs

Write and test a procedure, `(search-songs song database)` that lets someone search the database by song title.

A Solution

The solution is rather like the solution to problem 4, differing only in how we see whether a CD matches the criterion of "has this song".

```;;; Extract all CDs that match a key for a particular category.
;;; Parameters:
;;;   A song title (a string).
;;;   A list of CDs.
;;; Returns:
;;;   A list of CDs that match the given criteria.
;;; Preconditions:
;;;   Every CD in the list is in valid CD format.
;;; Postconditions:
;;;   Does not affect the list of CDs.
(define search-songs
(lambda (song cds)
(cond
;;; If there are no CDs, there are no matches
((null? cds) null)
;;; If the first CD matches, keep it and recurse
((inlist? song (get-cd-songs (car cds)))
(cons (car cds) (search-songs song (cdr cds))))
;;; Otherwise, just recurse on the rest
(else (search-songs song (cdr cds))))))
```

Note that we use a helper procedure, `(inlist string strings)` that returns true if the string is in the list and false otherwise. Some variant of `member` shold work, but we'll write it by hand.

```;;; Determine if a string is in a list of strings.
;;; Parameters:
;;;   A string.
;;;   A potentially empty list of strings.
;;; Preconditions:
;;;   None
;;; Postconditions:
;;;   Does not affect the list.
(define inlist?
(lambda (str strlist)
;;; Nothing is in the null list.
(if (null? strlist) #f
;;; str is in the list if it's the first element or part
;;; of the remainder.0
(or (equal? str (car strlist))
(inlist? str (cdr strlist))))))
```

C. A Course Record

After too many years of a difficult registration system, the Registrar has decided to turn to CSC151 for help in writing a new system. We've decided to store the information as a list of student records. For each student, we record name, id, graduation year, major, and a list of courses. For each course, we record semester, department, abbreviation, grade, and credits.

For example

```(define students
'(("Sarah Schemer" "000011235" "2002" "CS"
(("Fall 1999" "Math/CS" "CSC151" "A-" 4)
("Fall 1999" "General" "TUT101" "B" 4)
("Fall 1999" "Math/CS" "MAT133" "C+" 4)
("Spring 2000" "Math/CS" "CSC152" "F" 4)))
("Steven Schemer" "000011236" "2003" "Math"
(("Fall 1999" "Math/CS" "CSC151" "A-" 4)
("Fall 1999" "General" "TUT101" "B" 4)
("Fall 1999" "Math/CS" "MAT133" "C+" 4)
("Spring 2000" "Math/CS" "CSC152" "F" 4)))))
```

Solution Notes

You can find a sample solution in

7. Some Sample Records

Using this form, create a list of three students in the class of 2002 (who will have finished two years of classes).

A Solution
```
;;; Information about some students (not real students).
;;; Author:
;;;   Samuel A. Rebelsky
;;; Version:
;;;   1.0 of October 2000
;;; Each student is in the format
;;;   (name       ; a string
;;;    id         ; a string
;;;    year       ; a string
;;;    major      ; a string
;;;    courses    ; a list of courses
;;;   )
;;; Each course has the format
;;;   (semester
;;;    department
;;;    number
;;;    credits)

(define students
'(("Sarah Schemer" "000011235" "2002" "CS"
(("Fall 1998" "Math/CS" "CSC151" "A-" 4)
("Fall 1998" "General" "TUT101" "B" 4)
("Fall 1998" "Math/CS" "MAT133" "C+" 4)
("Spring 1999" "Math/CS" "CSC152" "F" 0) ; Made up next semester
("Spring 1999" "Anthropology" "ANT154" "B" 4)
("Fall 1999" "Math/CS" "CSC152" "B" 4)))
("Steven Schemer" "000011236" "2003" "Math"
(("Fall 1998" "General" "TUT101" "B" 4)
("Fall 1998" "Math/CS" "MAT133" "C+" 4)
("Spring 1999" "Math/CS" "CSC151" "A" 4)))
("Sam Rebelsky" "00000000" "1985" "Math"
(("Fall 1982" "Sociology" "SOC101" "B" 4)
("Fall 1982" "Math/CS" "MAT217" "A" 4)
("Fall 1982" "Physics" "PHY101" "A" 4))) ))

```

8. Computing GPA

Write a procedure, `(gpa studentname)` that computes a student's GPA. You may wish to create an auxiliary list that gives a point value for each letter grade.

A Solution

We begin with an association list that we can use to map letter grades to numeric values.

```;;; An association list that gives a numeric value for each letter grade
'(("A" . 4.0)
("A-" . 3.67)
("B+" . 3.33)
("B" . 3.0)
("B-" . 2.67)
("C+" . 2.33)
("C" . 2.0)
("D" . 1.0)
("F" . 0.0)))
```

We use that to create a simple procedure that maps any valid grade to the corresponding point value. Note that we don't suppor S/D/F (primarily because I've forgotten how to handle it).

```;;; Compute the numeric value for a grade.
;;; Parameters:
;;;   A letter grade ("A", "A-", "B+", "B", "B-", "C+", "C", "D" "F")
;;; Returns:
;;;   A numeric value.
;;; Preconditions:
;;;   The letter grade must be one of Grinnell's valid letter grades.
;;; Postconditions:
;;;   None
```

We take a detour to sum the number of credits for a student.

```;;; Compute the total number of credits a student received.
;;; Parameters:
;;;   A student
;;; Return value:
;;;   The number of credits the student received.
;;; Preconditions:
;;;   The student is in the appropriate format.
;;; Postconditions:
;;;   Does not affect the student.
(define credits
(lambda (student)
(credits-helper (get-student-courses student))))

;;; Compute the total number of credits for a list of courses.
;;; Parameters:
;;;   A list of courses.
;;; Return value:
;;;   The total number of credits for those courses.
;;; Preconditions:
;;;   Each course is in the appropriate format.
(define credits-helper
(lambda (courses)
(if (null? courses) 0
(+ (get-course-credits (car courses))
(credits-helper (cdr courses))))))
```

We use similar code to find the total number of points a student received across his or her career.

```;;; Compute the total number of points a student received.
;;; Parameters:
;;;   A student
;;; Return value:
;;;   The number of points the student received.
;;; Preconditions:
;;;   The student is in the appropriate format.
;;; Postconditions:
;;;   Does not affect the student.
(define points
(lambda (student)
(points-helper (get-student-courses student))))

;;; Compute the total number of points for a list of courses.
;;; Parameters:
;;;   A list of courses.
;;; Return value:
;;;   The total number of points for those courses.
;;; Preconditions:
;;;   Each course is in the appropriate format.
(define points-helper
(lambda (courses)
(if (null? courses) 0
(get-course-credits (car courses)))
(points-helper (cdr courses))))))
```

Once we've done all that work, finding the GPA is easy.

```;;; Compute a student's GPA.
;;; Parameters:
;;;   A student
;;; Return value:
;;;   The student's GPA.
;;; Preconditions:
;;;   The student is in the appropriate format.
;;;   The student has completed at least one course.
;;; Postconditions:
;;;   Does not affect the student.
(define gpa
(lambda (student)
(/ (points student) (credits student))))
```

9. Distribution Credits

Write a procedure, `(division-credits division studentname)`, that computes the number of credits the student has in a division (or non-divisional credits, such as tutorial). For example,

```> (division-credits 'science "Sam Rebelsky")
54
```
A Solution

We begin by writing a helper procedure, `find-student`, that lets us look up a student in the database.

```;;; Find a student in the database.  If the student is not there,
;;; return false.
;;; Parameters:
;;;   A name
;;;   A list of students
;;; Returns:
;;;   The student record for that name, if the name is in the list.
;;;   #f, otherwise.
;;; Preconditions:
;;;   Each student in the list is in the appropriate format.
;;; Postconditions:
;;;   Does not affect the list.
(define find-student
(lambda (name students)
(cond ((null? students) #f)
((equal? name (get-student-name (car students))) (car students))
(else (find-student name (cdr students))))))
```

We continue by writing the list of different divisions.

```;;; Lists of departments in various divisions
(define science-departments
'("Biology" "Biological Chemistry" "Chemistry" "Math/CS"
"Physics" "Psychology" "Science"))
(define socsci-departments
'("American Studies" "Anthropology" "Economics" "Education" "History"
"Political Science" "Sociology" "Social Studies"))
(define humanities-departments
'("Art" "Art History" "Chinese" "Classics" "English" "French"
"German" "Humanities" "Music" "Philosophy" "Religious Studies"
"Russian" "Spanish" "Theatre"))
(define general-departments
'("General" "Tutorial" "Gender and Women's Studies" "Technology Studies"
"Indpendent Major"))
```

We use those to allow us to get the division for any department.

```;;; Given a department, determine its division
;;; Parameters:
;;;   A department [a string]
;;; Return value:
;;;   The corresponding division (humanities, science, socsci, general)
;;;     [a symbol]
;;; Preconditions:
;;;   Must be a valid department
;;; Postconditions:
;;;   Does not affect anything.
(define department->division
(lambda (department)
(cond ((inlist? department science-departments) 'science)
((inlist? department socsci-departments) 'socsci)
((inlist? department humanities-departments) 'humanities)
(else 'general))))
```

Now we're ready to go. We simply step through the list, checking the division for each course and adding credits if it's in the right division. How do we get the list of courses? We look the student up in the database (with `(find-student name students)`), get the list of courses for that student with `get-student-courses` (or the equivalent), and pass it along to the helper function.

```;;; How many credits does a student have in a division?
;;; Parameters:
;;;   The division (science, socsci, humanities, general) [a symbol]
;;;   A student's name [a string]
;;; Returns:
;;;   The number of credits the student has in the division.
;;; Preconditions:
;;;   The global student database (students) contains the student.
;;;   The division is one of the legal values.
;;; Postconditions:
;;;   Does not affect the list of students.
(define division-credits
(lambda (name division)
(division-credits-helper
(get-student-courses (find-student name students)) division)))

;;; How many credits for a list of courses fall within a division.
;;; Parameters:
;;;   A list of courses.
;;;   A division.
;;; Preconditions:
;;;   The list of courses is in the appropriate format.
;;;   The division is valid.
(define division-credits-helper
(lambda (courses division)
(cond ((null? courses) 0)
((equal? division
(department->division (get-course-department (car courses))))
(+ (get-course-credits (car courses))
(division-credits-helper (cdr courses) division)))
(else (division-credits-helper (cdr courses) division)))))
```

10. Summaries

Write a procedure, `(summarize studentname)` that summarizes information about a student. For example,

```> (summarize "Sam Rebelsky")
(gpa 3.2 science 54 humanities 20 socsci 30 general 10)
```
A Solution

We simply make a list of the various parts.

```;;; Summarize a student (with a list)
;;; Parameters:
;;;   The name of a student.
;;; Return value:
;;;   A list that summarizes a student.
;;; Preconditions:
;;;   The student must be in the list named "students".
;;; Postconditions:
;;;   None
(define summarize-student
(lambda (name) (summarize-student-helper (find-student name students))))

;;; Summarize a student (with a list)
;;; Parameters:
;;;   A student record.
;;; Return value:
;;;   A list that summarizes a student.
;;; Preconditions:
;;;   The student must be in the list named "students".
;;; Postconditions:
;;;   None
(define summarize-student-helper
(lambda (student)
(list
'gpa (gpa student)
'science
(division-credits-helper (get-student-courses student) 'science)
'humanities
(division-credits-helper (get-student-courses student) 'humanities)
'socsci
(division-credits-helper (get-student-courses student) 'socsci)
'general
(division-credits-helper (get-student-courses student) 'general))))
```

History

Monday, 23 October 2000

• Created.

Tuesday, 24 October 2000

• Filled in more of the code (which I'd kept in separate files).

Disclaimer Often, these pages were created "on the fly" with little, if any, proofreading. Any or all of the information on the pages may be incorrect. Please contact me if you notice errors.

This page may be found at http://www.cs.grinnell.edu/~rebelsky/Courses/CS151/2000F/Exams/examnotes.01.html

Source text last modified Tue Oct 24 10:32:44 2000.

This page generated on Tue Oct 24 10:35:49 2000 by Siteweaver. Validate this page's HTML.

Contact our webmaster at rebelsky@grinnell.edu