1 // Scala Lecture 2 |
1 // Scala Lecture 2 |
2 //================= |
2 //================= |
3 |
3 |
|
4 // UNFINISHED BUSINESS from Lecture 1 |
|
5 //==================================== |
|
6 |
|
7 |
|
8 // for measuring time |
|
9 def time_needed[T](n: Int, code: => T) = { |
|
10 val start = System.nanoTime() |
|
11 for (i <- (0 to n)) code |
|
12 val end = System.nanoTime() |
|
13 (end - start) / 1.0e9 |
|
14 } |
|
15 |
|
16 |
|
17 val list = (1 to 1000000).toList |
|
18 time_needed(10, for (n <- list) yield n + 42) |
|
19 time_needed(10, for (n <- list.par) yield n + 42) |
|
20 |
|
21 |
|
22 // Just for "Fun": Mutable vs Immutable |
|
23 //======================================= |
|
24 // |
|
25 // - no vars, no ++i, no += |
|
26 // - no mutable data-structures (no Arrays, no ListBuffers) |
|
27 |
|
28 |
|
29 // Q: Count how many elements are in the intersections of two sets? |
|
30 |
|
31 def count_intersection(A: Set[Int], B: Set[Int]) : Int = { |
|
32 var count = 0 |
|
33 for (x <- A; if B contains x) count += 1 |
|
34 count |
|
35 } |
|
36 |
|
37 val A = (1 to 1000).toSet |
|
38 val B = (1 to 1000 by 4).toSet |
|
39 |
|
40 count_intersection(A, B) |
|
41 |
|
42 // but do not try to add .par to the for-loop above |
|
43 |
|
44 |
|
45 //propper parallel version |
|
46 def count_intersection2(A: Set[Int], B: Set[Int]) : Int = |
|
47 A.par.count(x => B contains x) |
|
48 |
|
49 count_intersection2(A, B) |
|
50 |
|
51 |
|
52 val A = (1 to 1000000).toSet |
|
53 val B = (1 to 1000000 by 4).toSet |
|
54 |
|
55 time_needed(100, count_intersection(A, B)) |
|
56 time_needed(100, count_intersection2(A, B)) |
|
57 |
|
58 |
|
59 |
|
60 // For-Comprehensions Again |
|
61 //========================== |
|
62 |
|
63 // the first produces a result, while the second does not |
|
64 for (n <- List(1, 2, 3, 4, 5)) yield n * n |
|
65 |
|
66 |
|
67 for (n <- List(1, 2, 3, 4, 5)) println(n) |
|
68 |
|
69 |
|
70 |
|
71 // Higher-Order Functions |
|
72 //======================== |
|
73 |
|
74 // functions can take functions as arguments |
|
75 |
|
76 def even(x: Int) : Boolean = x % 2 == 0 |
|
77 def odd(x: Int) : Boolean = x % 2 == 1 |
|
78 |
|
79 val lst = (1 to 10).toList |
|
80 |
|
81 lst.filter(x => even(x)) |
|
82 lst.filter(even(_)) |
|
83 lst.filter(even) |
|
84 |
|
85 lst.count(even) |
|
86 |
|
87 lst.find(_ > 8) |
|
88 |
|
89 |
|
90 val ps = List((3, 0), (3, 2), (4, 2), (2, 0), (1, 1), (1, 0)) |
|
91 |
|
92 ps.sortBy(_._1) |
|
93 ps.sortBy(_._2) |
|
94 |
|
95 ps.maxBy(_._1) |
|
96 ps.maxBy(_._2) |
|
97 |
|
98 |
|
99 |
|
100 // maps |
|
101 //===== |
|
102 |
|
103 def square(x: Int): Int = x * x |
|
104 |
|
105 val lst = (1 to 10).toList |
|
106 |
|
107 lst.map(square) |
|
108 |
|
109 // this is actually what for is defined at in Scala |
|
110 |
|
111 lst.map(n => square(n)) |
|
112 for (n <- lst) yield square(n) |
|
113 |
|
114 // this can be iterated |
|
115 |
|
116 lst.map(square).filter(_ > 4) |
|
117 |
|
118 lst.map(square).filter(_ > 4).map(square) |
|
119 |
|
120 |
|
121 // lets define our own functions |
|
122 // type of functions, for example f: Int => Int |
|
123 |
|
124 def my_map_int(lst: List[Int], f: Int => Int) : List[Int] = { |
|
125 if (lst == Nil) Nil |
|
126 else f(lst.head) :: my_map_int(lst.tail, f) |
|
127 } |
|
128 |
|
129 my_map_int(lst, square) |
|
130 |
|
131 |
|
132 // same function using pattern matching: a kind |
|
133 // of switch statement on steroids (see more later on) |
|
134 |
|
135 def my_map_int(lst: List[Int], f: Int => Int) : List[Int] = lst match { |
|
136 case Nil => Nil |
|
137 case x::xs => f(x)::my_map_int(xs, f) |
|
138 } |
|
139 |
|
140 |
|
141 // other function types |
|
142 // |
|
143 // f1: (Int, Int) => Int |
|
144 // f2: List[String] => Option[Int] |
|
145 // ... |
|
146 |
|
147 |
|
148 def sumOf(f: Int => Int, lst: List[Int]): Int = lst match { |
|
149 case Nil => 0 |
|
150 case x::xs => f(x) + sumOf(f, xs) |
|
151 } |
|
152 |
|
153 def sum_squares(lst: List[Int]) = sumOf(square, lst) |
|
154 def sum_cubes(lst: List[Int]) = sumOf(x => x * x * x, lst) |
|
155 |
|
156 sum_squares(lst) |
|
157 sum_cubes(lst) |
|
158 |
|
159 // lets try it factorial |
|
160 def fact(n: Int) : Int = ... |
|
161 |
|
162 def sum_fact(lst: List[Int]) = sumOf(fact, lst) |
|
163 sum_fact(lst) |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 // Map type |
|
170 //========== |
|
171 |
|
172 // Note the difference between map and Map |
|
173 |
|
174 def factors(n: Int) : List[Int] = |
|
175 ((1 until n).filter { divisor => |
|
176 n % divisor == 0 |
|
177 }).toList |
|
178 |
|
179 |
|
180 var ls = (1 to 10).toList |
|
181 |
|
182 val facs = ls.map(n => (n, factors(n))) |
|
183 |
|
184 facs.find(_._1 == 4) |
|
185 |
|
186 // works for lists of pairs |
|
187 facs.toMap |
|
188 |
|
189 |
|
190 facs.toMap.get(4) |
|
191 facs.toMap.getOrElse(4, Nil) |
|
192 |
|
193 val facsMap = facs.toMap |
|
194 |
|
195 val facsMap0 = facsMap + (0 -> List(1,2,3,4,5)) |
|
196 facsMap0.get(0) |
|
197 |
|
198 val facsMap4 = facsMap + (1 -> List(1,2,3,4,5)) |
|
199 facsMap.get(1) |
|
200 facsMap4.get(1) |
|
201 |
|
202 val ls = List("one", "two", "three", "four", "five") |
|
203 ls.groupBy(_.length) |
|
204 |
|
205 ls.groupBy(_.length).get(3) |
|
206 |
|
207 |
4 |
208 |
5 // Option type |
209 // Option type |
6 //============= |
210 //============= |
7 |
211 |
8 //in Java if something unusually happens, you return null; |
212 //in Java if something unusually happens, you return null; |
|
213 // |
9 //in Scala you use Option |
214 //in Scala you use Option |
10 // - if the value is present, you use Some(value) |
215 // - if the value is present, you use Some(value) |
11 // - if no value is present, you use None |
216 // - if no value is present, you use None |
12 |
217 |
13 |
218 |
14 List(7,2,3,4,5,6).find(_ < 4) |
219 List(7,2,3,4,5,6).find(_ < 4) |
15 List(5,6,7,8,9).find(_ < 4) |
220 List(5,6,7,8,9).find(_ < 4) |
16 |
221 |
17 |
222 // operations on options |
18 // Values in types |
|
19 // |
|
20 // Boolean: |
|
21 // Int: |
|
22 // String: |
|
23 // |
|
24 // Option[String]: |
|
25 // |
|
26 |
|
27 |
223 |
28 val lst = List(None, Some(1), Some(2), None, Some(3)) |
224 val lst = List(None, Some(1), Some(2), None, Some(3)) |
29 |
225 |
30 lst.flatten |
226 lst.flatten |
31 |
227 |
222 |
372 |
223 |
373 |
224 // User-defined Datatypes |
374 // User-defined Datatypes |
225 //======================== |
375 //======================== |
226 |
376 |
227 abstract class Tree |
377 |
228 case class Node(elem: Int, left: Tree, right: Tree) extends Tree |
378 abstract class Colour |
229 case class Leaf() extends Tree |
379 case object Red extends Colour |
230 |
380 case object Green extends Colour |
231 |
381 case object Blue extends Colour |
232 def insert(tr: Tree, n: Int): Tree = tr match { |
382 |
233 case Leaf() => Node(n, Leaf(), Leaf()) |
383 def fav_colour(c: Colour) : Boolean = c match { |
234 case Node(m, left, right) => |
384 case Red => false |
235 if (n == m) Node(m, left, right) |
385 case Green => true |
236 else if (n < m) Node(m, insert(left, n), right) |
386 case Blue => false |
237 else Node(m, left, insert(right, n)) |
387 } |
238 } |
388 |
239 |
389 fav_colour(Green) |
240 |
390 |
241 val t1 = Node(4, Node(2, Leaf(), Leaf()), Node(7, Leaf(), Leaf())) |
391 |
242 insert(t1, 3) |
392 // ... a bit more useful: Roman Numerals |
243 |
393 |
244 def depth(tr: Tree): Int = tr match { |
394 abstract class RomanDigit |
245 case Leaf() => 0 |
395 case object I extends RomanDigit |
246 case Node(_, left, right) => 1 + List(depth(left), depth(right)).max |
396 case object V extends RomanDigit |
247 } |
397 case object X extends RomanDigit |
248 |
398 case object L extends RomanDigit |
249 |
399 case object C extends RomanDigit |
250 def balance(tr: Tree): Int = tr match { |
400 case object D extends RomanDigit |
251 case Leaf() => 0 |
401 case object M extends RomanDigit |
252 case Node(_, left, right) => depth(left) - depth(right) |
402 |
253 } |
403 type RomanNumeral = List[RomanDigit] |
254 |
404 |
255 balance(insert(t1, 3)) |
405 def RomanNumeral2Int(rs: RomanNumeral): Int = rs match { |
|
406 case Nil => 0 |
|
407 case M::r => 1000 + RomanNumeral2Int(r) |
|
408 case C::M::r => 900 + RomanNumeral2Int(r) |
|
409 case D::r => 500 + RomanNumeral2Int(r) |
|
410 case C::D::r => 400 + RomanNumeral2Int(r) |
|
411 case C::r => 100 + RomanNumeral2Int(r) |
|
412 case X::C::r => 90 + RomanNumeral2Int(r) |
|
413 case L::r => 50 + RomanNumeral2Int(r) |
|
414 case X::L::r => 40 + RomanNumeral2Int(r) |
|
415 case X::r => 10 + RomanNumeral2Int(r) |
|
416 case I::X::r => 9 + RomanNumeral2Int(r) |
|
417 case V::r => 5 + RomanNumeral2Int(r) |
|
418 case I::V::r => 4 + RomanNumeral2Int(r) |
|
419 case I::r => 1 + RomanNumeral2Int(r) |
|
420 } |
|
421 |
|
422 RomanNumeral2Int(List(I,V)) // 4 |
|
423 RomanNumeral2Int(List(I,I,I,I)) // 4 (invalid Roman number) |
|
424 RomanNumeral2Int(List(V,I)) // 6 |
|
425 RomanNumeral2Int(List(I,X)) // 9 |
|
426 RomanNumeral2Int(List(M,C,M,L,X,X,I,X)) // 1979 |
|
427 RomanNumeral2Int(List(M,M,X,V,I,I)) // 2017 |
|
428 |
256 |
429 |
257 // another example |
430 // another example |
|
431 //================= |
|
432 |
|
433 // Once upon a time, in a complete fictional country there were Persons... |
|
434 |
258 |
435 |
259 abstract class Person |
436 abstract class Person |
260 case class King() extends Person |
437 case object King extends Person |
261 case class Peer(deg: String, terr: String, succ: Int) extends Person |
438 case class Peer(deg: String, terr: String, succ: Int) extends Person |
262 case class Knight(name: String) extends Person |
439 case class Knight(name: String) extends Person |
263 case class Peasant(name: String) extends Person |
440 case class Peasant(name: String) extends Person |
264 case class Clown() extends Person |
441 case object Clown extends Person |
265 |
442 |
266 def title(p: Person): String = p match { |
443 def title(p: Person): String = p match { |
267 case King() => "His Majesty the King" |
444 case King => "His Majesty the King" |
268 case Peer(deg, terr, _) => s"The ${deg} of ${terr}" |
445 case Peer(deg, terr, _) => s"The ${deg} of ${terr}" |
269 case Knight(name) => s"Sir ${name}" |
446 case Knight(name) => s"Sir ${name}" |
270 case Peasant(name) => name |
447 case Peasant(name) => name |
271 } |
448 } |
272 |
449 |
273 def superior(p1: Person, p2: Person): Boolean = (p1, p2) match { |
450 def superior(p1: Person, p2: Person): Boolean = (p1, p2) match { |
274 case (King(), _) => true |
451 case (King, _) => true |
275 case (Peer(_,_,_), Knight(_)) => true |
452 case (Peer(_,_,_), Knight(_)) => true |
276 case (Peer(_,_,_), Peasant(_)) => true |
453 case (Peer(_,_,_), Peasant(_)) => true |
277 case (Peer(_,_,_), Clown()) => true |
454 case (Peer(_,_,_), Clown) => true |
278 case (Knight(_), Peasant(_)) => true |
455 case (Knight(_), Peasant(_)) => true |
279 case (Knight(_), Clown()) => true |
456 case (Knight(_), Clown) => true |
280 case (Clown(), Peasant(_)) => true |
457 case (Clown, Peasant(_)) => true |
281 case _ => false |
458 case _ => false |
282 } |
459 } |
283 |
460 |
284 val people = List(Knight("David"), |
461 val people = List(Knight("David"), |
285 Peer("Duke", "Norfolk", 84), |
462 Peer("Duke", "Norfolk", 84), |
286 Peasant("Christian"), |
463 Peasant("Christian"), |
287 King(), |
464 King, |
288 Clown()) |
465 Clown) |
289 |
466 |
290 println(people.sortWith(superior(_, _)).mkString(", ")) |
467 println(people.sortWith(superior(_, _)).mkString(", ")) |
291 |
468 |
292 |
469 |
293 |
470 // Tail recursion |
294 // Higher-Order Functions |
471 //================ |
295 //======================== |
472 |
296 |
473 |
297 // functions can take functions as arguments |
474 def fact(n: Long): Long = |
298 |
475 if (n == 0) 1 else n * fact(n - 1) |
299 val lst = (1 to 10).toList |
476 |
300 |
477 fact(10) //ok |
301 def even(x: Int): Boolean = x % 2 == 0 |
478 fact(10000) // produces a stackoverflow |
302 def odd(x: Int): Boolean = x % 2 == 1 |
479 |
303 |
480 def factT(n: BigInt, acc: BigInt): BigInt = |
304 lst.filter(x => even(x)) |
481 if (n == 0) acc else factT(n - 1, n * acc) |
305 lst.filter(even(_)) |
482 |
306 lst.filter(even) |
483 factT(10, 1) |
307 |
484 factT(100000, 1) |
308 lst.find(_ > 8) |
485 |
309 |
486 // there is a flag for ensuring a function is tail recursive |
310 def square(x: Int): Int = x * x |
487 import scala.annotation.tailrec |
311 |
488 |
312 lst.map(square) |
489 @tailrec |
313 |
490 def factT(n: BigInt, acc: BigInt): BigInt = |
314 lst.map(square).filter(_ > 4) |
491 if (n == 0) acc else factT(n - 1, n * acc) |
315 |
492 |
316 lst.map(square).filter(_ > 4).map(square) |
493 |
317 |
494 |
318 // in my collatz.scala |
495 // for tail-recursive functions the Scala compiler |
319 //(1 to bnd).map(i => (collatz(i), i)).maxBy(_._1) |
496 // generates loop-like code, which does not need |
320 |
497 // to allocate stack-space in each recursive |
321 |
498 // call; Scala can do this only for tail-recursive |
322 // type of functions, for example f: Int => Int |
499 // functions |
323 |
500 |
324 def my_map_int(lst: List[Int], f: Int => Int): List[Int] = lst match { |
501 |
325 case Nil => Nil |
502 // A Web Crawler |
326 case x::xs => f(x)::my_map_int(xs, f) |
503 //=============== |
327 } |
504 // |
328 |
505 // the idea is to look for dead links using the |
329 my_map_int(lst, square) |
506 // regular expression "https?://[^"]*" |
330 |
507 |
331 // other function types |
508 import io.Source |
332 // |
509 import scala.util._ |
333 // f1: (Int, Int) => Int |
510 |
334 // f2: List[String] => Option[Int] |
511 // gets the first 10K of a web-page |
335 // ... |
512 def get_page(url: String) : String = { |
336 |
513 Try(Source.fromURL(url)("ISO-8859-1").take(10000).mkString). |
337 |
514 getOrElse { println(s" Problem with: $url"); ""} |
338 def sumOf(f: Int => Int, lst: List[Int]): Int = lst match { |
515 } |
339 case Nil => 0 |
516 |
340 case x::xs => f(x) + sumOf(f, xs) |
517 // regex for URLs and emails |
341 } |
518 val http_pattern = """"https?://[^"]*"""".r |
342 |
519 val email_pattern = """([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})""".r |
343 def sum_squares(lst: List[Int]) = sumOf(square, lst) |
520 |
344 def sum_cubes(lst: List[Int]) = sumOf(x => x * x * x, lst) |
521 |
345 |
522 // drops the first and last character from a string |
346 sum_squares(lst) |
523 def unquote(s: String) = s.drop(1).dropRight(1) |
347 sum_cubes(lst) |
524 |
348 |
525 def get_all_URLs(page: String): Set[String] = |
349 // lets try it factorial |
526 http_pattern.findAllIn(page).map(unquote).toSet |
350 def fact(n: Int): Int = ... |
527 |
351 |
528 // naive version of crawl - searches until a given depth, |
352 def sum_fact(lst: List[Int]) = sumOf(fact, lst) |
529 // visits pages potentially more than once |
353 sum_fact(lst) |
530 def crawl(url: String, n: Int) : Set[String] = { |
354 |
531 if (n == 0) Set() |
355 // Avoid being mutable |
532 else { |
356 //===================== |
533 println(s" Visiting: $n $url") |
357 |
534 val page = get_page(url) |
358 // a student showed me... |
535 val new_emails = email_pattern.findAllIn(page).toSet |
359 import scala.collection.mutable.ListBuffer |
536 new_emails ++ (for (u <- get_all_URLs(page).par) yield crawl(u, n - 1)).flatten |
360 |
537 } |
361 |
538 } |
362 |
539 |
363 def collatz_max(bnd: Long): (Long, Long) = { |
540 // some starting URLs for the crawler |
364 val colNos = ListBuffer[(Long, Long)]() |
541 val startURL = """https://nms.kcl.ac.uk/christian.urban/""" |
365 for (i <- (1L to bnd).toList) colNos += ((collatz(i), i)) |
542 |
366 colNos.max |
543 crawl(startURL, 2) |
367 } |
544 |
368 |
545 |
369 def collatz_max(bnd: Long): (Long, Long) = { |
546 |
370 (1L to bnd).map((i) => (collatz(i), i)).maxBy(_._1) |
547 |
371 } |
|
372 |
|
373 //views -> lazy collection |
|
374 def collatz_max(bnd: Long): (Long, Long) = { |
|
375 (1L to bnd).view.map((i) => (collatz(i), i)).maxBy(_._1) |
|
376 } |
|
377 |
|
378 // raises a GC exception |
|
379 (1 to 1000000000).filter(_ % 2 == 0).take(10).toList |
|
380 // ==> java.lang.OutOfMemoryError: GC overhead limit exceeded |
|
381 |
|
382 (1 to 1000000000).view.filter(_ % 2 == 0).take(10).toList |
|
383 |
548 |
384 |
549 |
385 |
550 |
386 // Sudoku |
551 // Sudoku |
387 //======== |
552 //======== |