This homework has three types of problems:
Self Check: You are strongly encouraged to think about and work through these questions, but you will not submit answers to them.
Problems: You will turn in answers to these questions.
Programming: This part involves writing Scala code. You are required to work with a partner on it.
You are welcome to choose your own partner, but I can also assist in matching pairs --- simply send me a Slack message and I will pair you with someone else also looking for a partner. Please do not let finding a partner go until the last minute.
(Required) Mitchell, Chapters 10--11
(Required) The Scala Tutorial
Mitchell, Problem 11.4
The given conversions between Cartesian and polar coordinates work for any point (x,y), where x \geq 0 and y > 0. Do not worry about points where x < 0 or y \leq 0. The figure P.11.4.1 appears on page 332.
You should try to write reasonably accurate Smalltalk code for part (b), but you do not need to use Squeak.
Mitchell, Problem 11.7
Mitchell, Problem 11.6
(You will find it useful to answer Problem 11.7 first before working on this one.)
Mitchell, Problem 11.8
Mitchell, Problem 11.9
See instructions here for setting up Scala.
The scala
command will give you a "read-eval-print" loop, as in Lisp and ML.
You can also compile and run a whole file as follows. Suppose file A.scala
contains:
object A {
def main(args : Array[String]) : Unit = {
println(args(0));
}
}
You then compile the program with scalac A.scala
, and run it
(and provide command-line arguments) with "scala A moo cow
".
There is plenty of very detailed information about Scala available online (e.g., http://www.scala-lang.org --- just web search for "Scala Language"). I suggest that you look at tutorial-style descriptions of the features of interest as well as the Scala Language Specification for some of the specifics.
There is also extensive online documentation for the Scala libraries at http://www.scala-lang.org/api/.
In this first question we'll use Scala answer a few questions about cows. Specifically the herd at Cricket Creek Farm...
First, write a program to read in and print out the data in the file "cows.txt" your gitlab repository for hw7. Each line contains the id, name, and daily milk production of a cow from the herd. (I've also included a "cows-short.txt" file that may be useful while debugging.)
The program should be in a file called "Cows.scala" that
includes a single object
definition. (I didn't give you this in your repo -- just create it yourself and it add with git add Cows.scala
.) Recall that object
s are
like class
es, except that only a single instance is created.
You file should be something like:
object Cows {
def main(args : Array[String]) : Unit = {
// ...
}
}
One useful snippet of code is the following line.
val lines = scala.io.Source.fromFile("cows.txt").getLines();
We will use this to read the file. Try this out in the Scala
interpreter. What type does lines
have? For convenience in
subsequent processing, it will be useful to convert line
into
a list:
val data = lines.toList;
Print out the list and verify you are successfully reading all
the data. Use a for
loop. For loops in Scala follow a familiar
syntax:
scala> for (i <- 1 to 3) println(i);
1
2
3
Print the data
again, using the foreach
method on lists.
The for
construct lets you do many other things as well, such
as selectively filtering out the elements while iterating. For
example:
scala> for (i <- 1 to 5 if i%2==0) println(i);
2
4
Use such a for
list to print all cows containing "s" in their
name. Make the test be case insensitive. Scala Strings support
all of the same string operations as Java Strings. A few useful
ones here and below:
class String {
def contains(str : String) : Bool
def startsWith(str : String ) : Bool
def toLowerCase() : String
def toUpperCase() : String
// split breaks up a line into pieces separated by separator.
// For ex: "A,B,C".split(",") -> ["A", "B", "C"]
def split(separator : String) : Array[String]
}
Now print all cows containing "s" but not "h". Multiple if
clauses can be chained together, as in
"1 to 10 if i%2==0 if i%3==0
".
Scala also supports list comprehensions:
val list = ...;
println (for (x <- list if ...) yield f(x));
Show an example of list comprehensions by computing something about the data with one. (You may need to look up list comprehensions in the documentation for more detail...)
Next, define a new class in "Cows.scala" to store one cow, its id, and its daily milk production.
class Cow(s : String) {
def id = ...
def name = ...
def milk = ...
override def toString() = {
...
}
}
It takes in a string of the form "id,name,milk" from the
data file and provides the three functions shown. For
toString
, you may find formatting code like
""%5d ".format(n)
" handy -- it formats the number n as a
string and pads it to 5 characters.
Use a map operation on data
to convert it from a list of
strings to a list of Cow
s. Print the data
and makes sure it
works.
Use a list comprehension to print all cows who produce more then 4 gallons of milk per day.
Use the sortWith
method on List
s to sort the cows by id.
Also use foldLeft
to tally up the milk production for the
whole herd.
class List[A] {
def sortWith (lt: (A, A) => Boolean) : List[A]
def foldLeft [B] (z: B)(f: (B, A) => B) : B
}
Note that foldLeft
is a polymorphic method with type parameter
B
. Also,
foldLeft
is curried, so you must call it specially, as in:
val list : List[Int] = ...;
val n : Int = ...;
list.foldLeft (n) ( (x: Int, elem: Int) => ... )
Finally, use the maxBy
and minBy
methods on your list of
cows to find the cows with the highest and lowest daily milk
production.
You'll now learn to speak like a pirate, with the help of Scala maps
and a Translator
class... The program will take in an English
sentence and convert it into pirate. For example, typing in
pardon, where is the pub?
gives you
avast, whar be th' Skull & Scuppers?
The handouts page contains a "Pirate.scala" file to start with. You
will be responsible for implementating a Translator
class, reading
in the priate dictionary, and processing the user input. It will be
easiest to proceed in the following steps:
First, complete the Translator
class. It has the following
signature:
class Translator {
// Add the given translation from an English word
// to a pirate word
def += (english : String, pirate : String) : Unit
// Look up the given english word. If it is in the dictionary,
// return the pirate equivalent.
// Otherwise, just return English.
def apply(english : String) : String
// Print the dictionary to stdout
override def toString() : String
}
Note that we're overloading the +=
and ()
operators for
Translator
. Thus, you use a Translator object as follows:
val pirate = new Translator();
pirate += ("hello", "ahoy");
..
val s = pirate("hello");
If "hello" is in the dictionary, its pirate-translation is returned. Otherwise, your translator should return the word passed in. Any non-word should also just be returned. Thus:
pirate("hello") ==> "hello"
pirate("moo") ==> "moo"
pirate(".") ==> "."
When writing apply
, use the get
method on map
and pattern
matching to handle the Option
type it returns. (See class
notes / tutorial for details on Option
.)
Finish the definition of Translator
using a Scala map instance
variable. To write toString
, you may find it handy to look at
the mkString
methods of the Scala Map classes.
Add a few lines to the Pirate main
method to test your
translator.
Now, read in the full pirate dictionary from the "pirate.txt" data file, and print out the resulting translator.
Once you have the translator built, uncomment the lines in
main
that process standard input, and process the text the
user types in. There are a few sample sentences on the handouts
page. Here is an example:
Stephen-Freund:~/scala] cat sentence1.txt
pardon, where is the pub?
I'm off to the old buried treasure.
Stephen-Freund:~/scala] scala Pirate < sentence1.txt
avast, whar be th' Skull & Scuppers?
I'm off to th' barnacle-covered buried treasure.
You may still get some deprecation warnings to the effect of "multiarg infix syntax looks like a tuple and will be deprecated". Typically I'd make sure the code produces no warnings, but let's not worry about it this week...
Using Scala 2.13+ on your own computer?
You may encounter one small issue if you are using Scala 2.13+ on your own computer.
Specifically, a library my starter code uses is no longer included with the compiler.
Please do the following if you run into compilation problems related to import
statements:
Download the following and save in your project directory: scala-parser-combinators.jar
Compile and run your code with:
scalac -cp .:scala-parser-combinators.jar Expressions.scala
scala -cp .:scala-parser-combinators.jar Expressions
(Note: if you are on a windows machine, use .;scala-parser-combinators.jar
as the classpath.)
You may ignore any deprecation warnings.
In Scala, algebraic datatypes can be defined with the use of abstract classes and case classes. Consider the following algebraic data type for expressions:
sealed abstract class Expr
case class Variable(name: Symbol) extends Expr
case class Constant(x: Double) extends Expr
case class Sum(l: Expr, r: Expr) extends Expr
case class Product(l: Expr, r: Expr) extends Expr
case class Power(b: Expr, e: Expr) extends Expr
This Scala code is equivalent to the following definition in ML:
datatype Expr =
Variable of Symbol
| Constant of double
| Sum of Expr * Expr
| Product of Expr * Expr
| Power of Expr * Expr;
Have a look at the starter code in Expressions.scala
to see an
example of Scala-style pattern matching on case classes.
Write a function that takes the derivative of an expression with respect to a given variable. Your function should have the following signature:
def derive(e: Expr, s: Symbol): Expr
Your function does not have to take the derivative of Powers with non-constant exponents. It is acceptable to throw an exception in that circumstance.
Also, you'll likely need the Chain Rule for the Power case, though you may further restrict your code to handle only cases where the base is a variable or constant for simplicity if you prefer.
You can find various derivative rules here.
Note that in Scala, Symbols can be declared by using a single quote before the name of the symbol, as such:
scala> 'x
res0: Symbol = 'x
scala> 'y
res1: Symbol = 'y
scala> 'abc
res2: Symbol = 'abc
Tests
The main
method for Expressions
contains a number of test cases
for derive
and the other methods you will write. Uncomment those
tests as you implement the methods. Those are the cases the autograder
uses, so if you pass them you should be all set.
You will find a description of how those tests work below.
Write a function that evaluates a given expression in a given environment. An environment is just a mapping from symbols to values for those symbols. Your function should have the following signature:
def eval(e: Expr, env: Map[Symbol, Double]): Double
If a variable in the expression is not in the given environment, you should throw an exception.
Write a function that when given an expression reduces that expression to its simplest form. Your function should have the following signature:
def simplify(e: Expr): Expr
For example,
simplify(Sum(Variable('x),Constant(0)))
should return Variable(’x)
. Your function need not be
exhaustive -- just implement four or five interesting cases.
In order to make the task of writing tests easier, we
provide an expression parser. The expression parser takes a string
and returns its corresponding Expr
. The expression parser can be
invoked on a string str
like so: Expr(str)
. For example, to
demonstrate that your simplifier knows about the additive identity
of zero, you might write the following test:
assertEquals(Expr("x"), simplify(Expr("x + 0")))
The syntax that the expression parser accepts can be expressed by the following grammar:
expr := sum
sum := product { ("+" | "-") product }
product := power { "*" power }
power := factor [ "^" factor ]
factor := "(" expr ")" | variable | constant
variable := ident constant := floatLit
floatLit := [ "-" ] positiveFloat
positiveFloat := numericLit [ "." [ numericLit ] ]
Submit your homework via GradeScope by the beginning of class on the due date.
Submit your answers to the Gradescope assignment named, for example, "HW 1". It should:
You will be asked to resubmit homework not satisfying these requirements. Please select the pages for each question when you submit.
If this homework includes programming problems, submit your code to the Gradescope assignment named, for example, "HW 1 Programming". Also:
Autograding: For most programming questions, Gradescope will run an autograder on your code that performs some simple tests. Be sure to look at the autograder output to verify your code works as expected. We will run more extensive tests on your code after the deadline. If you encounter difficulties or unexpected results from the autograder, please let us know.