updated
authorChristian Urban <christian.urban@kcl.ac.uk>
Mon, 21 Nov 2022 15:57:45 +0000
changeset 447 f51e593903ac
parent 446 99dcfdf5aed8
child 448 db2a3e3287a9
updated
progs/lecture1.scala
progs/lecture2.scala
wsheets/wsh01.scala
wsheets/wsh02.scala
wsheets/wsh02.tex
--- a/progs/lecture1.scala	Thu Nov 17 20:05:36 2022 +0000
+++ b/progs/lecture1.scala	Mon Nov 21 15:57:45 2022 +0000
@@ -1,7 +1,24 @@
 // Scala Lecture 1
 //=================
 
+// - List, Sets, Strings, ... 
+// - Value assignments (val vs var)
+// - How to define functions? (What is returned?)
+// - If-Conditions
 
+val tmp = 0
+val result = !(tmp == 0)
+val result = if (tmp == 0) true else false
+
+//        expressions (if (tmp == 0) true else false)
+// - For-Comprehensions (guards, with/without yield)
+//
+//
+// - Options
+// - Higher-Order Functions (short-hand notation)
+// - maps (behind for-comprehensions)
+// - Pattern-Matching
+// - String-Interpolations
 
 // Value assignments
 // (their names should be lower case)
@@ -180,6 +197,7 @@
 // you can make the type of a value explicit
 val name = "bob"
 
+val name : String = "bob"
 
 // type errors
 math.sqrt("64".toDouble)
@@ -327,8 +345,6 @@
 
 // with if-predicates / filters
 
-if (1 == 2) "a" else "b"
-
 for (n <- (1 to 3).toList; 
      m <- (1 to 3).toList;
      if (n + m) % 2 == 0) yield (n, m)
@@ -453,7 +469,7 @@
 
 def count_intersection(A: Set[Int], B: Set[Int]) : Int = {
   var count = 0
-  for (x <- A; if (B contains x)) count += 1 
+  for (x <- A.par; if (B contains x)) count += 1 
   count
 }
 
--- a/progs/lecture2.scala	Thu Nov 17 20:05:36 2022 +0000
+++ b/progs/lecture2.scala	Mon Nov 21 15:57:45 2022 +0000
@@ -70,14 +70,19 @@
 // List[Int]: Nil, List(_) 
 //
 // Option[Int]: None, Some(0), Some(1), ...
+// Option[Boolean]: None, Some(true), Some(false)
 // Option[...]: None, Some(_)
 
 def safe_div(x: Int, y: Int) : Option[Int] = 
   if (y == 0) None else Some(x / y)
 
-List(1,2,3,4,5,6).indexOf(7)
+safe_div(10 + 5, 4 - 1)  
 
-def my_min(ls: List[Int]) : Option[Int] = ls.minOption
+List(1,2,3,4,5,6).indexOf(7)
+List[Int]().minOption
+
+def my_min(ls: List[Int]) : Option[Int] = 
+  ls.minOption
 
 my_min(List(1,2,3,4))
 
@@ -90,9 +95,10 @@
 import scala.util._
 import io.Source
 
-val my_url = "https://nms.kcl.ac.uk/christian.urban2/"
+val my_url = "https://nms.kcl.ac.uk/christian.urban/"
 
 Source.fromURL(my_url)("ISO-8859-1").mkString
+Source.fromURL(my_url)("ISO-8859-1").getLines().toList
 
 Try(Source.fromURL(my_url)("ISO-8859-1").mkString).getOrElse("")
 
@@ -100,14 +106,18 @@
 
 
 // the same for files
+
 Try(Some(Source.fromFile("test.txt")("ISO-8859-1").mkString)).getOrElse(None)
 
+Try(Source.fromFile("test.txt")("ISO-8859-1").mkString).toOption
+
+Using(Source.fromFile("test.txt")("ISO-8859-1"))(_.mkString).toOption
 
 // how to implement a function for reading 
-// (lines) something from files...
+// (lines) from files...
 //
 def get_contents(name: String) : List[String] = 
-  Source.fromFile(name)("ISO-8859-1").getLines.toList
+  Source.fromFile(name)("ISO-8859-1").getLines().toList
 
 get_contents("text.txt")
 get_contents("test.txt")
@@ -120,7 +130,7 @@
 
 // much better - you record in the type that things can go wrong 
 def get_contents(name: String) : Option[List[String]] = 
-  Try(Some(Source.fromFile(name)("ISO-8859-1").getLines.toList)).getOrElse(None)
+  Try(Some(Source.fromFile(name)("ISO-8859-1").getLines().toList)).getOrElse(None)
 
 get_contents("text.txt")
 get_contents("test.txt")
@@ -158,8 +168,6 @@
 val lst = List(None, Some(1), Some(2), None, Some(3))
 
 
-
-
 // a function that turns strings into numbers 
 // (similar to .toInt)
 Integer.parseInt("1234")
@@ -234,7 +242,7 @@
 lst.find(n => odd(n) && n > 2)
 
 
-
+// lexicographic ordering
 val ps = List((3, 0), (3, 2), (4, 2), (2, 2), 
               (2, 0), (1, 1), (1, 0))
 
--- a/wsheets/wsh01.scala	Thu Nov 17 20:05:36 2022 +0000
+++ b/wsheets/wsh01.scala	Mon Nov 21 15:57:45 2022 +0000
@@ -22,7 +22,7 @@
 // Task 2
 
 val z = 42
-z = z + 1 .       // error
+z = z + 1         // error
 val x = 2 * z
 val z = 466       // old z not accessible anymore
 println(x)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wsheets/wsh02.scala	Mon Nov 21 15:57:45 2022 +0000
@@ -0,0 +1,77 @@
+
+// Task 1 
+
+List(7,2,3,4,5,6).find(_ < 4)  // => Some(3)
+List(5,6,7,8,9).find(_ < 4)    // => None
+List(5,6,7,8,9).min            // => 5
+List(5,6,7,8,9).minOption      // => Some(5)
+List[Int]().minOption          // => None 
+
+
+// Task 2
+
+Try(Some(List(5,6,7,8,9).min)).getOrElse(None)
+Try(Some(List[Int]().min)).getOrElse(None)
+
+// Task 3
+import scala.util._
+import io.Source
+
+val my_url = "https://nms.kcl.ac.uk/christian.urban/"
+
+// fails if there is no file with that name
+Source.fromFile("test.txt")("ISO-8859-1").mkString
+Source.fromFile("test.txt")("ISO-8859-1").getLines().toList
+
+// encapsulates the failure case as None
+Try(Some(Source.fromFile("test.txt")("ISO-8859-1").mkString)).getOrElse(None)
+Try(Source.fromFile("test.txt")("ISO-8859-1").mkString).toOption // same but shorter
+
+// for files with propper closing of the file after reading
+Using(Source.fromFile("test.txt")("ISO-8859-1"))(_.mkString).toOption
+
+// Task 4 (Higher-Order Functions)
+
+List(7,2,3,4,5,6).find(_ < 4)
+List(7,2,3,4,5,6).count(_ % 2 == 0)
+List(7,2,3,4,5,6).sortWith(_ > _)
+List(7,2,3,4,5,6).filter(_ > 4)
+
+// Task 5 (Maps)
+
+List(7,2,3,4,5,6).map(n => n * n)
+
+for (n <- List(7,2,3,4,5,6)) yield n * n
+
+// The advantages of for-comprehensions is that they
+// can be nested and also can contain guards. In such
+// cases the translations to maps and filters is a bit
+// involved. 
+
+// Task 6 (Pattern-Matching)
+
+def my_map(lst: List[Int], f: Int => Int) : List[Int] = {
+ if (lst == Nil) Nil
+ else f(lst.head) :: my_map(lst.tail, f)
+}
+
+def my_map(lst: List[Int], f: Int => Int) : List[Int] = lst macth {
+ case Nil => Nil
+ case x::xs => f(x) :: my_map(xs, f)
+}
+
+// Task 7 (Web-Crawler, hard)
+
+// see lecture2.scala
+
+// requires an accumulator that records all pages that have 
+// already been visited, for example
+
+def crawl(url: String, n: Int, acc : Set[String] = Set()) : Unit = {
+  if (n == 0) ()
+  else {
+    println(s"  Visiting: $n $url")
+    val urls = get_all_URLs(get_page(url))
+    for (u <- urls) crawl(u, n - 1, acc | urls)
+  }
+}
--- a/wsheets/wsh02.tex	Thu Nov 17 20:05:36 2022 +0000
+++ b/wsheets/wsh02.tex	Mon Nov 21 15:57:45 2022 +0000
@@ -31,76 +31,151 @@
 
 \subsection*{Task 1 (Options)}
 
-Get familiar with constructing strings and printing strings
-(i.e.~\texttt{println}, \texttt{mkString}, \texttt{toString}, \ldots)
+Get familiar with the return value of functions that can
+``go wrong'':
 
 \begin{lstlisting}[numbers=none]
 scala> List(7,2,3,4,5,6).find(_ < 4)
 scala> List(5,6,7,8,9).find(_ < 4)
-scala> 
+scala> List(5,6,7,8,9).min
+scala> List(5,6,7,8,9).minOption
+scala> List[Int]().minOption
 \end{lstlisting}
 
-\subsection*{Task 2 (URLs / Files)}
+\noindent
+Note that there needs to be a type-annotation for \texttt{List()} otherwise
+Scala will not know which \texttt{min}-version it should use. 
 
-ALso Try
+\subsection*{Task 2 (Try)}
+
+The Scala-Idiom \texttt{Try-getOrElse} allows you to conveniently
+deal with failure cases.
 
 \begin{lstlisting}[numbers=none]
-scala> 
-scala> 
-scala> 
+scala> Try(Some(List(5,6,7,8,9).min)).getOrElse(None)
+scala> Try(Some(List[Int]().min)).getOrElse(None)
 \end{lstlisting}
 
-\subsection*{Task 3 (Higher-Order Functions)}
+\noindent
+Note that \texttt{Try} needs the library \texttt{scala.util.\_} to be
+imported. 
 
-Make sure you understand if-conditions in Scala: They are
-\emph{expressions}, meaning they need to calculate a result. For
-example an \texttt{if} without an else-branch is nearly always
-\emph{not} what you intend to write (because you are not meant to
-change any ``state'' outside the expression). Also, remember the quirks
-in Scala with if-conditions needing parentheses and there is no
-\texttt{then}-keyword.
 
 \begin{lstlisting}[numbers=none]
-scala> val s1 = "foo"
-scala> val s2 = "bar"
-scala  val s3 = "foobar"
-scala> if (s1 == s2) print("equal") else print("unequal")
-scala> if (s1 ++ s2 == s3) print("equal") else print("unequal")
+def safe_div(x: Int, y: Int) : Option[Int] = 
+  Try(Some(x / y)).getOrElse(None)
+\end{lstlisting}
+
+\subsection*{Task 3 (URLs / Files)}
+
+For simple tasks such as reading webpages and files, Scala provides
+convenient functions \texttt{Source.fromURL} and \texttt{Source.fromFile}.
+To try them out, you need to import \texttt{io.Source}.
+
+\begin{lstlisting}[numbers=none]
+scala> Source.fromURL(my_url)("ISO-8859-1").mkString
+scala> Source.fromFile(my_file)("ISO-8859-1").mkString
+\end{lstlisting}
+
+\noindent
+These functions return an iterator, which can be transformed into a String
+using \texttt{mkString}. The second argument fixes the character encoding
+and should not be omitted. If you are interested in the individual lines
+in the file, for example, you can use
+
+\begin{lstlisting}[numbers=none]
+Source.fromFile(my_file)("ISO-8859-1")
+                            .getLines().toList
 \end{lstlisting}
 
-\subsection*{Task 4 (Maps)}
+\noindent
+If you are after proper error-handling, then you can use Scala's options
+as follows
+
+\begin{lstlisting}[numbers=none]
+Try(Some(Source.fromFile("test.txt")("ISO-8859-1")
+                          .mkString)).getOrElse(None)
+\end{lstlisting}  
+
+This can also be written slightly shorter as
 
-Write \texttt{for}-comprehensions that enumerate all triples containing the numbers 1 - 5. That is,
-print the list
+\begin{lstlisting}[numbers=none]
+Try(Source.fromFile("test.txt")("ISO-8859-1")
+                          .mkString).toOption
+\end{lstlisting}  
 
-\[
-\texttt{(1,1,1)}, \texttt{(2,1,1)}, \texttt{(3,1,1)} \;\ldots\; \texttt{(5,5,5)}
-\]
+\noindent
+In case of reading files, there can be an issue with closing
+files properly. For this Scala provides \texttt{Using}
+
+\begin{lstlisting}[numbers=none]
+  Using(Source.fromFile("test.txt")("ISO-8859-1"))
+                                 (_.mkString).toOption
+\end{lstlisting}  
 
 \noindent
-Modify the \texttt{for}-comprehensions such that only triples are
-printed where the sum is divisible by 3.  Then sort the list according
-to the middle element (for this use \texttt{sortBy}).
-
-\subsection*{Task 5 (Pattern-Matching)}
+This closes the files automatically after reading, but otherwise
+behaves as the code shown above: It gives a \texttt{Some} in the
+success case and \texttt{None} in the failure case. However,
+\texttt{Using} requires a function as argument for prescribing
+of what to do with the file content in the success case.
 
-\subsection*{Task 6 (Web-Crawler)}
+\subsection*{Task 4 (Higher-Order Functions)}
 
-Write a pretty print function for lists of integers which
-``condenses'' elements in the list, meaning if there is a number
-several times in a row, then print out \mbox{\texttt{n x e}}, where
-\texttt{n} is the number of repetitions and \texttt{e} is the
-number. For example
+Higher-Order functions means that Scala allows functions to
+have functions as arguments and also allows functions to
+return functions. Get familiar with the short-hand notation
+for simple functions
 
 \begin{lstlisting}[numbers=none]
-  List(1,1,1,2,3,3)    =>   List(3 x 1, 2, 2 x 3)
-  List(1,2,3,4,4,4)    =>   List(1, 2, 3, 3 x 4)
-  List(1,1,1,1,1,1)    =>   List(6 x 1)
-  List(1,1,1,2,1,1)    =>   List(3 x 1, 2, 2 x 1)
+scala> List(7,2,3,4,5,6).find(_ < 4)
+scala> List(7,2,3,4,5,6).count(_ % 2 == 0)
+scala> List(7,2,3,4,5,6).sortWith(_ > _)
+scala> List(7,2,3,4,5,6).filter(_ > 4)
 \end{lstlisting}
 
-You might like to define a separate function that first counts the occurences
-for each element and returns a list of (Int, Int)-pairs .
+\noindent
+Be aware that this short-hand notation only works for ``smallish'' functions
+and that sometimes Scala cannot figure out the types involved without
+explicit type annotations.
+
+\subsection*{Task 5 (Maps)}
+
+Get familiar with the map-function for lists, sets etc. It is the
+quintessential higher-order function and frequently used for transforming
+lists.
+
+\begin{lstlisting}[numbers=none]
+scala> List(7,2,3,4,5,6).map(n => n * n)
+\end{lstlisting}  
+
+\noindent
+Make also sure you see that Scala's \texttt{for}-comprehensions
+are just syntactic sugar for \texttt{map}s. What would this
+expression look like as \texttt{for}-comprehension? What are
+the advantages of \texttt{for}-comprehensions over \texttt{map}s.
+
+
+\subsection*{Task 6 (Pattern-Matching)}
+
+Rewrite the following function using pattern-matching
+
+\begin{lstlisting}[numbers=none]
+def my_map(lst: List[Int], f: Int => Int) : List[Int] = {
+ if (lst == Nil) Nil
+ else f(lst.head) :: my_map(lst.tail, f)
+}
+\end{lstlisting}
+
+\noindent
+Observe that the type of the function is from \texttt{Int}s to
+\texttt{Int}s, which is written in Scala as type \texttt{Int => Int}.
+
+
+\subsection*{Task 7 (Web-Crawler, Hard)}
+
+Have a look at the web-crawler at the end of \texttt{lecture2.scala}.
+Can you modify it such that every page is only visited once? 
 
 \end{document}