├── .gitignore ├── README.md ├── build.sbt ├── project ├── build.properties └── plugins.sbt └── src ├── main ├── java │ ├── Chapter02Task4.java │ ├── Chapter05Car.java │ ├── Chapter15Task4.java │ └── Chapter15Task5.java ├── resources │ ├── Chapter16Task04.html │ ├── Chapter16Task10.xhtml │ ├── FPinScalaCover.png │ ├── myfile.txt │ └── scaladays.png └── scala │ ├── Chapter01.scala │ ├── Chapter02.scala │ ├── Chapter03.scala │ ├── Chapter04.scala │ ├── Chapter05.scala │ ├── Chapter06.scala │ ├── Chapter07.scala │ ├── Chapter0701a.scala │ ├── Chapter0701b.scala │ ├── Chapter08.scala │ ├── Chapter09.scala │ ├── Chapter10.scala │ ├── Chapter11.scala │ ├── Chapter12.scala │ ├── Chapter13.scala │ ├── Chapter14.scala │ ├── Chapter15.scala │ ├── Chapter16.scala │ ├── Chapter17.scala │ ├── Chapter18.scala │ ├── Chapter19.scala │ ├── Chapter20.scala │ ├── Chapter21.scala │ └── Utils.scala └── test └── scala ├── Chapter01Spec.scala ├── Chapter02Spec.scala ├── Chapter03Spec.scala ├── Chapter04Spec.scala ├── Chapter05Spec.scala ├── Chapter06Spec.scala ├── Chapter07Spec.scala ├── Chapter08Spec.scala ├── Chapter09Spec.scala ├── Chapter10Spec.scala ├── Chapter11Spec.scala ├── Chapter12Spec.scala ├── Chapter13Spec.scala ├── Chapter14Spec.scala ├── Chapter15Spec.scala ├── Chapter16Spec.scala ├── Chapter17Spec.scala ├── Chapter18Spec.scala ├── Chapter19Spec.scala ├── Chapter20Spec.scala ├── Chapter21Spec.scala └── TestUtils.scala /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | release.properties 7 | 8 | *.iml 9 | .idea 10 | .project 11 | .classpath 12 | .DS_Store 13 | *.log 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Scala for the Impatient 2 | My solutions to the exercises from the excellent [Scala for the Impatient](http://horstmann.com/scala/) book. 3 | 4 | 1. [The Basics](src/main/scala/Chapter01.scala) => [tests](src/test/scala/Chapter01Spec.scala) 5 | 2. [Control Structures and Functions](src/main/scala/Chapter02.scala) => [tests](src/test/scala/Chapter02Spec.scala) 6 | 3. [Working with Arrays](src/main/scala/Chapter03.scala) => [tests](src/test/scala/Chapter03Spec.scala) 7 | 4. [Maps and Tuples](src/main/scala/Chapter04.scala) => [tests](src/test/scala/Chapter04Spec.scala) 8 | 5. [Classes](src/main/scala/Chapter05.scala) ([task 5 java](src/main/java/Chapter05Car.java)) => [tests](src/test/scala/Chapter05Spec.scala) 9 | 6. [Objects](src/main/scala/Chapter06.scala) => [tests](src/test/scala/Chapter06Spec.scala) 10 | 7. [Packages and Imports](src/main/scala/Chapter07.scala) ([task 1a](src/main/scala/Chapter0701a.scala), [1b](src/main/scala/Chapter0701b.scala)) => [tests](src/test/scala/Chapter07Spec.scala) 11 | 8. [Inheritance](src/main/scala/Chapter08.scala) => [tests](src/test/scala/Chapter08Spec.scala) 12 | 9. [Files and Regular Expressions](src/main/scala/Chapter09.scala) => [tests](src/test/scala/Chapter09Spec.scala) 13 | 10. [Traits](src/main/scala/Chapter10.scala) => [tests](src/test/scala/Chapter10Spec.scala) 14 | 11. [Operators](src/main/scala/Chapter11.scala) => [tests](src/test/scala/Chapter11Spec.scala) 15 | 12. [Higher-order functions](src/main/scala/Chapter12.scala) => [tests](src/test/scala/Chapter12Spec.scala) 16 | 13. [Collections](src/main/scala/Chapter13.scala) => [tests](src/test/scala/Chapter13Spec.scala) 17 | 14. [Pattern matching and case classes](src/main/scala/Chapter14.scala) => [tests](src/test/scala/Chapter14Spec.scala) 18 | 15. [Annotations](src/main/scala/Chapter15.scala) ([task 4 java](src/main/java/Chapter15Task4.java), [5 java](src/main/java/Chapter15Task5.java)) => [tests](src/test/scala/Chapter15Spec.scala) 19 | 16. [XML Processing](src/main/scala/Chapter16.scala) => [tests](src/test/scala/Chapter16Spec.scala) 20 | 17. [Type parameters](src/main/scala/Chapter17.scala) => [tests](src/test/scala/Chapter17Spec.scala) 21 | 18. [Advanced types](src/main/scala/Chapter18.scala) => [tests](src/test/scala/Chapter18Spec.scala) 22 | 19. [Parsing](src/main/scala/Chapter19.scala) => [tests](src/test/scala/Chapter19Spec.scala) 23 | 20. [Actors](src/main/scala/Chapter20.scala) => [tests](src/test/scala/Chapter20Spec.scala) 24 | 21. [Implicits](src/main/scala/Chapter21.scala) => [tests](src/test/scala/Chapter21Spec.scala) 25 | 22. Delimited continuations 26 | 27 | Almost all solutions are covered with corresponding tests. 28 | You can build and run tests and see the coverage by using the following command: 29 | 30 | ```bash 31 | activator clean coverage test coverageReport 32 | ``` 33 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | 2 | name := "scala-impatient" 3 | 4 | organization := "com.github.viktor-podzigun" 5 | 6 | version := "1.0-SNAPSHOT" 7 | 8 | scalaVersion := "2.11.7" 9 | 10 | //fork := false 11 | 12 | // to run coverage tests use: 13 | // 14 | // activator clean coverage test coverageReport 15 | // 16 | // 17 | //coverageEnabled := true 18 | 19 | coverageMinimum := 80 20 | 21 | coverageFailOnMinimum := true 22 | 23 | scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature") 24 | 25 | libraryDependencies ++= Seq( 26 | "org.scala-lang.modules" %% "scala-xml" % "1.0.2", 27 | "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.4", 28 | "org.scala-lang" % "scala-actors" % "2.11.5", 29 | "junit" % "junit" % "4.11", 30 | "org.hamcrest" % "hamcrest-all" % "1.3", 31 | "org.scala-lang" % "scala-reflect" % "2.11.7" % "test", 32 | "org.scalatest" %% "scalatest" % "2.2.4" % "test" 33 | ) 34 | 35 | resolvers ++= Seq( 36 | // "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots", 37 | // "Sonatype OSS Releases" at "https://oss.sonatype.org/content/repositories/releases", 38 | "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/" 39 | ) 40 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.5 -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | 2 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.3.5") 3 | -------------------------------------------------------------------------------- /src/main/java/Chapter02Task4.java: -------------------------------------------------------------------------------- 1 | 2 | public class Chapter02Task4 { 3 | 4 | public static void javaForLoop() { 5 | for (int i = 10; i >= 0; i--) { 6 | // use scala output to be able to redirect the output and test the results 7 | scala.Console.println(i); 8 | //System.out.println(i); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/Chapter05Car.java: -------------------------------------------------------------------------------- 1 | 2 | public class Chapter05Car { 3 | 4 | private final String manufacturer; 5 | private final String modelName; 6 | private final int modelYear; 7 | private final String licensePlate; 8 | 9 | public Chapter05Car(final String manufacturer, final String modelName) { 10 | this(manufacturer, modelName, -1); 11 | } 12 | 13 | public Chapter05Car(final String manufacturer, final String modelName, final int modelYear) { 14 | this(manufacturer, modelName, modelYear, ""); 15 | } 16 | 17 | public Chapter05Car(final String manufacturer, 18 | final String modelName, 19 | final String licensePlate) { 20 | 21 | this(manufacturer, modelName, -1, licensePlate); 22 | } 23 | 24 | public Chapter05Car(final String manufacturer, 25 | final String modelName, 26 | final int modelYear, 27 | final String licensePlate) { 28 | 29 | this.manufacturer = manufacturer; 30 | this.modelName = modelName; 31 | this.modelYear = modelYear; 32 | this.licensePlate = licensePlate; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/Chapter15Task4.java: -------------------------------------------------------------------------------- 1 | 2 | public class Chapter15Task4 { 3 | 4 | public static int scalaSum(final int... args) { 5 | return Chapter15.sum(args); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/Chapter15Task5.java: -------------------------------------------------------------------------------- 1 | import java.io.IOException; 2 | 3 | public class Chapter15Task5 { 4 | 5 | public static String scalaFileToString(final String file) { 6 | try { 7 | return Chapter15.fileToString(file); 8 | } catch (final IOException e) { 9 | throw new RuntimeException(e); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/resources/Chapter16Task04.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | 5 | 6 | 7 |

8 | Ref 2 9 |
10 | 11 | 12 | Ref 3 13 |
14 | Some text 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/resources/Chapter16Task10.xhtml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Sample XHTML document 7 | 8 | 9 | 10 |
11 | [CDATA[should be preserved]] 12 |
13 |

14 | 15 | 16 | 17 |

18 |

19 | Ref 2 20 |

21 |
22 | 23 | 24 | Ref 3 25 |
26 |

27 | Some text 28 |

29 | 30 | 31 | -------------------------------------------------------------------------------- /src/main/resources/FPinScalaCover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viktor-podzigun/scala-impatient/9569bce74837102f7a98bfea828d4f6af6788f3a/src/main/resources/FPinScalaCover.png -------------------------------------------------------------------------------- /src/main/resources/myfile.txt: -------------------------------------------------------------------------------- 1 | 2 | Simple text file with example words. 3 | We will parse the file and count the words. 4 | -------------------------------------------------------------------------------- /src/main/resources/scaladays.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viktor-podzigun/scala-impatient/9569bce74837102f7a98bfea828d4f6af6788f3a/src/main/resources/scaladays.png -------------------------------------------------------------------------------- /src/main/scala/Chapter01.scala: -------------------------------------------------------------------------------- 1 | import scala.math.BigInt 2 | 3 | object Chapter01 { 4 | 5 | /** 6 | * Task 1: 7 | * 8 | * In the Scala REPL, type `3.` followed by the Tab key. What methods can be applied? 9 | * 10 | * Solution: 11 | * 12 | * On my machine, when typing `3.` and pressing the Tab key for the first time, 13 | * these methods are available: 14 | * {{{ 15 | * % & * + - / > >= >> >>> ^ asInstanceOf isInstanceOf 16 | * toByte toChar toDouble toFloat toInt toLong toShort toString 17 | * unary_+ unary_- unary_~ | 18 | * }}} 19 | * And when pressing the Tab key the second time: 20 | * {{{ 21 | * != % * - < <= == >= >>> asInstanceOf getClass isInstanceOf 22 | * toChar toFloat toLong toString unary_- | 23 | * ## & + / << > >> ^ equals hashCode 24 | * toByte toDouble toInt toShort unary_+ unary_~ 25 | * }}} 26 | */ 27 | 28 | /** 29 | * Task 2: 30 | * 31 | * In the Scala REPL, compute the square root of 3, and then square that value. 32 | * By how much does the result differ from 3? (Hint: The res variables are your friend.) 33 | * 34 | * Solution: 35 | * 36 | * {{{ 37 | * scala> Math.sqrt(3) 38 | * res0: Double = 1.7320508075688772 39 | * 40 | * scala> res0 * res0 41 | * res1: Double = 2.9999999999999996 42 | * 43 | * scala> 3 - res1 44 | * res2: Double = 4.440892098500626E-16 45 | * }}} 46 | */ 47 | 48 | /** 49 | * Task 3: 50 | * 51 | * Are the res variables `val` or `var` ? 52 | * 53 | * Solution: 54 | * 55 | * They are `val`: 56 | * {{{ 57 | * scala> res2 = 1.0 58 | * :13: error: reassignment to val 59 | * res2 = 1.0 60 | * ^ 61 | * }}} 62 | */ 63 | 64 | /** 65 | * Task 4: 66 | * 67 | * Scala lets you multiply a string with a number - try out `"crazy" * 3` in the REPL. 68 | * What does this operation do? Where can you find it in `Scaladoc`? 69 | * 70 | * Solution: 71 | * 72 | * This operation concatenates the given string to itself the given number of times: 73 | * {{{ 74 | * scala> "crazy" * 3 75 | * res3: String = crazycrazycrazy 76 | * }}} 77 | * It can be found in `Scaladoc` by searching for `StringOps` class, it has the following 78 | * method: 79 | * {{{ 80 | * def *(n: Int): String 81 | * Return the current string concatenated n times. 82 | * }}} 83 | */ 84 | 85 | /** 86 | * Task 5: 87 | * 88 | * What does `10 max 2` mean? In which class is the `max` method defined? 89 | * 90 | * Solution: 91 | * 92 | * It compares two integer values and returns the maximum value. 93 | * The `max` method is defined in `RichInt` class. 94 | * The `Int` value `10` is first converted to a `RichInt` and then the `max` method is called 95 | * on that value. 96 | */ 97 | 98 | /** 99 | * Task 6: 100 | * 101 | * Using `BigInt`, compute `2^^1024`. 102 | */ 103 | def computeBigInt(): BigInt = BigInt(2).pow(1024) 104 | 105 | /** 106 | * Task 7: 107 | * 108 | * What do you need to `import` so that you can get a random prime as 109 | * `probablePrime(100, Random)`, without any qualifiers before `probablePrime` and `Random`? 110 | */ 111 | def task7(): BigInt = { 112 | import scala.BigInt._ 113 | import scala.util._ 114 | 115 | probablePrime(100, Random) 116 | } 117 | 118 | /** 119 | * Task 8: 120 | * 121 | * One way to create random file or directory names is to produce a random `BigInt` 122 | * and convert it to base `36`, yielding a `string` such as "qsnvbevtomcj38o06kul". 123 | * Poke around `Scaladoc` to find a way of doing this in Scala. 124 | */ 125 | def randomFileName(): String = util.Random.alphanumeric.take(20).mkString.toLowerCase 126 | 127 | /** 128 | * Task 9: 129 | * 130 | * How do you get the first character of a string in Scala? The last character? 131 | */ 132 | def task9(): Unit = { 133 | val str = "Some string" 134 | 135 | val first = str(0) 136 | val first2 = str.head 137 | 138 | val last = str(str.length - 1) 139 | val last2 = str.last 140 | } 141 | 142 | /** 143 | * Task 10: 144 | * 145 | * What do the `take`, `drop`, `takeRight`, and `dropRight` string functions do? 146 | * What advantage or disadvantage do they have over using `substring`? 147 | * 148 | * Solution: 149 | * 150 | * The advantage is that they don't throw `IndexOutOfBoundsException`. 151 | * See examples below: 152 | */ 153 | def task10(): Unit = { 154 | val str = "Some string" 155 | 156 | val first10 = str.take(15) // "Some string", no IndexOutOfBoundsException exception ! 157 | val empty = "".drop(5) // "", no IndexOutOfBoundsException exception ! 158 | 159 | val take = str.take(4) // "Some" 160 | val drop = str.drop(5) // "string" 161 | 162 | val takeRight = str.takeRight(6) // "string" 163 | val dropRight = str.dropRight(7) // "Some" 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/main/scala/Chapter02.scala: -------------------------------------------------------------------------------- 1 | import scala.math.Numeric.IntIsIntegral 2 | 3 | object Chapter02 { 4 | 5 | /** 6 | * Task 1: 7 | * 8 | * The `signum` of a number is `1` if the number is positive, `–1` if it is negative, 9 | * and `0` if it is zero. Write a function that computes this value. 10 | */ 11 | def signum(n: Int): Int = { 12 | if (n < 0) -1 13 | else if (n > 0) 1 14 | else 0 15 | } 16 | 17 | /** 18 | * Task 2: 19 | * 20 | * What is the value of an empty block expression `{}`? What is its type? 21 | * 22 | * Solution: 23 | * 24 | * The value of an empty block expression `{}` is `()` and it's type is `scala.Unit`. 25 | * Which is equivalent to `java.lang.Void`. 26 | */ 27 | def task2(): Unit = {} 28 | 29 | /** 30 | * Task 3: 31 | * 32 | * Come up with one situation where the assignment `x = y = 1` is valid in Scala. 33 | * (Hint: Pick a suitable type for `x`.) 34 | */ 35 | def task3(): Unit = { 36 | var y: Int = 0 37 | var x: Unit = () 38 | x = y = 1 39 | } 40 | 41 | /** 42 | * Task 4: 43 | * 44 | * Write a `Scala` equivalent for the `Java` loop 45 | * {{{ 46 | * for (int i = 10; i >= 0; i--) System.out.println(i); 47 | * }}} 48 | */ 49 | def task4(): Unit = for (i <- 10 to (0, -1)) println(i) 50 | 51 | /** 52 | * Task 5: 53 | * 54 | * Write a procedure `countdown(n: Int)` that prints the numbers from n to 0. 55 | */ 56 | def countdown(n: Int): Unit = for (i <- n to (0, -1)) println(i) 57 | 58 | /** 59 | * Task 6: 60 | * 61 | * Write a `for` loop for computing the product of the Unicode codes of all letters 62 | * in a string. For example, the product of the characters in "Hello" is 825152896. 63 | */ 64 | def productLoop(s: String): Int = { 65 | var result = 1 66 | for (c <- s) result *= c.toInt 67 | 68 | result 69 | } 70 | 71 | /** 72 | * Task 7: 73 | * 74 | * Solve the preceding exercise without writing a loop. 75 | * (Hint: Look at the `StringOps` Scaladoc.) 76 | */ 77 | def productNoLoop(s: String): Int = s.map(_.toInt).product 78 | 79 | /** 80 | * Task 8: 81 | * 82 | * Write a function `product(s : String)` that computes the product, as described 83 | * in the preceding exercises. 84 | */ 85 | def product(s : String): Int = productLoop(s) 86 | 87 | /** 88 | * Task 9: 89 | * 90 | * Make the function of the preceding exercise a recursive function. 91 | */ 92 | def productRecursive(s : String): Int = { 93 | if (s.isEmpty) { 94 | return 1 95 | } 96 | 97 | s.head.toInt * productLoop(s.tail) 98 | } 99 | 100 | /** 101 | * Task 10: 102 | * 103 | * Write a function that computes `x^^n`, where `n` is an integer. Use the following 104 | * recursive definition: 105 | * {{{ 106 | * x^n = y^2 if n is even and positive, where y = x^(n / 2). 107 | * x^n = x * x^(n – 1) if n is odd and positive. 108 | * x^0 = 1. 109 | * x^n = 1 / (x^–n) if n is negative. 110 | * }}} 111 | * Don't use a `return` statement. 112 | */ 113 | def pow(x: Int, n: Int): Double = { 114 | if (n == 0) 1 115 | else if (n < 0) 1 / pow(x, -n) 116 | else if (n % 2 != 0) x * pow(x, n - 1) 117 | else { 118 | val y = pow(x, n / 2) 119 | y * y 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/main/scala/Chapter03.scala: -------------------------------------------------------------------------------- 1 | import java.awt.datatransfer.{DataFlavor, SystemFlavorMap} 2 | import java.util.TimeZone 3 | 4 | import scala.collection.mutable 5 | import scala.collection.mutable.ArrayBuffer 6 | import scala.util.{Random, Sorting} 7 | 8 | object Chapter03 { 9 | 10 | /** 11 | * Task 1: 12 | * Write a code snippet that sets a to an array of n random integers 13 | * between 0 (inclusive) and n (exclusive) 14 | */ 15 | def randomIntArray(n: Int): Array[Int] = { 16 | val a = new Array[Int](n) 17 | for (i <- a.indices) { 18 | a(i) = Random.nextInt(n) 19 | } 20 | 21 | a 22 | } 23 | 24 | /** 25 | * Task 2: 26 | * Write a loop that swaps adjacent elements of an array of integers. 27 | * For example, Array(1, 2, 3, 4, 5) becomes Array(2, 1, 4, 3, 5) 28 | */ 29 | def swapAdjacent(a: Array[Int]): Array[Int] = { 30 | for (i <- 1 until a.length if (i % 2) != 0) { 31 | val tmp = a(i - 1) 32 | a(i - 1) = a(i) 33 | a(i) = tmp 34 | } 35 | 36 | a 37 | } 38 | 39 | /** 40 | * Task 3: 41 | * Repeat the preceding assignment, but produce a new array 42 | * with the swapped values. Use for/yield 43 | */ 44 | def swapAdjacentYield(a: Array[Int]) = { 45 | for (i <- a.indices) yield a( 46 | if ((i % 2) == 0) 47 | if (i + 1 == a.length) i 48 | else i + 1 49 | else i - 1 50 | ) 51 | } 52 | 53 | /** 54 | * Task 4: 55 | * Given an array of integers, produce a new array that contains 56 | * all positive values of the original array, in their original order, 57 | * followed by all values that are zero or negative, 58 | * in their original order 59 | */ 60 | def positivesThenNegatives(a: Array[Int]): Array[Int] = { 61 | val length = a.length 62 | val b = new Array[Int](length) 63 | var index = 0 64 | for (i <- 0 until length if a(i) > 0) { 65 | b(index) = a(i) 66 | index += 1 67 | } 68 | 69 | if (index < length) { 70 | for (i <- 0 until length if a(i) <= 0) { 71 | b(index) = a(i) 72 | index += 1 73 | } 74 | } 75 | 76 | b 77 | } 78 | 79 | /** 80 | * Task 5: 81 | * How do you compute the average of an Array[Double] ? 82 | */ 83 | def computeAverage(a: Array[Double]): Double = { 84 | a.sum / a.length 85 | } 86 | 87 | /** 88 | * Reverses the given array in place. 89 | * 90 | *

Got from here: 91 | *
http://javarevisited.blogspot.de/2015/03/how-to-reverse-array-in-place-in-java.html 92 | */ 93 | private def reverse[T](a: Array[T]): Array[T] = { 94 | for (i <- 0 until a.length / 2) { 95 | // swap elements 96 | val temp = a(i) 97 | val j = a.length - 1 - i 98 | a(i) = a(j) 99 | a(j) = temp 100 | } 101 | 102 | a 103 | } 104 | 105 | /** 106 | * Task 6a: 107 | * How do you rearrange the elements of an Array[Int] 108 | * so that they appear in reverse sorted order? 109 | */ 110 | def reverseSortArray(a: Array[Int]): Array[Int] = { 111 | Sorting.quickSort(a) 112 | reverse(a) 113 | } 114 | 115 | /** 116 | * Task 6b: 117 | * How do you rearrange the elements of an ArrayBuffer[Int] 118 | * so that they appear in reverse sorted order? 119 | */ 120 | def reverseSortArrayBuffer(buf: ArrayBuffer[Int]): ArrayBuffer[Int] = { 121 | val arr: Array[Int] = reverseSortArray(buf.toArray) 122 | buf.clear() 123 | arr.copyToBuffer(buf) 124 | buf 125 | } 126 | 127 | /** 128 | * Task 7: 129 | * Write a code snippet that produces all values from an array with duplicates removed. 130 | * (Hint: Look at Scaladoc.) 131 | */ 132 | def removeDuplicates(a: Array[Int]): Array[Int] = a.distinct 133 | 134 | /** 135 | * Task 8: 136 | * Rewrite the example at the end of Section 3.4, "Transforming Arrays", on page 34 137 | * using the drop method for dropping the index of the first match. 138 | * Look the method up in Scaladoc. 139 | */ 140 | def dropNegativesExceptFirst(a: ArrayBuffer[Int]): ArrayBuffer[Int] = { 141 | val allNegativeIndexes = for (i <- a.indices if a(i) < 0) yield i 142 | val indexes = allNegativeIndexes.drop(1) 143 | 144 | for (j <- indexes.indices.reverse) a.remove(indexes(j)) 145 | a 146 | } 147 | 148 | /** 149 | * Task 9: 150 | * Make a collection of all time zones returned by java.util.TimeZone.getAvailableIDs 151 | * that are in America. Strip off the "America/" prefix and sort the result. 152 | */ 153 | def americaTimeZones: Array[String] = { 154 | TimeZone.getAvailableIDs.filter(_.startsWith("America/")) 155 | .map(_.stripPrefix("America/")) 156 | .sorted 157 | } 158 | 159 | /** 160 | * Task 10: 161 | * Import java.awt.datatransfer._ and make an object of type SystemFlavorMap with the call 162 | * 163 | * val flavors = SystemFlavorMap.getDefaultFlavorMap().asInstanceOf[SystemFlavorMap] 164 | * 165 | * Then call the getNativesForFlavor method with parameter DataFlavor.imageFlavor 166 | * and get the return value as a Scala buffer. (Why this obscure class? It’s hard 167 | * to find uses of java.util.List in the standard Java library.) 168 | */ 169 | def javaListAsScalaBuffer: mutable.Buffer[String] = { 170 | import scala.collection.JavaConversions.asScalaBuffer 171 | 172 | SystemFlavorMap.getDefaultFlavorMap.asInstanceOf[SystemFlavorMap] 173 | .getNativesForFlavor(DataFlavor.imageFlavor) 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/main/scala/Chapter04.scala: -------------------------------------------------------------------------------- 1 | import java.util 2 | import java.util.Scanner 3 | 4 | import scala.collection.JavaConversions.{mapAsScalaMap, propertiesAsScalaMap} 5 | import scala.collection.immutable.SortedMap 6 | import scala.collection.mutable 7 | import scala.collection.mutable.ListBuffer 8 | 9 | object Chapter04 { 10 | 11 | /** 12 | * Task 1: 13 | * Set up a map of prices for a number of gizmos that you covet. 14 | * Then produce a second map with the same keys and the prices at a 10 percent discount. 15 | */ 16 | def gizmosWithReducedPrice(): Map[String, Int] = { 17 | val gizmos = Map("iPhone" -> 600, 18 | "iPad" -> 500, 19 | "MacBook Pro" -> 2000, 20 | "ScalaDays 2016 Berlin" -> 750) 21 | 22 | gizmos.mapValues(price => price - (price / 10)) 23 | } 24 | 25 | /** 26 | * Task 2: 27 | * Write a program that reads words from a file. Use a mutable map to count 28 | * how often each word appears. To read the words, simply use a java.util.Scanner: 29 | *

30 | * val in = new java.util.Scanner(new java.io.File("myfile.txt"))
31 | * while (in.hasNext()) process in.next() 32 | *
33 | */ 34 | def countWordsMutableMap(): mutable.Map[String, Int] = { 35 | val words = new mutable.HashMap[String, Int] 36 | 37 | processWords(w => words(w) = words.getOrElse(w, 0) + 1) 38 | words 39 | } 40 | 41 | private def processWords(process: String => Unit): Unit = { 42 | val in = new Scanner(getClass.getResourceAsStream("/myfile.txt")) 43 | try { 44 | while (in.hasNext) { 45 | process(in.next()) 46 | } 47 | } finally { 48 | in.close() 49 | } 50 | } 51 | 52 | /** 53 | * Task 3: 54 | * Repeat the preceding exercise with an immutable map. 55 | */ 56 | def countWordsImmutableMap(): Map[String, Int] = { 57 | var words = Map[String, Int]() 58 | 59 | processWords(w => words += w -> (words.getOrElse(w, 0) + 1)) 60 | words 61 | } 62 | 63 | /** 64 | * Task 4: 65 | * Repeat the preceding exercise with a sorted map, 66 | * so that the words are printed in sorted order. 67 | */ 68 | def countWordsSortedMap(): Map[String, Int] = { 69 | var words = SortedMap[String, Int]() 70 | 71 | processWords(w => words += w -> (words.getOrElse(w, 0) + 1)) 72 | words 73 | } 74 | 75 | /** 76 | * Task 5: 77 | * Repeat the preceding exercise with a java.util.TreeMap 78 | * that you adapt to the Scala API. 79 | */ 80 | def countWordsTreeMap(): mutable.Map[String, Int] = { 81 | val words = new util.TreeMap[String, Int]() 82 | 83 | processWords(w => words(w) = words.getOrElse(w, 0) + 1) 84 | words 85 | } 86 | 87 | /** 88 | * Task 6: 89 | * Define a linked hash map that maps "Monday" to java.util.Calendar.MONDAY, 90 | * and similarly for the other weekdays. Demonstrate that the elements 91 | * are visited in insertion order. 92 | */ 93 | def weekdaysLinkedHashMap(): mutable.Map[String, Int] = { 94 | val weekdays = mutable.LinkedHashMap[String, Int]("Monday" -> util.Calendar.MONDAY) 95 | weekdays += ("Tuesday" -> util.Calendar.TUESDAY, 96 | "Wednesday" -> util.Calendar.WEDNESDAY, 97 | "Thursday" -> util.Calendar.THURSDAY, 98 | "Friday" -> util.Calendar.FRIDAY, 99 | "Saturday" -> util.Calendar.SATURDAY, 100 | "Sunday" -> util.Calendar.SUNDAY) 101 | 102 | weekdays 103 | } 104 | 105 | /** 106 | * Task 7: 107 | * Print a table of all Java properties, like this: 108 | *
109 | * java.runtime.name | Java(TM) SE Runtime Environment
110 | * sun.boot.library.path | /home/apps/jdk1.6.0_21/jre/lib/i386
111 | * java.vm.version | 17.0-b16
112 | * java.vm.vendor | Sun Microsystems Inc.
113 | * java.vendor.url | http://java.sun.com/
114 | * path.separator | :
115 | * java.vm.name | Java HotSpot(TM) Server VM
116 | *
117 | * You need to find the length of the longest key before you can print the table. 118 | */ 119 | def formatJavaProperties(): List[String] = { 120 | val props: collection.Map[String, String] = System.getProperties 121 | val maxKeyLen = props.foldLeft(0)((maxLen, entry) => { 122 | if (maxLen < entry._1.length) entry._1.length 123 | else maxLen 124 | }) 125 | 126 | val result = ListBuffer[String]() 127 | for ((key, value) <- props) { 128 | result += key.padTo(maxKeyLen, ' ') + " | " + value 129 | } 130 | 131 | result.toList 132 | } 133 | 134 | /** 135 | * Task 8: 136 | * Write a function minmax(values: Array[Int]) that returns a pair containing 137 | * the smallest and largest values in the array. 138 | */ 139 | def minmax(values: Array[Int]): (Int, Int) = (values.min, values.max) 140 | 141 | /** 142 | * Task 9: 143 | * Write a function lteqgt(values: Array[Int], v: Int) that returns a triple containing 144 | * the counts of values less than v , equal to v , and greater than v. 145 | */ 146 | def lteqgt(values: Array[Int], v: Int): (Int, Int, Int) = { 147 | // val count = values.count _ 148 | // 149 | // (count(_ < v), count(_ == v), count(_ > v)) 150 | var lt, eq, gt = 0 151 | 152 | for (e <- values) { 153 | if (e < v) lt += 1 154 | else if (e == v) eq += 1 155 | else gt += 1 156 | } 157 | 158 | (lt, eq, gt) 159 | } 160 | 161 | def main(args: Array[String]) { 162 | // task 2 163 | println(countWordsMutableMap().mkString("\n")) 164 | 165 | // task 7 166 | println(formatJavaProperties().mkString("\n")) 167 | 168 | // task 10 169 | println("Hello".zip("World").mkString("\n")) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/main/scala/Chapter05.scala: -------------------------------------------------------------------------------- 1 | import scala.beans.BeanProperty 2 | 3 | object Chapter05 { 4 | 5 | /** 6 | * Task 1: 7 | * Improve the Counter class in Section 5.1, "Simple Classes and Parameterless Methods" 8 | * on page 51 so that it doesn't turn negative at Int.MaxValue 9 | */ 10 | class Counter01(private var value: Int = 0) { 11 | 12 | def increment(): Int = { 13 | if (value == Int.MaxValue) { 14 | throw new IllegalStateException("counter reached Int.MaxValue") 15 | } 16 | 17 | value += 1 18 | value 19 | } 20 | 21 | def current: Int = value 22 | } 23 | 24 | /** 25 | * Task 2: 26 | * Write a class BankAccount with methods deposit and withdraw, 27 | * and a read-only property balance. 28 | */ 29 | class BankAccount02 { 30 | 31 | private var amount: Int = 0 32 | 33 | def deposit(sum: Int): Unit = { 34 | amount += sum 35 | } 36 | 37 | def withdraw(sum: Int): Unit = { 38 | amount -= sum 39 | } 40 | 41 | def balance: Int = amount 42 | } 43 | 44 | /** 45 | * Task 3: 46 | * Write a class Time with read-only properties hours and minutes and a method 47 | * 48 | * before(other: Time): Boolean 49 | * 50 | * that checks whether this time comes before the other. 51 | * 52 | * A Time object should be constructed as new Time(hrs, min), where hrs is in 53 | * military time format (between 0 and 23). 54 | */ 55 | class Time03(private val hours: Int, private val minutes: Int) { 56 | 57 | def before(other: Time03): Boolean = { 58 | if (hours < other.hours) true 59 | else if (hours == other.hours && minutes < other.minutes) true 60 | else false 61 | } 62 | } 63 | 64 | /** 65 | * Task 4: 66 | * Reimplement the Time class from the preceding exercise so that the internal 67 | * representation is the number of minutes since midnight (between 0 and 24 × 60 – 1). 68 | * Do not change the public interface. That is, client code should be unaffected by your change. 69 | */ 70 | class Time04(hrs: Int, min: Int) { 71 | 72 | private val minutes: Int = (hrs * 60) + min 73 | 74 | def before(other: Time04): Boolean = { 75 | minutes < other.minutes 76 | } 77 | } 78 | 79 | /** 80 | * Task 5: 81 | * Make a class Student with read-write JavaBeans properties name (of type String ) 82 | * and id (of type Long ). What methods are generated? (Use javap to check.) 83 | * Can you call the JavaBeans getters and setters in Scala? Should you? 84 | */ 85 | class Student05 { 86 | 87 | @BeanProperty 88 | var name: String = "" 89 | 90 | @BeanProperty 91 | var id: Long = 0 92 | } 93 | 94 | /** 95 | * Task 6: 96 | * In the Person class of Section 5.1, "Simple Classes and Parameterless Methods," 97 | * on page 51, provide a primary constructor that turns negative ages to 0. 98 | */ 99 | class Person06(inAge: Int) { 100 | 101 | var age: Int = if (inAge < 0) 0 else inAge 102 | } 103 | 104 | /** 105 | * Task 7: 106 | * Write a class Person with a primary constructor that accepts a string containing 107 | * a first name, a space, and a last name, such as new Person("Fred Smith"). 108 | * Supply read-only properties firstName and lastName. 109 | * Should the primary constructor parameter be a var, a val, or a plain parameter? Why? 110 | */ 111 | class Person07 private(names: Array[String]) { 112 | 113 | val firstName: String = names(0) 114 | val lastName: String = names(1) 115 | 116 | def this(name: String) { 117 | this(Person07.parseName(name)) 118 | } 119 | } 120 | 121 | private object Person07 { 122 | def parseName(name: String): Array[String] = { 123 | val names = name.split(" ") 124 | if (names.length < 2) { 125 | throw new IllegalArgumentException("name should contain a first name, a space" + 126 | ", and a last name, such as \"Fred Smith\"") 127 | } 128 | 129 | names 130 | } 131 | } 132 | 133 | /** 134 | * Task 8: 135 | * Make a class Car with read-only properties for manufacturer, model name, 136 | * and model year, and a read-write property for the license plate. 137 | * Supply four constructors. All require the manufacturer and model name. 138 | * Optionally, model year and license plate can also be specified in the constructor. 139 | * If not, the model year is set to -1 and the license plate to the empty string. 140 | * Which constructor are you choosing as the primary constructor? Why? 141 | */ 142 | class Car08(val manufacturer: String, 143 | val modelName: String, 144 | val modelYear: Int = -1, 145 | val licensePlate: String = "") { 146 | 147 | def this(manufacturer: String, modelName: String, licensePlate: String) { 148 | this(manufacturer, modelName, -1, licensePlate) 149 | } 150 | 151 | // Can be removed 152 | // see http://stackoverflow.com/questions/24480989/scala-auxilliary-constructor-behavior 153 | // 154 | // def this(manufacturer: String, modelName: String, modelYear: Int) { 155 | // this(manufacturer, modelName, modelYear, "") 156 | // } 157 | // 158 | // def this(manufacturer: String, modelName: String) { 159 | // this(manufacturer, modelName, -1) 160 | // } 161 | } 162 | 163 | /** 164 | * Task 9: 165 | * Reimplement the class of the preceding exercise in Java, C#, or C++ (your choice). 166 | * How much shorter is the Scala class? 167 | */ 168 | // see src/main/java/Chapter05Car.java 169 | 170 | /** 171 | * Task 10: 172 | * Consider the class 173 | * 174 | * class Employee(val name: String, var salary: Double) { 175 | * def this() { this("John Q. Public", 0.0) } 176 | * } 177 | * 178 | * Rewrite it to use explicit fields and a default primary constructor. 179 | * Which form do you prefer? Why? 180 | */ 181 | class Employee10 { 182 | val name: String = "John Q. Public" 183 | var salary: Double = 0.0 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/main/scala/Chapter06.scala: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Task 1: 4 | * Write an object Conversions with methods inchesToCentimeters, gallonsToLiters, 5 | * and milesToKilometers. 6 | */ 7 | object Conversions { 8 | 9 | def inchesToCentimeters(inches: Double): Double = inches * 2.54 10 | 11 | def gallonsToLiters(gallons: Double): Double = gallons * 3.785 12 | 13 | def milesToKilometers(miles: Double): Double = miles / 0.62137 14 | } 15 | 16 | /** 17 | * Task 2: 18 | * The preceding problem wasn't very object-oriented. Provide a general super-class 19 | * UnitConversion and define objects InchesToCentimeters, GallonsToLiters, and 20 | * MilesToKilometers that extend it. 21 | */ 22 | abstract class UnitConversion { 23 | 24 | def convert(inches: Double): Double 25 | } 26 | 27 | object InchesToCentimeters extends UnitConversion { 28 | 29 | override def convert(inches: Double) = inches * 2.54 30 | } 31 | 32 | object GallonsToLiters extends UnitConversion { 33 | 34 | override def convert(gallons: Double) = gallons * 3.785 35 | } 36 | 37 | object MilesToKilometers extends UnitConversion { 38 | 39 | override def convert(miles: Double) = miles / 0.62137 40 | } 41 | 42 | /** 43 | * Task 3: 44 | * Define an Origin object that extends java.awt.Point. Why is this not actually a good idea? 45 | * (Have a close look at the methods of the Point class.) 46 | */ 47 | object Origin extends java.awt.Point { 48 | // Its not a good idea since java.awt.Point class is mutable. 49 | } 50 | 51 | package task0604 { 52 | 53 | /** 54 | * Task 4: 55 | * 56 | * Define a Point class with a companion object so that you can construct Point 57 | * instances as Point(3, 4), without using new. 58 | */ 59 | class Point private(val x: Int, val y: Int) { 60 | } 61 | 62 | object Point { 63 | def apply(x: Int, y: Int) = new Point(x, y) 64 | } 65 | 66 | } 67 | 68 | /** 69 | * Task 5: 70 | * Write a Scala application, using the App trait, that prints the command-line 71 | * arguments in reverse order, separated by spaces. For example: 72 | *
73 | * scala Reverse Hello World 74 | *
75 | * should print 76 | *
77 | * World Hello 78 | *
79 | */ 80 | object Reverse extends App { 81 | println(args.reverse.mkString(" ")) 82 | } 83 | 84 | /** 85 | * Task 6: 86 | * Write an enumeration describing the four playing card suits so that the toString method 87 | * returns ♣, ♦, ♥, or ♠. 88 | */ 89 | object PlayingCard extends Enumeration { 90 | 91 | val Clubs = Value("♣") 92 | val Diamonds = Value("♦") 93 | val Hearts = Value("♥") 94 | val Spades = Value("♠") 95 | 96 | /** 97 | * Task 7: 98 | * Implement a function that checks whether a card suit value from the preceding exercise 99 | * is red. 100 | */ 101 | def isRed(card: PlayingCard.Value): Boolean = { 102 | card == Hearts || card == Diamonds 103 | } 104 | } 105 | 106 | /** 107 | * Task 8: 108 | * Write an enumeration describing the eight corners of the RGB color cube. 109 | * As IDs, use the color values (for example, 0xff0000 for Red). 110 | */ 111 | object RGB extends Enumeration { 112 | 113 | val Black = Value(0x000000) 114 | val White = Value(0xffffff) 115 | val Red = Value(0xff0000) 116 | val Lime = Value(0x00ff00) 117 | val Blue = Value(0x0000ff) 118 | val Yellow = Value(0xffff00) 119 | val Cyan = Value(0x00ffff) 120 | val Magenta = Value(0xff00ff) 121 | } 122 | -------------------------------------------------------------------------------- /src/main/scala/Chapter07.scala: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Task 1: 4 | * 5 | *

Write an example program to demonstrate that 6 | *

7 | * package com.horstmann.impatient 8 | *
9 | * is not the same as 10 | *
11 | * package com
12 | * package horstmann
13 | * package impatient
14 | *
15 | * 16 | * @see Chapter0701a.scala for solution part A 17 | * @see Chapter0701b.scala for solution part B 18 | */ 19 | package com { 20 | 21 | object FromCom { 22 | val value = 1 23 | } 24 | 25 | package horstmann { 26 | 27 | object FromHorstmann { 28 | val value = 2 29 | } 30 | 31 | package impatient { 32 | 33 | object FromImpatient { 34 | val value = 3 35 | } 36 | } 37 | } 38 | } 39 | 40 | /** 41 | * Task 2: 42 | * 43 | *

Write a puzzler that baffles your Scala friends, using a package com 44 | * that isn’t at the top level. 45 | */ 46 | package puzzler { 47 | 48 | package com { 49 | object FromCom { 50 | val value = 21 51 | } 52 | } 53 | } 54 | 55 | /** 56 | * Task 3: 57 | * 58 | *

Write a package random with functions 59 | * nextInt(): Int, 60 | * nextDouble(): Double, 61 | * and setSeed(seed: Int): Unit. 62 | * 63 | *

To generate random numbers, use the linear congruential generator 64 | * next = previous × a + b mod 2n, 65 | * where a = 1664525, b = 1013904223, and n = 32. 66 | */ 67 | package object random { 68 | 69 | private val addition: Int = (1013904223 % (1L << 32)).toInt 70 | private var seed : Int = 0 71 | 72 | def nextInt(): Int = { 73 | seed = (seed * 1664525) + addition 74 | 75 | if (seed < 0) ~seed 76 | else seed 77 | } 78 | 79 | def nextDouble(): Double = { 80 | nextInt() / (Int.MaxValue + 1.0) 81 | } 82 | 83 | def setSeed(seed: Int): Unit = this.seed = seed 84 | } 85 | 86 | /** 87 | * Task 4: 88 | * 89 | *

Why do you think the Scala language designers provided the package object syntax instead 90 | * of simply letting you add functions and variables to a package? 91 | * 92 | *

Solution:
93 | * They decided to make it explicit by adding just one word "object" to package declaration, 94 | * in my opinion, for a couple of reasons: 95 | *

101 | */ 102 | 103 | /** 104 | * Task 5: 105 | * 106 | *

What is the meaning of private[com] def giveRaise(rate: Double)? 107 | * Is it useful? 108 | */ 109 | package com { 110 | 111 | /** 112 | * private[com] makes definition package-private, meaning it is visible within 113 | * the same package and all sub-packages. 114 | */ 115 | object VisibilityDef { 116 | private[com] def giveRaise(rate: Double): Double = rate * 0.5 117 | } 118 | 119 | object VisibilityUsage { 120 | println(VisibilityDef.giveRaise(1)) 121 | } 122 | 123 | package horstmann { 124 | 125 | object VisibilityUsage { 126 | println(VisibilityDef.giveRaise(1)) 127 | } 128 | } 129 | } 130 | 131 | /** 132 | * Task 6: 133 | * 134 | *

Write a program that copies all elements from a Java hash map into a Scala hash map. 135 | * Use imports to rename both classes. 136 | */ 137 | object Chapter0706 { 138 | 139 | /** 140 | * Task 7: 141 | * 142 | *

In the preceding exercise, move all imports into the innermost scope possible. 143 | */ 144 | import java.util.{HashMap => JavaHashMap} 145 | 146 | import scala.collection.mutable.{HashMap => ScalaHashMap} 147 | 148 | def fromJavaHashMap(javaHashMap: JavaHashMap[String, Int]): ScalaHashMap[String, Int] = { 149 | import scala.collection.JavaConversions.iterableAsScalaIterable 150 | 151 | val result = new ScalaHashMap[String, Int] 152 | for (entry <- javaHashMap.entrySet()) { 153 | result(entry.getKey) = entry.getValue 154 | } 155 | 156 | result 157 | } 158 | } 159 | 160 | /** 161 | * Task 8: 162 | * 163 | *

What is the effect of 164 | *

165 | * import java._
166 | * import javax._
167 | *
168 | * Is this a good idea? 169 | */ 170 | object Chapter0708 { 171 | 172 | import java._ 173 | 174 | /** 175 | * Since we imported everything from java package, we can use sub-packages. 176 | */ 177 | def doSomething(list: util.List[String]) { 178 | } 179 | } 180 | 181 | /** 182 | * Task 9: 183 | * 184 | *

Write a program that imports the java.lang.System class, 185 | * reads the user name from the user.name system property, 186 | * reads a password from the Console object, and prints a message 187 | * to the standard error stream if the password is not "secret". 188 | * Otherwise, print a greeting to the standard output stream. 189 | * Do not use any other imports, and do not use any qualified names (with dots). 190 | */ 191 | object Chapter0709 extends App { 192 | //import java.lang.System 193 | 194 | val userName = System.getProperty("user.name") 195 | val password: String = readLine("Please, enter password: ") 196 | 197 | if (password != "secret") error("Wrong password!") 198 | else println("Welcome " + userName) 199 | } 200 | 201 | /** 202 | * Task 10: 203 | * 204 | *

Apart from StringBuilder, what other members of java.lang does the scala package override? 205 | */ 206 | object JavaLangOverrides extends App { 207 | 208 | val overrides = List[Class[_]](classOf[Boolean], 209 | classOf[Byte], 210 | classOf[Double], 211 | classOf[Float], 212 | classOf[Iterable[_]], 213 | classOf[Long], 214 | classOf[Short], 215 | classOf[StringBuilder]) 216 | 217 | println(overrides.mkString("\n")) 218 | } 219 | -------------------------------------------------------------------------------- /src/main/scala/Chapter0701a.scala: -------------------------------------------------------------------------------- 1 | package com.horstmann.impatient 2 | 3 | /** 4 | * Solution A for Task 1: 5 | * 6 | *

FromCom and FromHorstmann are not accessible from here. 7 | * They require additional imports: 8 | *

9 | * import com.FromCom
10 | * import com.horstmann.FromHorstmann
11 | *
12 | */ 13 | object Chapter0701a { 14 | 15 | //println(FromCom.value) 16 | //println(FromHorstmann.value) 17 | println(FromImpatient.value) 18 | } 19 | -------------------------------------------------------------------------------- /src/main/scala/Chapter0701b.scala: -------------------------------------------------------------------------------- 1 | package com 2 | package horstmann 3 | package impatient 4 | 5 | /** 6 | * Solution B for Task 1: 7 | * 8 | *

FromCom and FromHorstmann are accessible from here. 9 | * No additional imports are required. 10 | */ 11 | object Chapter0701b { 12 | 13 | println(FromCom.value) 14 | println(FromHorstmann.value) 15 | println(FromImpatient.value) 16 | } 17 | -------------------------------------------------------------------------------- /src/main/scala/Chapter08.scala: -------------------------------------------------------------------------------- 1 | import scala.collection.mutable.ListBuffer 2 | 3 | /** 4 | * Task 1: 5 | * 6 | * Extend the following `BankAccount` class to a `CheckingAccount` class that charges $1 7 | * for every deposit and withdrawal. 8 | * {{{ 9 | * class BankAccount(initialBalance: Double) { 10 | * private var balance = initialBalance 11 | * def deposit(amount: Double) = { balance += amount; balance } 12 | * def withdraw(amount: Double) = { balance -= amount; balance } 13 | * } 14 | * }}} 15 | */ 16 | class BankAccount(initialBalance: Double) { 17 | private var balance = initialBalance 18 | 19 | def deposit(amount: Double) = { 20 | balance += amount 21 | balance 22 | } 23 | 24 | def withdraw(amount: Double) = { 25 | balance -= amount 26 | balance 27 | } 28 | } 29 | 30 | class CheckingAccount(initialBalance: Double) extends BankAccount(initialBalance) { 31 | 32 | private val charge: Double = 1 33 | 34 | override def deposit(amount: Double) = super.deposit(amount - charge) 35 | 36 | override def withdraw(amount: Double) = super.withdraw(amount + charge) 37 | } 38 | 39 | /** 40 | * Task 2: 41 | * 42 | * Extend the `BankAccount` class of the preceding exercise into a class `SavingsAccount` 43 | * that earns interest every month (when a method `earnMonthlyInterest` is called) 44 | * and has three free deposits or withdrawals every month. Reset the transaction 45 | * count in the `earnMonthlyInterest` method. 46 | */ 47 | class SavingsAccount(initialBalance: Double) extends BankAccount(initialBalance) { 48 | 49 | private val monthlyInterest: Double = 0.01 50 | private val maxFreeTransactions: Int = 3 51 | 52 | private var transactionsCount: Int = 0 53 | private var balance: Double = initialBalance 54 | 55 | override def deposit(amount: Double) = { 56 | balance = super.deposit(amount - charge) 57 | balance 58 | } 59 | 60 | override def withdraw(amount: Double) = { 61 | balance = super.withdraw(amount + charge) 62 | balance 63 | } 64 | 65 | def getBalance: Double = { 66 | balance 67 | } 68 | 69 | def earnMonthlyInterest(): Unit = { 70 | transactionsCount = 0 71 | 72 | balance = super.deposit(balance * monthlyInterest) 73 | } 74 | 75 | private def charge: Double = { 76 | if (transactionsCount < maxFreeTransactions) { 77 | transactionsCount += 1 78 | return 0.0 79 | } 80 | 81 | 1.0 82 | } 83 | } 84 | 85 | package task0803 { 86 | 87 | /** 88 | * Task 3: 89 | * 90 | * Consult your favorite Java or C++ textbook that is sure to have an example 91 | * of a toy inheritance hierarchy, perhaps involving employees, pets, graphical 92 | * shapes, or the like. Implement the example in Scala. 93 | */ 94 | abstract class Shape { 95 | def draw(): Unit 96 | 97 | def erase(): Unit 98 | } 99 | 100 | class Circle extends Shape { 101 | 102 | override def draw(): Unit = { 103 | println("drawing Circle...") 104 | } 105 | 106 | override def erase(): Unit = { 107 | println("erasing Circle...") 108 | } 109 | } 110 | 111 | class Square extends Shape { 112 | 113 | override def draw(): Unit = { 114 | println("drawing Square...") 115 | } 116 | 117 | override def erase(): Unit = { 118 | println("erasing Square...") 119 | } 120 | } 121 | 122 | object Shapes extends App { 123 | 124 | cleanAndPaint(new Circle) 125 | cleanAndPaint(new Square) 126 | 127 | def cleanAndPaint(shape: Shape): Unit = { 128 | shape.erase() 129 | shape.draw() 130 | } 131 | } 132 | 133 | } 134 | 135 | package task0804 { 136 | 137 | /** 138 | * Task 4: 139 | * 140 | * Define an abstract class `Item` with methods `price` and `description`. A `SimpleItem` 141 | * is an item whose `price` and `description` are specified in the constructor. Take advantage 142 | * of the fact that a val can override a def. A `Bundle` is an item that contains other items. 143 | * Its price is the sum of the prices in the bundle. Also provide a mechanism for adding items 144 | * to the bundle and a suitable description method. 145 | */ 146 | abstract class Item { 147 | 148 | /** Price in minor units */ 149 | def price: Int 150 | 151 | def description: String 152 | } 153 | 154 | class SimpleItem(override val price: Int, override val description: String) extends Item 155 | 156 | class Bundle extends Item { 157 | 158 | private val items = new ListBuffer[Item] 159 | 160 | def addItem(item: Item): Bundle = { 161 | items += item 162 | this 163 | } 164 | 165 | override def price = items.foldLeft(0)((sum, item) => sum + item.price) 166 | 167 | override def description = items.map(_.description).mkString("\n\n") 168 | } 169 | 170 | } 171 | 172 | package task0805 { 173 | 174 | /** 175 | * Task 5: 176 | * 177 | * Design a class `Point` whose x and y coordinate values can be provided in a constructor. 178 | * Provide a subclass `LabeledPoint` whose constructor takes a `label` value and `x` and `y` 179 | * coordinates, such as 180 | * {{{ 181 | * new LabeledPoint("Black Thursday", 1929, 230.07) 182 | * }}} 183 | */ 184 | class Point(val x: Double, val y: Double) 185 | 186 | class LabeledPoint(val label: String, x: Double, y: Double) extends Point(x, y) 187 | 188 | } 189 | 190 | package task0806 { 191 | 192 | import task0805.Point 193 | 194 | /** 195 | * Task 6: 196 | * 197 | * Define an abstract class `Shape` with an abstract method `centerPoint` and subclasses 198 | * `Rectangle` and `Circle`. Provide appropriate constructors for the subclasses and 199 | * override the `centerPoint` method in each subclass. 200 | */ 201 | abstract class Shape { 202 | def centerPoint: Point 203 | } 204 | 205 | class Circle(override val centerPoint: Point, val radius: Double) extends Shape 206 | 207 | class Rectangle(val x1: Double, val y1: Double, val x2: Double, val y2: Double) extends Shape { 208 | 209 | override def centerPoint = new Point((x1 + x2) / 2, (y1 + y2) / 2) 210 | } 211 | 212 | } 213 | 214 | package task0807 { 215 | 216 | import java.awt.Rectangle 217 | 218 | /** 219 | * Task 7: 220 | * 221 | * Provide a class `Square` that extends `java.awt.Rectangle` and has three constructors: 222 | * one that constructs a square with a given corner point and width, 223 | * one that constructs a square with corner (0, 0) and a given width, 224 | * and one that constructs a square with corner (0, 0) and width 0. 225 | */ 226 | class Square(x: Int = 0, y: Int = 0, width: Int = 0) extends Rectangle(x, y, width, width) { 227 | 228 | def this(width: Int) { 229 | this(0, 0, width) 230 | } 231 | } 232 | 233 | } 234 | 235 | package task0808 { 236 | 237 | /** 238 | * Task 8: 239 | * 240 | * Compile the `Person` and `SecretAgent` classes in Section 8.6, “Overriding Fields,” on page 91 241 | * and analyze the class files with `javap`. 242 | * How many name fields are there? 243 | * How many name getter methods are there? 244 | * What do they get? 245 | * (Hint: Use the -c and -private options.) 246 | */ 247 | class Person(val name: String) { 248 | override def toString = getClass.getName + "[name=" + name + "]" 249 | } 250 | 251 | /** 252 | * Solution: 253 | * 254 | * There are two name fields, one in Person class, and one in SecretAgent class. 255 | * There are two name getter methods, one in each class as well. The second one overrides the first. 256 | * They get/return corresponding name field from their class. 257 | * 258 | * Additionally, there is separate toString field and overrided toString method, which returns 259 | * that field. 260 | */ 261 | class SecretAgent(codename: String) extends Person(codename) { 262 | // Don’t want to reveal name... 263 | override val name = "secret" 264 | // ...or class name 265 | override val toString = "secret" 266 | } 267 | 268 | } 269 | 270 | /** 271 | * Task 9: 272 | * 273 | * In the `Creature` class of Section 8.10, "Construction Order and Early Definitions," 274 | * on page 94, replace `val range` with a `def`. What happens when you also use a 275 | * `def` in the `Ant` subclass? What happens when you use a `val` in the subclass? 276 | * Why? 277 | */ 278 | class Creature { 279 | def range: Int = 10 280 | val env: Array[Int] = new Array[Int](range) 281 | } 282 | 283 | class Ant extends Creature { 284 | override def range = 2 285 | } 286 | 287 | /** 288 | * Solution: 289 | * 290 | * When we use def in both superclass and subclass the array length is initialized with expected, 291 | * overrided value 2. This happens because there is no field to hold the value, and we don't have 292 | * initialization order problem. 293 | * 294 | * But when we use def in superclass and val in subclass initialization order problem is back 295 | * again. Since now the range value in subclass is backed by field and at the moment when 296 | * corresponding generated range getter method is called from superclass constructor the value is 297 | * not initialized yet and equals to 0 by default. 298 | */ 299 | object Creatures extends App { 300 | println(new Ant().env.length) 301 | } 302 | 303 | /** 304 | * Task 10: 305 | * 306 | * The file `scala/collection/immutable/Stack.scala` contains the definition 307 | * {{{ 308 | * class Stack[A] protected (protected val elems: List[A]) 309 | * }}} 310 | * Explain the meanings of the `protected` keywords. (Hint: Review the discussion 311 | * of private constructors in Chapter 5.) 312 | * 313 | * 314 | * Solution: 315 | * 316 | * The first `protected` keyword defines protected primary constructor, which is accessible only 317 | * from auxiliary constructor or from subclass primary constructor. 318 | * The second `protected` keyword defines protected field `elems` with corresponding protected 319 | * getter method, they are accessible within class and subclasses. 320 | */ 321 | -------------------------------------------------------------------------------- /src/main/scala/Chapter09.scala: -------------------------------------------------------------------------------- 1 | import java.io._ 2 | import java.nio.file._ 3 | import java.nio.file.attribute.BasicFileAttributes 4 | import scala.collection.mutable.ArrayBuffer 5 | import scala.io.{BufferedSource, Source} 6 | import scala.language.implicitConversions 7 | 8 | object Chapter09 { 9 | 10 | /** 11 | * Task 1: 12 | * 13 | * Write a Scala code snippet that reverses the lines in a file 14 | * (making the last line the first one, and so on). 15 | */ 16 | def reverseLines(file: File): Unit = { 17 | val source: BufferedSource = Source.fromFile(file) 18 | val lines = try { 19 | source.getLines().toBuffer.reverse 20 | } 21 | finally { 22 | source.close() 23 | } 24 | 25 | val writer = new PrintWriter(file) 26 | try { 27 | lines.foreach(writer.println) 28 | } 29 | finally { 30 | writer.close() 31 | } 32 | } 33 | 34 | /** 35 | * Task 2: 36 | * 37 | * Write a Scala program that reads a file with tabs, replaces each tab with spaces 38 | * so that tab stops are at n-column boundaries, and writes the result to the same file. 39 | */ 40 | def replaceTabs(file: File, charsPerColumn: Int = 4): Unit = { 41 | var chars = 0 42 | val buff = new ArrayBuffer[Char] 43 | val source: BufferedSource = Source.fromFile(file) 44 | try for (c <- source) c match { 45 | case '\t' => for (_ <- 0 until (charsPerColumn - chars % charsPerColumn)) buff += ' ' 46 | chars = 0 47 | case '\n' => buff += c 48 | chars = 0 49 | case _ => buff += c 50 | chars += 1 51 | } 52 | finally { 53 | source.close() 54 | } 55 | 56 | val writer = new PrintWriter(file) 57 | try { 58 | buff.foreach(writer.print) 59 | } 60 | finally { 61 | writer.close() 62 | } 63 | } 64 | 65 | /** 66 | * Task 3: 67 | * 68 | * Write a Scala code snippet that reads a file and prints all words with more than 69 | * 12 characters to the console. Extra credit if you can do this in a single line. 70 | */ 71 | def printLongWords(file: String): Unit = { 72 | val maxWordLength: Int = 12 73 | Source.fromFile(file).mkString.split("\\s+").filter(_.length > maxWordLength).foreach(println) 74 | } 75 | 76 | /** 77 | * Task 4: 78 | * 79 | * Write a Scala program that reads a text file containing only floating-point numbers. 80 | * Print the sum, average, maximum, and minimum of the numbers in the file. 81 | */ 82 | def printNumbersStat(file: String): Unit = { 83 | var count: Int = 0 84 | var sum: Double = 0.0 85 | var min: Double = Double.MaxValue 86 | var max: Double = Double.MinValue 87 | for (token <- Source.fromFile(file).mkString.split("\\s+"); num = token.toDouble) { 88 | count += 1 89 | sum += num 90 | if (num < min) min = num 91 | if (num > max) max = num 92 | } 93 | 94 | printf("sum: %.3f\n", sum) 95 | printf("average: %.3f\n", sum / count) 96 | printf("minimum: %.3f\n", min) 97 | printf("maximum: %.3f\n", max) 98 | } 99 | 100 | /** 101 | * Task 5: 102 | * 103 | * Write a Scala program that writes the powers of 2 and their reciprocals to a file, 104 | * with the exponent ranging from 0 to 20. Line up the columns: 105 | * {{{ 106 | * 1 1 107 | * 2 0.5 108 | * 4 0.25 109 | * ... ... 110 | * }}} 111 | */ 112 | def printPowersOf2(file: File): Unit = { 113 | val writer = new PrintWriter(file) 114 | try { 115 | for (i <- 0 to 20) { 116 | writer.println("%8.0f %f".format(math.pow(2.0, i), math.pow(2.0, -i))) 117 | } 118 | } 119 | finally { 120 | writer.close() 121 | } 122 | } 123 | 124 | /** 125 | * Task 6: 126 | * 127 | * Make a regular expression searching for quoted strings "like this, maybe with \" or \\" 128 | * in a Java or C++ program. Write a Scala program that prints out all such strings 129 | * in a source file. 130 | */ 131 | def printQuotedStrings(file: String): Unit = { 132 | // got from here: 133 | // http://stackoverflow.com/questions/2498635/java-regex-for-matching-quoted-string-with-escaped-quotes 134 | val pattern = "\"(([^\\\\\"]+|\\\\([btnfr\"'\\\\]|[0-3]?[0-7]{1,2}|u[0-9a-fA-F]{4}))*)\"".r 135 | 136 | for (pattern(s, _, _) <- pattern.findAllIn(Source.fromFile(file).mkString)) { 137 | println(s) 138 | } 139 | } 140 | 141 | /** 142 | * Task 7: 143 | * 144 | * Write a Scala program that reads a text file and prints all tokens in the file 145 | * that are not floating-point numbers. Use a regular expression. 146 | */ 147 | def printNonNumberTokens(file: String): Unit = { 148 | val pattern = "(?![\\d]+(\\.[\\d]+)?)\\w+".r 149 | 150 | for (s <- pattern.findAllIn(Source.fromFile(file).mkString)) { 151 | println(s) 152 | } 153 | } 154 | 155 | /** 156 | * Task 8: 157 | * 158 | * Write a Scala program that prints the `src` attributes of all `img` tags of a web page. 159 | * Use regular expressions and groups. 160 | */ 161 | def printSrcOfImageTags(file: String): Unit = { 162 | val pattern = "(?i) { 178 | if (f.toString.endsWith(".class")) count += 1 179 | }) 180 | 181 | count 182 | } 183 | 184 | implicit def makeFileVisitor(f: (Path) => Unit): FileVisitor[Path] = new SimpleFileVisitor[Path] { 185 | override def visitFile(p: Path, attrs: BasicFileAttributes) = { 186 | f(p) 187 | FileVisitResult.CONTINUE } 188 | } 189 | 190 | /** 191 | * Task 10: 192 | * 193 | * Expand the example with the serializable `Person` class that stores a collection of friends. 194 | * Construct a few `Person` objects, make some of them friends of another, and then 195 | * save an `Array[Person]` to a file. Read the array back in and verify that the friend 196 | * relations are intact. 197 | */ 198 | class Person(val name: String) extends Serializable with Iterable[Person] { 199 | 200 | private val friends = new ArrayBuffer[Person] 201 | 202 | def addFriend(friend: Person): Person = { 203 | friends += friend 204 | this 205 | } 206 | 207 | override def iterator: Iterator[Person] = friends.toIterator 208 | } 209 | 210 | def savePersons(file: File, persons: Array[Person]): Unit = { 211 | val out = new ObjectOutputStream(new FileOutputStream(file)) 212 | try { 213 | out.writeObject(persons) 214 | } finally { 215 | out.close() 216 | } 217 | } 218 | 219 | def readPersons(file: File): Array[Person] = { 220 | val in = new ObjectInputStream(new FileInputStream(file)) 221 | try { 222 | in.readObject().asInstanceOf[Array[Person]] 223 | } finally { 224 | in.close() 225 | } 226 | } 227 | } 228 | 229 | object Chapter09PrintLongWordsApp extends Utils.FileApp(Chapter09.printLongWords) 230 | 231 | object Chapter09PrintNumbersStatApp extends Utils.FileApp(Chapter09.printNumbersStat) 232 | 233 | object Chapter09PrintQuotedStringsApp extends Utils.FileApp(Chapter09.printQuotedStrings) 234 | 235 | object Chapter09PrintNonNumberTokensApp extends Utils.FileApp(Chapter09.printNonNumberTokens) 236 | 237 | object Chapter09PrintSrcOfImageTagsApp extends Utils.FileApp(Chapter09.printSrcOfImageTags) 238 | -------------------------------------------------------------------------------- /src/main/scala/Chapter12.scala: -------------------------------------------------------------------------------- 1 | 2 | 3 | object Chapter12 { 4 | 5 | /** 6 | * Task 1: 7 | * 8 | * Write a function `values(fun: (Int) => Int, low: Int, high: Int)` that yields a collection 9 | * of function inputs and outputs in a given range. For example, `values(x => x * x, -5, 5)` 10 | * should produce a collection of pairs `(-5, 25)`, `(-4, 16)`, `(-3, 9)`, ..., `(5, 25)`. 11 | */ 12 | def values(fun: (Int) => Int, low: Int, high: Int): Seq[(Int, Int)] = { 13 | for (i <- low to high) yield (i, fun(i)) 14 | } 15 | 16 | /** 17 | * Task 2: 18 | * 19 | * How do you get the largest element of an array with `reduceLeft`? 20 | */ 21 | def largestElement(arr: Array[Int]): Int = arr.reduceLeft((a, b) => if (a > b) a else b) 22 | 23 | /** 24 | * Task 3: 25 | * 26 | * Implement the `factorial` function using `to` and `reduceLeft`, without a loop or recursion. 27 | */ 28 | def factorial(n: Int): Int = if (n <= 0) 1 else (1 to n).reduceLeft(_ * _) 29 | 30 | /** 31 | * Task 4: 32 | * 33 | * The previous implementation needed a special case when `n < 1`. Show how you can avoid this 34 | * with `foldLeft`. (Look at the Scaladoc for `foldLeft`. It’s like `reduceLeft`, except that 35 | * the first value in the chain of combined values is supplied in the call.) 36 | */ 37 | def factorial2(n: Int): Int = (1 to n).foldLeft(1)(_ * _) 38 | 39 | /** 40 | * Task 5: 41 | * 42 | * Write a function `largest(fun: (Int) => Int, inputs: Seq[Int])` that yields the largest 43 | * value of a function within a given sequence of inputs. For example, 44 | * `largest(x => 10 * x - x * x, 1 to 10)` should return `25`. Don't use a loop or recursion. 45 | */ 46 | def largest(fun: (Int) => Int, inputs: Seq[Int]): Int = { 47 | inputs.map(fun(_)).reduceLeft((a, b) => if (a > b) a else b) 48 | } 49 | 50 | /** 51 | * Task 6: 52 | * 53 | * Modify the previous function to return the input at which the output is largest. For example, 54 | * `largestAt(x => 10 * x - x * x, 1 to 10)` should return `5`. Don't use a loop or recursion. 55 | */ 56 | def largestAt(fun: (Int) => Int, inputs: Seq[Int]): Int = { 57 | val valueWithInput: (Int, Int) = inputs.map(i => (fun(i), i)).reduceLeft {(a, b) => 58 | if (a._1 > b._1) a 59 | else b 60 | } 61 | 62 | valueWithInput._2 63 | } 64 | 65 | /** 66 | * Task 7: 67 | * 68 | * It's easy to get a sequence of pairs, for example 69 | * {{{ 70 | * val pairs = (1 to 10) zip (11 to 20) 71 | * }}} 72 | * Now suppose you want to do something with such a sequence - say, add up the values. 73 | * But you can't do 74 | * {{{ 75 | * pairs.map(_ + _) 76 | * }}} 77 | * The function `_ + _` takes two `Int` parameters, not an `(Int, Int)` pair. Write a function 78 | * `adjustToPair` that receives a function of type `(Int, Int) => Int` and returns the equivalent 79 | * function that operates on a pair. For example, `adjustToPair(_ * _)((6, 7))` is `42`. 80 | * Then use this function in conjunction with `map` to compute the sums of the elements in pairs. 81 | */ 82 | def adjustToPair(fun: (Int, Int) => Int): ((Int, Int)) => Int = { 83 | (pair: (Int, Int)) => fun(pair._1, pair._2) 84 | } 85 | 86 | // Or using currying: 87 | // 88 | // def adjustToPair(fun: (Int, Int) => Int)(pair: (Int, Int)): Int = { 89 | // fun(pair._1, pair._2) 90 | // } 91 | 92 | def mapPairs(pairs: Seq[(Int, Int)], fun: (Int, Int) => Int): Seq[Int] = { 93 | pairs.map(adjustToPair(fun)) 94 | } 95 | 96 | /** 97 | * Task 8: 98 | * 99 | * In Section 12.8, "Currying", on page 149, you saw the `corresponds` method used with two 100 | * arrays of strings. Make a call to corresponds that checks whether the elements in an 101 | * array of strings have the lengths given in an array of integers. 102 | */ 103 | def correspondsLen(strings: Array[String], lengths: Array[Int]): Boolean = { 104 | strings.corresponds(lengths)(_.length == _) 105 | } 106 | 107 | /** 108 | * Task 9: 109 | * 110 | * Implement `corresponds` without currying. Then try the call from the preceding exercise. 111 | * What problem do you encounter? 112 | * 113 | * Solution: 114 | * 115 | * Without currying the compiler is not be able to infer the types for predicate function. 116 | * So, it should be called like this: 117 | * {{{ 118 | * corresponds2(Array("a"), Array(1), (a: String, b: Int) => a.length == b) 119 | * }}} 120 | */ 121 | def corresponds2[A, B](ax: Array[A], bx: Array[B], predicate: (A, B) => Boolean): Boolean = { 122 | ax.corresponds(bx)(predicate) 123 | } 124 | 125 | /** 126 | * Task 10: 127 | * 128 | * Implement an `unless` control abstraction that works just like `if`, but with an inverted 129 | * condition. Does the first parameter need to be a call-by-name parameter? Do you need currying? 130 | * 131 | * Solution: 132 | * 133 | * The first parameter not need to be a call-by-name parameter since it is evaluated only once. 134 | * Yes, we need currying, with it looks exactly like an `if` expression. 135 | */ 136 | def unless(cond: Boolean)(block: => Unit): Unit = if (!cond) block 137 | } 138 | -------------------------------------------------------------------------------- /src/main/scala/Chapter13.scala: -------------------------------------------------------------------------------- 1 | import scala.collection.{concurrent, mutable} 2 | import scala.io.Source 3 | 4 | object Chapter13 { 5 | 6 | /** 7 | * Task 1: 8 | * 9 | * Write a function that, given a string, produces a map of the indexes of all characters. 10 | * For example, `indexes("Mississippi")` should return a map associating 11 | * 'M' with the set {0}, 12 | * ‘i’ with the set {1, 4, 7, 10}, and so on. 13 | * Use a mutable map of characters to mutable sets. How can you ensure that the set is sorted? 14 | * 15 | * Solution: 16 | * 17 | * We have to use `LinkedHashSet` to maintain the indices order in set. 18 | */ 19 | def indexes(s: String): mutable.Map[Char, mutable.Set[Int]] = { 20 | val map = new mutable.HashMap[Char, mutable.Set[Int]] 21 | for (i <- 0 until s.length) { 22 | map.getOrElseUpdate(s(i), new mutable.LinkedHashSet[Int]) += i 23 | } 24 | 25 | map 26 | } 27 | 28 | /** 29 | * Task 2: 30 | * 31 | * Repeat the preceding exercise, using an immutable map of characters to lists. 32 | */ 33 | def indexes2(s: String): Map[Char, List[Int]] = { 34 | var map = Map[Char, List[Int]]() 35 | for (i <- 0 until s.length) { 36 | val c = s(i) 37 | map = map.updated(c, map.getOrElse(c, Nil) :+ i) 38 | } 39 | 40 | map 41 | } 42 | 43 | /** 44 | * Task 3: 45 | * 46 | * Write a function that removes all zeroes from a linked list of integers. 47 | */ 48 | type LinkedList[T] = mutable.LinkedList[T] 49 | 50 | def removeAllZeroes(list: LinkedList[Int]): LinkedList[Int] = { 51 | // remove elements at the beginning of the list 52 | var result = list 53 | while (result.nonEmpty && result.elem == 0) { 54 | result = result.next 55 | } 56 | 57 | // remove elements till the end of the list 58 | var prev, curr = result 59 | while (curr.nonEmpty) { 60 | if (curr.elem == 0) { 61 | prev.next = curr.next 62 | curr = prev 63 | } 64 | 65 | prev = curr 66 | curr = curr.next 67 | } 68 | 69 | result 70 | } 71 | 72 | /** 73 | * Task 4: 74 | * 75 | * Write a function that receives a collection of strings and a map from strings to integers. 76 | * Return a collection of integers that are values of the map corresponding to one of 77 | * the strings in the collection. For example, given 78 | * `Array("Tom", "Fred", "Harry")` and `Map("Tom" -> 3, "Dick" -> 4, "Harry" -> 5)`, 79 | * return `Array(3, 5)`. Hint: Use flatMap to combine the Option values returned by get. 80 | */ 81 | def mapToValues(coll: Traversable[String], map: Map[String, Int]): Traversable[Int] = { 82 | coll.flatMap(map.get) 83 | } 84 | 85 | /** 86 | * Task 5: 87 | * 88 | * Implement a function that works just like `mkString`, using `reduceLeft`. 89 | */ 90 | def collToString[T](xs: Traversable[T]): String = { 91 | if (xs.isEmpty) "" 92 | // we have to specify Any type here, since its the closest common parent type for String and T 93 | else xs.reduceLeft((a: Any, b: T) => a + ", " + b).toString 94 | } 95 | 96 | /** 97 | * Task 6: 98 | * 99 | * Given a list of integers `lst`, what is 100 | * `(lst :\ List[Int]())(_ :: _)` ? 101 | * `(List[Int]() /: lst)(_ :+ _)` ? 102 | * How can you modify one of them to reverse the list? 103 | * 104 | * Solution: 105 | * 106 | * The first expression executes `foldRight` and prepends the elements to the resulting list. 107 | * The second expression executes `foldLeft` and appends the elements to the resulting list. 108 | * Both expressions produce new `List[Int]` with the same elements as in the original list, 109 | * and in the same order. 110 | * To reverse the list its better to modify the second expression to prepend the elements, 111 | * which is cheaper for lists comparing to append. 112 | */ 113 | def reversList(lst: List[Int]): List[Int] = { 114 | //(lst :\ List[Int]())((el, res) => res :+ el) 115 | 116 | (List[Int]() /: lst)((res, el) => el :: res) 117 | } 118 | 119 | /** 120 | * Task 7: 121 | * 122 | * In Section 13.11, "Zipping", on page 171, the expression 123 | * {{{ 124 | * (prices zip quantities) map { p => p._1 * p._2 } 125 | * }}} 126 | * is a bit inelegant. We can't do 127 | * {{{ 128 | * (prices zip quantities) map { _ * _ } 129 | * }}} 130 | * because `_ * _` is a function with two arguments, and we need a function with one argument 131 | * that is a tuple. The `tupled` method of the `Function` object changes a function with 132 | * two arguments to one that take a tuple. Apply `tupled` to the multiplication function 133 | * so you can map it over the list of pairs. 134 | */ 135 | def multiply(prices: Iterable[Int], quantities: Iterable[Int]): Iterable[Int] = { 136 | (prices zip quantities) map Function.tupled { _ * _ } 137 | } 138 | 139 | /** 140 | * Task 8: 141 | * 142 | * Write a function that turns an array of `Double` values into a two-dimensional array. 143 | * Pass the number of columns as a parameter. For example, with `Array(1, 2, 3, 4, 5, 6)` 144 | * and three columns, return `Array(Array(1, 2, 3), Array(4, 5, 6))`. Use the `grouped` method. 145 | */ 146 | def twoDimensionalArray(arr: Array[Double], columns: Int): Array[Array[Double]] = { 147 | require(arr.length % columns == 0, 148 | "array length should be compatible with the number of columns") 149 | 150 | //val res: Array[Array[Double]] = Array.ofDim(arr.length / columns, columns) 151 | val res = new mutable.ArrayBuffer[Array[Double]](arr.length / columns) 152 | for (row <- arr.grouped(columns)) { 153 | res += row 154 | } 155 | 156 | res.toArray 157 | } 158 | 159 | /** 160 | * Task 9: 161 | * 162 | * Harry Hacker writes a program that accepts a sequence of file names on the command line. 163 | * For each, he starts a new thread that reads the file and updates a letter frequency map 164 | * declared as 165 | * {{{ 166 | * val frequencies = new scala.collection.mutable.HashMap[Char, Int] with 167 | * scala.collection.mutable.SynchronizedMap[Char, Int] 168 | * }}} 169 | * When reading a letter `c`, he calls 170 | * {{{ 171 | * frequencies(c) = frequencies.getOrElse (c, 0) + 1 172 | * }}} 173 | * Why won't this work? Will it work if he used instead 174 | * {{{ 175 | * import scala.collection.JavaConversions.asScalaConcurrentMap 176 | * val frequencies: scala.collection.mutable.ConcurrentMap[Char, Int] = 177 | * new java.util.concurrent.ConcurrentHashMap[Char, Int] 178 | * }}} 179 | * 180 | * Solution: 181 | * 182 | * It won't work with SynchronizedMap since its not synchronize addition operation. 183 | * And its not enough using ConcurrentHashMap, we also need to perform threadsafe addition. 184 | * See the fixed code below. 185 | */ 186 | def getLetterFrequencyMap(files: Iterable[String]): Map[Char, Int] = { 187 | import scala.collection.JavaConversions.mapAsScalaConcurrentMap 188 | 189 | //val frequencies = new mutable.HashMap[Char, Int] with mutable.SynchronizedMap[Char, Int] 190 | val frequencies: concurrent.Map[Char, Int] = 191 | new java.util.concurrent.ConcurrentHashMap[Char, Int] 192 | 193 | val threads = files.map(file => 194 | new Thread(new Runnable() { 195 | override def run() = { 196 | val source = Source.fromInputStream(getClass.getResourceAsStream(file)) 197 | try { 198 | for (c <- source) { 199 | //frequencies(c) = frequencies.getOrElse(c, 0) + 1 200 | var incremented = false 201 | while (!incremented) { 202 | val oldVal = frequencies.putIfAbsent(c, 0).getOrElse(0) 203 | incremented = frequencies.replace(c, oldVal, oldVal + 1) 204 | } 205 | } 206 | } 207 | finally { 208 | source.close() 209 | } 210 | } 211 | })) 212 | threads.foreach(_.start()) 213 | threads.foreach(_.join()) 214 | 215 | frequencies.toMap 216 | } 217 | 218 | /** 219 | * Task 10: 220 | * 221 | * Harry Hacker reads a file into a string and wants to use a parallel collection to update 222 | * the letter frequencies concurrently on portions of the string. He uses the following code: 223 | * {{{ 224 | * val frequencies = new scala.collection.mutable.HashMap[Char, Int] 225 | * for (c <- str.par) frequencies(c) = frequencies.getOrElse(c, 0) + 1 226 | * }}} 227 | * Why is this a terrible idea? How can he really parallelize the computation? 228 | * (Hint: Use aggregate.) 229 | * 230 | * Solution: 231 | * 232 | * Its bad, because with parallel computation we shouldn't mutate the shared data. 233 | */ 234 | def getLetterFrequencyMap2(str: String): Map[Char, Int] = { 235 | str.par.aggregate(new mutable.HashMap[Char, Int])((freq, c) => { 236 | freq(c) = freq.getOrElse(c, 0) + 1 237 | freq 238 | }, (freq, freq2) => { 239 | for ((k, v) <- freq2) freq(k) = freq.getOrElse(k, 0) + v 240 | freq 241 | }).toMap 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/main/scala/Chapter14.scala: -------------------------------------------------------------------------------- 1 | 2 | 3 | object Chapter14 { 4 | 5 | /** 6 | * Task 1: 7 | * 8 | * Your Java Development Kit distribution has the source code for much of the JDK in the 9 | * `src.zip` file. Unzip and search for case labels (regular expression `case [^:]+:`). 10 | * Then look for comments starting with `//` and containing `[Ff]alls? thr` to catch comments 11 | * such as `// Falls through` or `// just fall thru`. 12 | * Assuming the JDK programmers follow the Java code convention, which requires such a comment, 13 | * what percentage of cases falls through? 14 | * 15 | * Solution: 16 | * {{{ 17 | * mkdir /tmp/java-test/src 18 | * unzip /usr/lib/jvm/java-7-oracle/src.zip -d /tmp/java-test/src 19 | * cd /tmp/java-test 20 | * grep --include=*.java -r -E 'case [^:]+:' src > 1.txt 21 | * grep --include=*.java -r -E '//.*[Ff]alls? thr' src > 2.txt 22 | * }}} 23 | * As a result on my machine there is around 9500 lines in the first file and 24 | * 100 lines in the second file. Then: 25 | * {{{ 26 | * (100 * 100%) / 9500 = 1% 27 | * }}} 28 | */ 29 | 30 | /** 31 | * Task 2: 32 | * 33 | * Using pattern matching, write a function `swap` that receives a pair of integers and 34 | * returns the pair with the components swapped. 35 | */ 36 | def swap(pair: (Int, Int)): (Int, Int) = pair match { 37 | case (one, two) => (two, one) 38 | } 39 | 40 | /** 41 | * Task 3: 42 | * 43 | * Using pattern matching, write a function `swap` that swaps the first two elements of 44 | * an array provided its length is at least two. 45 | */ 46 | def swap2(arr: Array[Int]): Array[Int] = arr match { 47 | case Array(first, second, _*) => 48 | arr(0) = second 49 | arr(1) = first 50 | arr 51 | case _ => arr 52 | } 53 | 54 | /** 55 | * Task 4: 56 | * 57 | * Add a case class `Multiple` that is a subclass of the `Item` class. For example, 58 | * `Multiple(10, Article("Blackwell Toaster", 29.95))` describes ten toasters. Of course, 59 | * you should be able to handle any items, such as bundles or multiples, in the second argument. 60 | * Extend the `price` function to handle this new case. 61 | */ 62 | sealed abstract class Item 63 | case class Article(description: String, price: Double) extends Item 64 | case class Bundle(description: String, discount: Double, items: Item*) extends Item 65 | case class Multiple(count: Int, item: Item) extends Item 66 | 67 | def price(it: Item): Double = it match { 68 | case Article(_, p) => p 69 | case Bundle(_, disc, its @ _*) => its.map(price).sum - disc 70 | case Multiple(count, item) => count * price(item) 71 | } 72 | 73 | /** 74 | * Task 5: 75 | * 76 | * One can use lists to model trees that store values only in the leaves. For example, the list 77 | * `((3 8) 2 (5))` describes the tree 78 | * {{{ 79 | * * 80 | * /|\ 81 | * * 2 * 82 | * /\ | 83 | * 3 8 5 84 | * }}} 85 | * However, some of the list elements are numbers and others are lists. In Scala, you cannot 86 | * have heterogeneous lists, so you have to use a `List[Any]`. Write a `leafSum` function to 87 | * compute the sum of all elements in the leaves, using pattern matching to differentiate 88 | * between numbers and lists. 89 | */ 90 | def leafSum(xs: List[Any]): Double = { 91 | xs.foldLeft(0.0) {(acc: Double, item) => item match { 92 | case n: Number => acc + n.doubleValue() 93 | case list: List[_] => acc + leafSum(list) 94 | case _ => acc 95 | } 96 | } 97 | } 98 | 99 | /** 100 | * Task 6: 101 | * 102 | * A better way of modeling such trees is with case classes. Let's start with binary trees. 103 | * {{{ 104 | * sealed abstract class BinaryTree 105 | * case class Leaf(value: Int) extends BinaryTree 106 | * case class Node(left: BinaryTree, right: BinaryTree) extends BinaryTree 107 | * }}} 108 | * Write a function to compute the sum of all elements in the leaves. 109 | */ 110 | object Task6 { 111 | 112 | sealed abstract class BinaryTree 113 | 114 | case class Leaf(value: Int) extends BinaryTree 115 | 116 | case class Node(left: BinaryTree, right: BinaryTree) extends BinaryTree 117 | 118 | def leafSum(bt: BinaryTree): Int = bt match { 119 | case leaf: Leaf => leaf.value 120 | case left Node right => leafSum(left) + leafSum(right) 121 | } 122 | } 123 | 124 | /** 125 | * Task 7: 126 | * 127 | * Extend the tree in the preceding exercise so that each node can have an arbitrary number 128 | * of children, and reimplement the `leafSum` function. The tree in exercise 5 should be as 129 | * {{{ 130 | * Node(Node(Leaf(3), Leaf(8)), Leaf(2), Node(Leaf(5))) 131 | * }}} 132 | */ 133 | object Task7 { 134 | 135 | sealed abstract class BinaryTree 136 | 137 | case class Leaf(value: Int) extends BinaryTree 138 | 139 | case class Node(children: BinaryTree*) extends BinaryTree 140 | 141 | def leafSum(bt: BinaryTree): Int = bt match { 142 | case leaf: Leaf => leaf.value 143 | case node: Node => node.children.foldLeft(0)((acc, item) => acc + leafSum(item)) 144 | } 145 | } 146 | 147 | /** 148 | * Task 8: 149 | * 150 | * Extend the tree in the preceding exercise so that each non-leaf node stores an operator 151 | * in addition to the child nodes. Then write a function `eval` that computes the value. 152 | * For example, the tree 153 | * {{{ 154 | * + 155 | * /|\ 156 | * * 2 - 157 | * /\ | 158 | * 3 8 5 159 | * }}} 160 | * has value `(3 x 8) + 2 + (-5) = 21`. 161 | */ 162 | object Task8 { 163 | 164 | sealed abstract class BinaryTree 165 | 166 | case class Leaf(value: Int) extends BinaryTree 167 | 168 | case class Node(op: Op, children: BinaryTree*) extends BinaryTree 169 | 170 | class Op private(val identity: Int, op: (Int, Int) => Int) { 171 | def apply(a: Int, b: Int): Int = op(a, b) 172 | } 173 | 174 | object Op { 175 | val Plus = new Op(0, _ + _) 176 | val Minus = new Op(0, _ - _) 177 | val Product = new Op(1, _ * _) 178 | } 179 | 180 | def eval(bt: BinaryTree): Int = bt match { 181 | case leaf: Leaf => leaf.value 182 | case node: Node => node.children.foldLeft(node.op.identity) { (acc, item) => 183 | node.op(acc, eval(item)) 184 | } 185 | } 186 | } 187 | 188 | /** 189 | * Task 9: 190 | * 191 | * Write a function that computes the sum of the non-None values in a `List[Option[Int]]`. 192 | * Don't use a match statement. 193 | */ 194 | def sumOfNonNoneValues(xs: List[Option[Int]]): Int = xs.foldLeft(0) { (acc, item) => 195 | acc + item.getOrElse(0) 196 | } 197 | 198 | /** 199 | * Task 10: 200 | * 201 | * Write a function that composes two functions of type `Double => Option[Double]`, yielding 202 | * another function of the same type. The composition should yield `None` if either function does. 203 | * For example, 204 | * {{{ 205 | * def f(x: Double) = if (x >= 0) Some(sqrt(x)) else None 206 | * def g(x: Double) = if (x != 1) Some(1 / (x - 1)) else None 207 | * val h = compose(f, g) 208 | * }}} 209 | * Then `h(2)` is `Some(1)`, and `h(1)` and `h(0)` are `None`. 210 | */ 211 | def compose(f1: Double => Option[Double], f2: Double => Option[Double]): Double => Option[Double] = { 212 | (x: Double) => f2(x) match { 213 | case Some(y) => f1(y) 214 | case None => None 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/main/scala/Chapter15.scala: -------------------------------------------------------------------------------- 1 | import java.io.IOException 2 | import org.junit.Test 3 | import scala.annotation.{tailrec, varargs} 4 | import scala.io.Source 5 | 6 | object Chapter15 { 7 | 8 | /** 9 | * Task 1: 10 | * 11 | * Write four `JUnit` test cases that use the `@Test` annotation with and without each of 12 | * its arguments. Run the tests with JUnit. 13 | */ 14 | class Task1 { 15 | 16 | @Test 17 | def testCase1(): Unit = { 18 | } 19 | 20 | @Test(timeout = 500) 21 | def testCase2(): Unit = { 22 | } 23 | 24 | @Test(expected = classOf[RuntimeException]) 25 | def testCase3(): Unit = { 26 | throw new RuntimeException() 27 | } 28 | 29 | @Test(timeout = 500, expected = classOf[RuntimeException]) 30 | def testCase4(): Unit = { 31 | throw new RuntimeException() 32 | } 33 | } 34 | 35 | /** 36 | * Task 2: 37 | * 38 | * Make an example class that shows every possible position of an annotation. 39 | * Use `@deprecated` as your sample annotation. 40 | * 41 | * Solution: 42 | * 43 | * I've used `@unchecked` instead to avoid compilation warnings, since `@deprecated` now takes 44 | * two arguments. 45 | */ 46 | @unchecked 47 | class Deprecated[@unchecked T] @unchecked() (@unchecked val field: Int) { 48 | 49 | @unchecked 50 | type Dep = Int @unchecked 51 | 52 | @unchecked 53 | var field2: Int @unchecked = 0 54 | 55 | @unchecked 56 | def unchecked(@deprecatedName('oldArg) newArg: Int): Int = { 57 | // annotation on an expression 58 | 1 + 2: @unchecked 59 | } 60 | } 61 | 62 | /** 63 | * Task 3: 64 | * 65 | * Which annotations from the Scala library use one of the meta-annotations 66 | * {{{ 67 | * @param, @field, @getter, @setter, @beanGetter, or @beanSetter? 68 | * }}} 69 | * 70 | * Solution: 71 | * 72 | * - `@deprecated` 73 | * - `@deprecatedName` 74 | * - `@BeanProperty` 75 | */ 76 | 77 | /** 78 | * Task 4: 79 | * 80 | * Write a Scala method `sum` with variable integer arguments that returns the sum of its 81 | * arguments. Call it from Java. 82 | * 83 | * @see Chapter15Task4.java 84 | */ 85 | @varargs 86 | def sum(args: Int*): Int = { 87 | args.sum 88 | } 89 | 90 | /** 91 | * Task 5: 92 | * 93 | * Write a Scala method that returns a string containing all lines of a file. Call it from Java. 94 | */ 95 | @throws[IOException] 96 | def fileToString(file: String): String = { 97 | val inStream = getClass.getResourceAsStream(file) 98 | if (inStream == null) { 99 | throw new IOException("Resource is not found: " + file) 100 | } 101 | 102 | Source.fromInputStream(inStream).mkString 103 | } 104 | 105 | /** 106 | * Task 6: 107 | * 108 | * Write a Scala object with a `volatile` `Boolean` field. Have one thread sleep for some time, 109 | * then set the field to `true`, print a message, and exit. Another thread will keep checking 110 | * whether the field is `true`. If so, it prints a message and exits. If not, it sleeps for 111 | * a short time and tries again. What happens if the variable is not volatile? 112 | * 113 | * Solution: 114 | * 115 | * If the variable is not volatile, then threads may see the changes to this variable with some 116 | * delay, or may not see changes at all. Since non-volatile (normal) variables may be cached. 117 | */ 118 | object Work { 119 | 120 | @volatile 121 | private var done = false 122 | 123 | def doWork(): Unit = { 124 | val threads = List(new Thread(new Runnable { 125 | 126 | override def run(): Unit = { 127 | Thread.sleep(200) 128 | 129 | Work.done = true 130 | println("Work.done flag was set to true") 131 | } 132 | }), new Thread(new Runnable { 133 | 134 | override def run(): Unit = { 135 | while (!Work.done) { 136 | println("Work is not done yet, waiting...") 137 | Thread.sleep(100) 138 | } 139 | 140 | println("Work is done, exiting") 141 | } 142 | })) 143 | 144 | threads.foreach(_.start()) 145 | threads.foreach(_.join()) 146 | } 147 | } 148 | 149 | /** 150 | * Task 7: 151 | * 152 | * Give an example to show that the tail recursion optimization is not valid when a method 153 | * can be overridden. 154 | * 155 | * Solution: 156 | * 157 | * To be tail recursive the method in class should be either `private` or `final`. 158 | */ 159 | class TailRecursion { 160 | 161 | @tailrec 162 | final def sum(xs: List[Int], acc: Int = 0): Int = xs match { 163 | case head :: tail => sum(tail, acc + head) 164 | case _ => acc 165 | } 166 | } 167 | 168 | /** 169 | * Task 8: 170 | * 171 | * Add the `allDifferent` method to an object, compile and look at the bytecode. 172 | * What methods did the `@specialized` annotation generate? 173 | */ 174 | def allDifferent[@specialized T](x: T, y: T, z: T): Boolean = x != y && x != z && y != z 175 | 176 | /** 177 | * Task 9: 178 | * 179 | * The `Range.foreach` method is annotated as `@specialized(Unit)`. Why? 180 | * Look at the bytecode by running 181 | * {{{ 182 | * javap -classpath /path/to/scala/lib/scala-library.jar scala.collection.immutable.Range 183 | * }}} 184 | * and consider the `@specialized` annotations on `Function1`. Click on the `Function1.scala` 185 | * link in Scaladoc to see them. 186 | * 187 | * Solution: 188 | * 189 | * In the bytecode for my scala library version (2.11.7): 190 | * {{{ 191 | * public final void foreach$mVc$sp(scala.Function1); 192 | * }}} 193 | * Int the source code, from `scala.collection.immutable.Range`: 194 | * {{{ 195 | * override def foreach[@specialized(Unit) U](f: Int => U) 196 | * }}} 197 | * Int the source code, from `Function1.scala`: 198 | * {{{ 199 | * trait Function1[@specialized(Int, Long, Float, Double) -T1, 200 | * @specialized(Unit, Boolean, Int, Float, Long, Double) +R] 201 | * }}} 202 | * 203 | * Its like this, because `foreach[U]` has parametrized return type and Function1 is specialized, 204 | * so `foreach` in Range class is also specialized for `Unit` only to use specialized version of 205 | * Function1. 206 | */ 207 | 208 | /** 209 | * Task 10: 210 | * 211 | * Add `assert(n >= 0)` to a factorial method. 212 | * Compile with assertions enabled and verify that factorial(-1) throws an exception. 213 | * Compile without assertions. What happens? Use `javap` to check what happened to the assertion 214 | * call. 215 | * 216 | * Solution: 217 | * 218 | * After compiling without assertions (by specifying `-Xelide-below 2001` scala compiler option), 219 | * `AssertionError` is not thrown anymore and the call to 220 | * {{{ 221 | * // Method scala/Predef$.assert:(Z)V 222 | * }}} 223 | * is removed from the generated bytecode. 224 | */ 225 | def factorial(n: Int): Int = { 226 | assert(n >= 0) 227 | 228 | n // <- implementation is not important here 229 | } 230 | } 231 | 232 | object Chapter15WorkApp extends App { 233 | 234 | Chapter15.Work.doWork() 235 | } 236 | -------------------------------------------------------------------------------- /src/main/scala/Chapter16.scala: -------------------------------------------------------------------------------- 1 | import scala.collection.mutable 2 | import scala.xml._ 3 | import scala.xml.dtd.DocType 4 | import scala.xml.parsing.XhtmlParser 5 | import scala.xml.transform.{RewriteRule, RuleTransformer} 6 | 7 | object Chapter16 { 8 | 9 | /** 10 | * Task 1: 11 | * 12 | * What is `(0)`? `(0)(0)`? Why? 13 | * 14 | * Solution: 15 | * 16 | * Since `Elem` extends `Node` and `Node` extends `NodeSeq`, which is in turn extends 17 | * `Seq[Node]`, in other words an XML element is represented as node sequence of one item. 18 | * So, expression `elem(0)` will always return the same `elem`: 19 | * {{{ 20 | * elem(0) == elem 21 | * elem(0)(0) == elem 22 | * elem(0)(0)(0) == elem 23 | * ... 24 | * }}} 25 | */ 26 | 27 | /** 28 | * Task 2: 29 | * 30 | * What is the result of 31 | * {{{ 32 | *

38 | * }}} 39 | * How do you fix it? 40 | * 41 | * Solution: 42 | * 43 | * The given snippet produces an error in the third `li` element: "No closing Tag", because 44 | * the brace `{` symbol is interpreted by Scala compiler as start of Scala expression. 45 | * To fix it we can escape braces by using '{{' and '}}'. 46 | * 47 | * @see Chapter16Spec.scala 48 | */ 49 | 50 | /** 51 | * Task 3: 52 | * 53 | * Contrast 54 | * {{{ 55 | *
  • Fred
  • match { case
  • {Text(t)}
  • => t } 56 | * }}} 57 | * and 58 | * {{{ 59 | *
  • {"Fred"}
  • match { case
  • {Text(t)}
  • => t } 60 | * }}} 61 | * Why do they act differently? 62 | * 63 | * Solution: 64 | * 65 | * Since embedded strings, like `{"Fred"}` don't get turned into `Text` nodes we cannot 66 | * properly pattern match using `Text` node. That's why our second expression failed. 67 | * To fix it we should either rewrite our patten match expression or we can wrap embedded strings 68 | * into `Text` node: 69 | * {{{ 70 | *
  • {Text("Fred")}
  • match { case
  • {Text(t)}
  • => t } 71 | * }}} 72 | */ 73 | 74 | /** 75 | * Task 4: 76 | * 77 | * Read an XHTML file and print all `img` elements that don't have an `alt` attribute. 78 | */ 79 | def printImgWithoutAlt(file: String): Unit = { 80 | val root = XML.load(getClass.getResourceAsStream(file)) 81 | for (n <- root \\ "img" if n.attribute("alt").isEmpty) { 82 | println(n) 83 | } 84 | } 85 | 86 | /** 87 | * Task 5: 88 | * 89 | * Print the names of all images in an XHTML file. That is, print all `src` attribute values 90 | * inside `img` elements. 91 | */ 92 | def printAllImg(file: String): Unit = { 93 | val root = XML.load(getClass.getResourceAsStream(file)) 94 | for (n <- root \\ "img"; 95 | src <- n.attribute("src")) { 96 | 97 | println(src.text) 98 | } 99 | } 100 | 101 | /** 102 | * Task 6: 103 | * 104 | * Read an XHTML file and print a table of all hyperlinks in the file, together with their URLs. 105 | * That is, print the child text and the `href` attribute of each a element. 106 | */ 107 | def printAllHyperlinks(file: String): Unit = { 108 | val root = XML.load(getClass.getResourceAsStream(file)) 109 | var maxTextLen = 0 110 | var maxHrefLen = 0 111 | 112 | // extract hyperlinks 113 | val links = mutable.Buffer[(String, String)]() 114 | for (n <- root \\ "a"; 115 | hrefAttr <- n.attribute("href")) { 116 | 117 | // extract text from a tag 118 | val sb = new StringBuilder() 119 | for (c <- n.child) sb ++= (c match { 120 | case Text(item) => item.trim 121 | case item => item.toString() 122 | }) 123 | 124 | val text = sb.toString() 125 | val href = hrefAttr.text 126 | maxTextLen = if (maxTextLen < text.length) text.length else maxTextLen 127 | maxHrefLen = if (maxHrefLen < href.length) href.length else maxHrefLen 128 | links += Tuple2(text, href) 129 | } 130 | 131 | val headerAndFooter: String = { 132 | val sb = new StringBuilder("+") 133 | for (_ <- 0 until maxTextLen) sb += '-' 134 | sb ++= "--+--" 135 | for (_ <- 0 until maxHrefLen) sb += '-' 136 | sb += '+' 137 | sb.toString() 138 | } 139 | 140 | // print extracted hyperlinks as table 141 | println(headerAndFooter) 142 | for ((text, href) <- links) { 143 | print("| ") 144 | print(text) 145 | for (_ <- text.length until maxTextLen) print(' ') 146 | print(" | ") 147 | print(href) 148 | for (_ <- href.length until maxHrefLen) print(' ') 149 | println(" |") 150 | } 151 | 152 | println(headerAndFooter) 153 | } 154 | 155 | /** 156 | * Task 7: 157 | * 158 | * Write a function that has a parameter of type `Map[String, String]` and returns a `dl` element 159 | * with a `dt` for each key and `dd` for each value. For example, 160 | * {{{ 161 | * Map("A" -> "1", "B" -> "2") 162 | * }}} 163 | * should yield `
    A
    1
    B
    2
    `. 164 | */ 165 | def mapToXml(map: Map[String, String]): Elem = { 166 |
    { 167 | for ((key, value) <- map) yield { 168 |
    {key}
    169 |
    {value}
    170 | } 171 | }
    172 | } 173 | 174 | /** 175 | * Task 8: 176 | * 177 | * Write a function that takes a `dl` element and turns it into a `Map[String, String]`. 178 | * This function should be the inverse of the function in the preceding exercise, provided 179 | * all `dt` children are distinct. 180 | */ 181 | def xmlToMap(elem: Elem): Map[String, String] = elem match { 182 | case
    {children @ _*}
    => 183 | val map = new mutable.HashMap[String, String] 184 | var currKey = "" 185 | for (child <- children) child match { 186 | case
    {key}
    => currKey = key.text.trim 187 | case
    {value}
    => map(currKey) = value.text.trim 188 | } 189 | 190 | map.toMap 191 | 192 | case _ => Map.empty 193 | } 194 | 195 | /** 196 | * Task 9: 197 | * 198 | * Transform an XHTML document by adding an `alt="TODO"` attribute to all img elements without 199 | * an `alt` attribute, preserving everything else. 200 | */ 201 | def transformXhtml(root: Node): Node = { 202 | val rule = new RewriteRule { 203 | override def transform(n: Node) = n match { 204 | case e @ if e.attribute("alt").isEmpty || e.attributes("alt").text.isEmpty => 205 | e.asInstanceOf[Elem] % Attribute(null, "alt", "TODO", Null) 206 | case _ => n 207 | } 208 | } 209 | 210 | new RuleTransformer(rule).transform(root).head 211 | } 212 | 213 | /** 214 | * Task 10: 215 | * 216 | * Write a function that reads an XHTML document, carries out the transformation of 217 | * the preceding exercise, and saves the result. 218 | * Be sure to preserve the DTD and any CDATA sections. 219 | */ 220 | def transformXhtmlFile(inFile: String, outFile: String): Unit = { 221 | val doc = new XhtmlParser(scala.io.Source.fromInputStream( 222 | getClass.getResourceAsStream(inFile))).initialize.document() 223 | 224 | val xhtml = transformXhtml(doc.docElem) 225 | val dtd = doc.dtd 226 | XML.save(outFile, xhtml, enc = "UTF-8", 227 | doctype = DocType(xhtml.label, dtd.externalID, dtd.decls)) 228 | } 229 | } 230 | 231 | object Chapter16PrintImgWithoutAltApp extends Utils.FileApp(Chapter16.printImgWithoutAlt) 232 | 233 | object Chapter16PrintAllImgApp extends Utils.FileApp(Chapter16.printAllImg) 234 | 235 | object Chapter16PrintAllHyperlinksApp extends Utils.FileApp(Chapter16.printAllHyperlinks) 236 | -------------------------------------------------------------------------------- /src/main/scala/Chapter17.scala: -------------------------------------------------------------------------------- 1 | 2 | 3 | /** 4 | * Task 1: 5 | * 6 | * Define an immutable `class Pair[T, S]` with a method `swap` that returns a new pair with 7 | * the components swapped. 8 | */ 9 | package task1701 { 10 | 11 | class Pair[T, S](val first: T, val second: S) { 12 | 13 | def swap(): Pair[S, T] = new Pair(second, first) 14 | } 15 | 16 | } 17 | 18 | /** 19 | * Task 2: 20 | * 21 | * Define a mutable `class Pair[T]` with a method `swap` that swaps the components of the pair. 22 | */ 23 | package task1702 { 24 | 25 | class Pair[T](var first: T, var second: T) { 26 | 27 | def swap(): Unit = { 28 | val tmp = first 29 | first = second 30 | second = tmp 31 | } 32 | } 33 | 34 | } 35 | 36 | /** 37 | * Task 3: 38 | * 39 | * Given a `class Pair[T, S]`, write a generic method `swap` that takes a pair as its argument 40 | * and returns a new pair with the components swapped. 41 | */ 42 | object Chapter17Task03 { 43 | 44 | import task1701._ 45 | 46 | def swap[T, S](pair: Pair[T, S]): Pair[S, T] = new Pair(pair.second, pair.first) 47 | 48 | } 49 | 50 | /** 51 | * Task 4: 52 | * 53 | * Why don't we need a lower bound for the `replaceFirst` method in Section 17.3, 54 | * "Bounds for Type Variables”, on page 232 if we want to replace the first component of 55 | * a `Pair[Person]` with a `Student`? 56 | * 57 | * Solution: 58 | * 59 | * We don't need a lower bound because we replacing with a sub-class, which is OK, since 60 | * the result type is still `Pair[Person]`. 61 | */ 62 | package task1704 { 63 | 64 | class Person(val name: String) 65 | class Student(name: String) extends Person(name) 66 | 67 | class Pair[T](val first: T, val second: T) { 68 | 69 | def replaceFirst(newFirst: T): Pair[T] = new Pair(newFirst, second) 70 | } 71 | 72 | } 73 | 74 | /** 75 | * Task 5: 76 | * 77 | * Why does `RichInt` implement `Comparable[Int]` and not `Comparable[RichInt]`? 78 | * 79 | * Solution: 80 | * 81 | * To be able to use view bounds, like 82 | * {{{ 83 | * class Pair[T <% Comparable[T]](val first: T, val second: T) 84 | * }}} 85 | * which than can be used with `Int` types, like 86 | * {{{ 87 | * new Pair(1, 2) 88 | * }}} 89 | * we need to have implicit conversion from `T` to `Comparable[T]`. `RichInt` class 90 | * implements `Comparable[Int]` and there is implicit conversion from `Int` to `RichInt`. 91 | * So, we don't use `RichInt` class directly. 92 | */ 93 | 94 | /** 95 | * Task 6: 96 | * 97 | * Write a generic method `middle` that returns the middle element from any `Iterable[T]`. 98 | * For example, `middle("World")` is 'r'. 99 | */ 100 | object Chapter17Task06 { 101 | 102 | //def middle[T](xs: Iterable[T]): Option[T] = { 103 | //def middle[A, C](xs: C)(implicit ev: C <:< Iterable[A]): Option[A] = { 104 | //def middle[A, C <% Iterable[A]](xs: C): Option[A] = { 105 | def middle[A, C](xs: C)(implicit ev: C => Iterable[A]): Option[A] = { 106 | val size = xs.size 107 | if (size % 2 == 0) { 108 | return None 109 | } 110 | 111 | var distance = size / 2 112 | xs.find { _ => 113 | val found = if (distance == 0) true else false 114 | distance -= 1 115 | found 116 | } 117 | } 118 | } 119 | 120 | /** 121 | * Task 7: 122 | * 123 | * Look through the methods of the `Iterable[+A]` trait. Which methods use the type parameter `A`? 124 | * Why is it in a covariant position in these methods? 125 | * 126 | * Solution: 127 | * 128 | * Parameter `A` used in the following methods, for example: `head`, `last`, `min`, `max`. 129 | * Its used in a covariant position since its defined with the covariant variance annotation (+A). 130 | */ 131 | 132 | /** 133 | * Task 8: 134 | * 135 | * In Section 17.10, "Co- and Contravariant Positions", on page 238, the `replaceFirst` method 136 | * has a type bound. Why can't you define an equivalent method on a mutable `Pair[T]`? 137 | * {{{ 138 | * def replaceFirst[R >: T](newFirst: R) { first = newFirst } // Error 139 | * }}} 140 | * 141 | * Solution: 142 | * 143 | * It is an error because the used type bound (`R >: T`) allows passing supper type instances. 144 | * But we can change the type bound definition to allow passing sub type instances: 145 | * {{{ 146 | * def replaceFirst[R <: T](newFirst: R) { first = newFirst } 147 | * }}} 148 | */ 149 | 150 | /** 151 | * Task 9: 152 | * 153 | * It may seem strange to restrict method parameters in an immutable `class Pair[+T]`. However, 154 | * suppose you could define 155 | * {{{ 156 | * def replaceFirst(newFirst: T) 157 | * }}} 158 | * in a `Pair[+T]`. The problem is that this method can be overridden in an unsound way. 159 | * Construct an example of the problem. Define a subclass `NastyDoublePair` of `Pair[Double]` 160 | * that overrides `replaceFirst` so that it makes a pair with the square root of `newFirst`. 161 | * Then construct the call `replaceFirst("Hello")` on a `Pair[Any]` that is actually 162 | * a `NastyDoublePair`. 163 | */ 164 | object Chapter17Task09 { 165 | 166 | class Pair[+T](val first: T, val second: T) { 167 | 168 | //def replaceFirst(newFirst: T): Pair[T] = new Pair(newFirst, second) 169 | 170 | def replaceFirst[R >: T](newFirst: R): Pair[R] = new Pair(newFirst, second) 171 | } 172 | 173 | class NastyDoublePair(first: Double, second: Double) extends Pair[Double](first, second) { 174 | 175 | //override def replaceFirst[R >: Double](newFirst: R) = new Pair(math.sqrt(newFirst), second) 176 | 177 | override def replaceFirst[R >: Double](newFirst: R) = new Pair(math.sqrt(first), second) 178 | } 179 | 180 | def check(pair: Pair[Any]): Pair[Any] = pair.replaceFirst("Hello") 181 | } 182 | 183 | /** 184 | * Task 10: 185 | * 186 | * Given a mutable `Pair[S, T]` class, use a type constraint to define a `swap` method that can 187 | * be called if the type parameters are the same. 188 | */ 189 | package task1710 { 190 | 191 | class Pair[S, T](var first: S, var second: T) { 192 | 193 | def swap(implicit ev: T =:= S): Unit = { 194 | val tmp = first.asInstanceOf[T] 195 | first = second 196 | second = tmp 197 | } 198 | } 199 | 200 | } 201 | -------------------------------------------------------------------------------- /src/main/scala/Chapter18.scala: -------------------------------------------------------------------------------- 1 | import scala.beans.BeanProperty 2 | import scala.language.reflectiveCalls 3 | 4 | object Chapter18 { 5 | 6 | /** 7 | * Task 1: 8 | * 9 | * Implement a `Bug` class modeling a bug that moves along a horizontal line. 10 | * The `move` method moves in the current direction, 11 | * the `turn` method makes the bug turn around, 12 | * and the `show` method prints the current position. 13 | * Make these methods chainable. For example, 14 | * {{{ 15 | * bugsy.move(4).show().move(6).show().turn().move(5).show() 16 | * }}} 17 | * should display `4 10 5`. 18 | */ 19 | class Bug { 20 | 21 | private var position: Int = 0 22 | private var turnedAround: Boolean = false 23 | 24 | def move(steps: Int): this.type = { 25 | if (turnedAround) position -= steps 26 | else position += steps 27 | this 28 | } 29 | 30 | def turn(): this.type = { 31 | turnedAround = !turnedAround 32 | this 33 | } 34 | 35 | def show(): this.type = { 36 | print(" ") 37 | print(position) 38 | this 39 | } 40 | } 41 | 42 | /** 43 | * Task 2: 44 | * 45 | * Provide a fluent interface for the `Bug` class of the preceding exercise, so that one can 46 | * write 47 | * {{{ 48 | * bugsy move 4 and show and then move 6 and show turn around move 5 and show 49 | * }}} 50 | */ 51 | trait FluentBug { this: Bug => 52 | 53 | def and(obj: Show.type): this.type = this.show() 54 | 55 | def and(obj: Then.type): this.type = this 56 | 57 | def turn(obj: Around.type): this.type = this.turn() 58 | } 59 | 60 | object Show 61 | object Then 62 | object Around 63 | 64 | val show = Show 65 | val then = Then 66 | val around = Around 67 | 68 | /** 69 | * Task 3: 70 | * 71 | * Complete the fluent interface in Section 18.1, "Singleton Types", on page 246 72 | * so that one can call 73 | * {{{ 74 | * book set Title to "Scala for the Impatient" set Author to "Cay Horstmann" 75 | * }}} 76 | */ 77 | trait FluentDocument { this: Document => 78 | 79 | private var useNextArgsAs: Option[Any] = None 80 | 81 | def set(obj: Title.type): this.type = setNextArgsAs(obj) 82 | def set(obj: Author.type): this.type = setNextArgsAs(obj) 83 | 84 | def to(arg: String): this.type = { 85 | for (obj <- useNextArgsAs) obj match { 86 | case Title => setTitle(arg) 87 | case Author => setAuthor(arg) 88 | } 89 | 90 | this 91 | } 92 | 93 | private def setNextArgsAs(obj: Any): this.type = { 94 | useNextArgsAs = Some(obj) 95 | this 96 | } 97 | } 98 | 99 | object Title 100 | object Author 101 | 102 | class Document { 103 | 104 | @BeanProperty var title: String = null 105 | @BeanProperty var author: String = null 106 | } 107 | 108 | class Book extends Document with FluentDocument { 109 | 110 | def addChapter(chapter: String): this.type = this 111 | } 112 | 113 | /** 114 | * Task 4: 115 | * 116 | * Implement the `equals` method for the `Member` class that is nested inside the `Network` 117 | * class in Section 18.2, "Type Projections", on page 247. For two members to be equal, 118 | * they need to be in the same network. 119 | */ 120 | class Network { 121 | 122 | class Member { 123 | 124 | override def equals(that: Any): Boolean = that match { 125 | case _: Member => true 126 | case _ => false 127 | } 128 | } 129 | } 130 | 131 | /** 132 | * Task 5: 133 | * 134 | * Consider the type alias 135 | * {{{ 136 | * type NetworkMember = n.Member forSome { val n: Network } 137 | * }}} 138 | * and the function 139 | * {{{ 140 | * def process(m1: NetworkMember, m2: NetworkMember) = (m1, m2) 141 | * }}} 142 | * How does this differ from the `process` function in Section 18.8, "Existential Types", 143 | * on page 252? 144 | * 145 | * Solution: 146 | * 147 | * The difference is that `NetworkMember` defines type alias for `Member` from any `Network`, 148 | * which is the same as defining `process` function like this: 149 | * {{{ 150 | * def process[M >: n.Member forSome { val n: Network }](m1: M, m2: M) = (m1, m2) 151 | * }}} 152 | * While the `process` function, from Section 18.8, accepts only members from the same network, 153 | * and it is defined like this: 154 | * {{{ 155 | * def process[M <: n.Member forSome { val n: Network }](m1: M, m2: M) = (m1, m2) 156 | * }}} 157 | */ 158 | import scala.language.existentials 159 | 160 | type NetworkMember = n.Member forSome { val n: Network } 161 | 162 | def process(m1: NetworkMember, m2: NetworkMember) = (m1, m2) 163 | 164 | def processAny[M >: n.Member forSome { val n: Network }](m1: M, m2: M) = (m1, m2) 165 | 166 | def processSame[M <: n.Member forSome { val n: Network }](m1: M, m2: M) = (m1, m2) 167 | 168 | /** 169 | * Task 6: 170 | * 171 | * The `Either` type in the Scala library can be used for algorithms that return either a result 172 | * or some failure information. Write a function that takes two parameters: 173 | * a sorted array of integers and an integer value. 174 | * Return either the index of the value in the array or the index of the element that is closest 175 | * to the value. Use an infix type as the return type. 176 | */ 177 | def findClosestValueIndex(sortedArr: Array[Int], value: Int): Int Either Int = { 178 | var i = 0 179 | while (i < sortedArr.length) { 180 | val elem = sortedArr(i) 181 | if (elem == value) return Left(i) 182 | else if (elem > value) return Right(i) 183 | i += 1 184 | } 185 | 186 | // return index if the last element 187 | Right(i - 1) 188 | } 189 | 190 | /** 191 | * Task 7: 192 | * 193 | * Implement a method that receives an object of any class that has a method 194 | * {{{ 195 | * def close(): Unit 196 | * }}} 197 | * together with a function that processes that object. Call the function and invoke the 198 | * `close` method upon completion, or when any exception occurs. 199 | */ 200 | def processAndClose[T <: { def close(): Unit }, R](obj: T)(f: T => R): R = { 201 | try { 202 | f(obj) 203 | } 204 | finally { 205 | obj.close() 206 | } 207 | } 208 | 209 | /** 210 | * Task 8: 211 | * 212 | * Write a function `printValues` with three parameters `f`, `from`, `to` that prints all values 213 | * of `f` with inputs from the given range. Here, `f` should be any object with an `apply` method 214 | * that consumes and yields an `Int`. For example, 215 | * {{{ 216 | * printValues((x: Int) => x * x, 3, 6) // Prints 9 16 25 36 217 | * printValues(Array(1, 1, 2, 3, 5, 8, 13, 21, 34, 55), 3, 6) // Prints 3 5 8 13 218 | * }}} 219 | */ 220 | def printValues(f: { def apply(i: Int): Int }, from: Int, to: Int): Unit = { 221 | var i = from 222 | while (i <= to) { 223 | print(" ") 224 | print(f(i)) 225 | i += 1 226 | } 227 | } 228 | 229 | /** 230 | * Task 9: 231 | * 232 | * Consider this class that models a physical dimension: 233 | * {{{ 234 | * abstract class Dim[T](val value: Double, val name: String) { 235 | * protected def create(v: Double): T 236 | * def +(other: Dim[T]) = create(value + other.value) 237 | * override def toString() = value + " " + name 238 | * } 239 | * }}} 240 | * Here is a concrete subclass: 241 | * {{{ 242 | * class Seconds(v: Double) extends Dim[Seconds](v, "s") { 243 | * override def create(v: Double) = new Seconds(v) 244 | * } 245 | * }}} 246 | * But now a knucklehead could define 247 | * {{{ 248 | * class Meters(v: Double) extends Dim[Seconds](v, "m") { 249 | * override def create(v: Double) = new Seconds(v) 250 | * } 251 | * }}} 252 | * allowing meters and seconds to be added. Use a self type to prevent that. 253 | */ 254 | abstract class Dim[T](val value: Double, val name: String) { this: T => 255 | 256 | protected def create(v: Double): T 257 | 258 | def +(other: Dim[T]) = create(value + other.value) 259 | 260 | override def toString = value + " " + name 261 | } 262 | 263 | class Seconds(v: Double) extends Dim[Seconds](v, "s") { 264 | override def create(v: Double) = new Seconds(v) 265 | } 266 | 267 | class Meters(v: Double) extends Dim[Meters](v, "m") { 268 | override def create(v: Double) = new Meters(v) 269 | } 270 | } 271 | 272 | /** 273 | * Task 10: 274 | * 275 | * Self types can usually be replaced with traits that extend classes, but there can be 276 | * situations where using self types changes the initialization and override orders. 277 | * Construct such an example. 278 | */ 279 | package task1810 { 280 | 281 | class A(val name: String) { 282 | 283 | override def toString: String = name 284 | } 285 | 286 | trait Named { self: A => 287 | 288 | override val name: String = "Named: " + self.name 289 | 290 | override def toString: String = "from Named: " + super.toString 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /src/main/scala/Chapter21.scala: -------------------------------------------------------------------------------- 1 | import Chapter11.Fraction 2 | import java.awt.Point 3 | import scala.annotation.tailrec 4 | import scala.collection.immutable.IndexedSeq 5 | import scala.io.StdIn 6 | import scala.language.implicitConversions 7 | 8 | 9 | object Chapter21 { 10 | 11 | /** 12 | * Task 1: 13 | * 14 | * How does `->` work? That is, how can `"Hello" -> 42` and `42 -> "Hello"` be pairs 15 | * `("Hello", 42)` and `(42, "Hello")`? Hint: `Predef.any2ArrowAssoc`. 16 | * 17 | * Solution: 18 | * 19 | * Currently, as of Scala 2.11.x, its implemented by using `implicit class ArrowAssoc` that 20 | * enriches any instance with `->` method. 21 | */ 22 | 23 | /** 24 | * Task 2: 25 | * 26 | * Define an operator `+%` that adds a given percentage to a value. For example, 27 | * `120 +% 10` should be `132`. Hint: Since operators are methods, not functions, 28 | * you will also need to provide an `implicit`. 29 | */ 30 | implicit class PercentAdder(private val value: Int) { 31 | 32 | def +%(percent: Int): Int = value + ((value * percent) / 100d).toInt 33 | } 34 | 35 | // as of Scala 2.11.x not needed any more, since we can use implicit class 36 | //implicit def int2PercentAdder(value: Int): PercentAdder = new PercentAdder(value) 37 | 38 | /** 39 | * Task 3: 40 | * 41 | * Define a `!` operator that computes the factorial of an integer. For example, 42 | * `5!` is `120`. You will need an enrichment class and an implicit conversion. 43 | */ 44 | implicit class Int2Factorial(private val value: Int) { 45 | 46 | def ! : Int = { 47 | @tailrec 48 | def fact(acc: Int, n: Int): Int = { 49 | if (n == 1) acc 50 | else fact(acc * n, n - 1) 51 | } 52 | 53 | fact(1, value) 54 | } 55 | } 56 | 57 | /** 58 | * Task 4: 59 | * 60 | * Some people are fond of "fluent APIs" that read vaguely like English sentences. 61 | * Create such an API for reading integers, floating-point numbers, and strings from the console. 62 | * For example: 63 | * {{{ 64 | * Read in aString askingFor "Your name" and 65 | * anInt askingFor "Your age" and 66 | * aDouble askingFor "Your weight". 67 | * }}} 68 | */ 69 | sealed trait FluentFieldType 70 | 71 | object aString extends FluentFieldType 72 | 73 | object anInt extends FluentFieldType 74 | 75 | object aDouble extends FluentFieldType 76 | 77 | def Read: FluentReader = new FluentReader 78 | 79 | class FluentReader { 80 | 81 | private var data: List[(String, Any)] = List() 82 | 83 | private var nextType: FluentFieldType = aString 84 | 85 | def getData: List[(String, Any)] = data 86 | 87 | def in(fieldType: FluentFieldType): FluentReader = { 88 | nextType = fieldType 89 | this 90 | } 91 | 92 | def and(fieldType: FluentFieldType): FluentReader = in(fieldType) 93 | 94 | def askingFor(fieldName: String): FluentReader = { 95 | print(fieldName + ": ") 96 | 97 | val value = nextType match { 98 | case _: aString.type => StdIn.readLine() 99 | case _: anInt.type => StdIn.readInt() 100 | case _: aDouble.type => StdIn.readDouble() 101 | } 102 | 103 | data = data :+ (fieldName, value) 104 | this 105 | } 106 | } 107 | 108 | /** 109 | * Task 5: 110 | * 111 | * Provide the machinery that is needed to compute 112 | * {{{ 113 | * smaller(Fraction(1, 7), Fraction(2, 9)) 114 | * }}} 115 | * in Section 21.6, "Implicit Conversions with Implicit Parameters," on page 310. 116 | * Supply a `class RichFraction` that extends `Ordered[Fraction]`. 117 | */ 118 | def smaller[T](a: T, b: T)(implicit order: T => Ordered[T]): T = 119 | if (order(a) < b) a 120 | else b 121 | 122 | implicit class RichFraction(self: Fraction) extends Ordered[Fraction] { 123 | 124 | override def compare(that: Fraction): Int = (self - that).num 125 | } 126 | 127 | /** 128 | * Task 6: 129 | * 130 | * Compare objects of the `class java.awt.Point` by lexicographic comparison. 131 | */ 132 | implicit class LexicographicPointOrdering(self: Point) extends Ordered[Point] { 133 | 134 | override def compare(that: Point): Int = 135 | if (self.x == that.x) self.y - that.y 136 | else self.x - that.x 137 | } 138 | 139 | /** 140 | * Task 7: 141 | * 142 | * Continue the previous exercise, comparing two points according to their distance to 143 | * the origin. How can you switch between the two orderings? 144 | * 145 | * Solution: 146 | * 147 | * We can switch between the two orderings by importing appropriate `implicit class`: 148 | * {{{ 149 | * import Chapter21.LexicographicPointOrdering 150 | * }}} 151 | * or 152 | * {{{ 153 | * import Chapter21.DistancePointOrdering 154 | * }}} 155 | */ 156 | implicit class DistancePointOrdering(self: Point) extends Ordered[Point] { 157 | 158 | override def compare(that: Point): Int = { 159 | val d1 = self.distance(0, 0) 160 | val d2 = that.distance(0, 0) 161 | 162 | if (d1 < d2) -1 163 | else if (d1 > d2) 1 164 | else 0 165 | } 166 | } 167 | 168 | /** 169 | * Task 8: 170 | * 171 | * Use the `implicitly` command in the REPL to summon the implicit objects described in 172 | * Section 21.5, "Implicit Parameters," on page 309 and 173 | * Section 21.6, "Implicit Conversions with Implicit Parameters," on page 310. 174 | * What objects do you get? 175 | * 176 | * Solution: 177 | * 178 | * To start the REPL console, start the following command in the project root directory: 179 | * {{{ 180 | * activator console 181 | * }}} 182 | * Here is the REPL output: 183 | * {{{ 184 | * scala> import Chapter21._ 185 | * import Chapter21._ 186 | * 187 | * scala> import Chapter11.Fraction 188 | * import Chapter11.Fraction 189 | * 190 | * scala> implicitly[Delimiters] 191 | * res0: Chapter21.Delimiters = Delimiters(<<,>>) 192 | * 193 | * scala> implicitly[Ordered[Fraction]] 194 | * :15: error: could not find implicit value for parameter e: Ordered[Chapter11.Fraction] 195 | * implicitly[Ordered[Fraction]] 196 | * ^ 197 | * 198 | * scala> implicitly[Ordering[Fraction]] 199 | * res2: Ordering[Chapter11.Fraction] = Chapter21$FractionOrdering@3af82646 200 | * 201 | * }}} 202 | * For the second case, `implicitly[Ordered[Fraction]]`, it didn't work, since there is no 203 | * appropriate implicit value defined. And our `implicit class RichFraction` is not suitable 204 | * since it requires an input parameter. 205 | */ 206 | case class Delimiters(left: String, right: String) 207 | 208 | implicit val quoteDelimiters = Delimiters("<<", ">>") 209 | 210 | class FractionOrdering extends Ordering[Fraction] { 211 | 212 | override def compare(x: Fraction, y: Fraction): Int = x.compare(y) 213 | } 214 | 215 | implicit val fractionOrdering = new FractionOrdering 216 | 217 | /** 218 | * Task 9: 219 | * 220 | * Look up the `=:=` object in `Predef.scala`. Explain how it works. 221 | * 222 | * Solution: 223 | * 224 | * It is defined like this: 225 | * {{{ 226 | * sealed abstract class =:=[From, To] extends (From => To) with Serializable 227 | * private[this] final val singleton_=:= = new =:=[Any,Any] { def apply(x: Any): Any = x } 228 | * object =:= { 229 | * implicit def tpEquals[A]: A =:= A = singleton_=:=.asInstanceOf[A =:= A] 230 | * } 231 | * }}} 232 | * So, `=:=` is a `Function` class with one argument and with one singleton instance and 233 | * implicit conversion method `tpEquals`, defined in its companion object. 234 | * When it is used as implicit evidence parameter: 235 | * {{{ 236 | * def someMethod[A, B](obj: A)(implicit ev: A =:= B): Unit = { 237 | * //can call methods, defined in B 238 | * obj.methodInB() 239 | * } 240 | * }}} 241 | * compiler sees that implicit argument is a function with one parameter, so it calls it 242 | * {{{ 243 | * ev(obj).methodInB() 244 | * }}} 245 | * to convert instance from one type `A` to the given type (`B` in this case). 246 | * In this way compiler can check/prove that instance confirms to the given constraint 247 | * (`=:=` equal types, in this case). 248 | */ 249 | 250 | /** 251 | * Task 10: 252 | * 253 | * The result of `"abc".map(_.toUpper)` is a `String`, but the result of `"abc".map(_.toInt)` 254 | * is a Vector. Find out why. 255 | * 256 | * Solution: 257 | * 258 | * Since `CanBuildFrom` factory trait for `String` is defined in the `WrappedString` companion 259 | * object like this: 260 | * {{{ 261 | * implicit def canBuildFrom: CanBuildFrom[WrappedString, Char, WrappedString] 262 | * }}} 263 | * it can build result `String` collection only for `Char` elements. 264 | * For other element types, like `Int`, as defined in the `IndexedSeq` companion object 265 | * {{{ 266 | * def newBuilder[A]: Builder[A, IndexedSeq[A]] = Vector.newBuilder[A] 267 | * }}} 268 | * `Vector` is the current default implementation. 269 | */ 270 | def stringMapTest(): Unit = { 271 | val str: String = "abc".map(_.toUpper) 272 | val seq: IndexedSeq[Int] = "abc".map(_.toInt) 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/main/scala/Utils.scala: -------------------------------------------------------------------------------- 1 | import java.io.{File, IOException} 2 | import scala.collection.immutable.Seq 3 | import scala.collection.mutable.ArrayBuffer 4 | 5 | object Utils { 6 | 7 | abstract class FileApp(process: String => Unit) extends App { 8 | if (args.length < 1) { 9 | sys.error("Expect file name as first argument") 10 | System.exit(1) 11 | } 12 | 13 | process(args(0)) 14 | } 15 | 16 | def getFileExt(file: File): String = { 17 | val name = file.getName 18 | name.substring(name.lastIndexOf('.') + 1) 19 | } 20 | 21 | def listAllFiles(dirPath: String, fileExtensions: String*): Seq[File] = { 22 | val dir = new File(dirPath) 23 | require(dir.isDirectory, s"Given path should represent directory: $dirPath") 24 | 25 | val buf = new ArrayBuffer[File]() 26 | def traverse(dir: File): Unit = { 27 | val files = dir.listFiles() 28 | if (files == null) { 29 | throw new IOException(s"Cannot read directory content: $dir") 30 | } 31 | 32 | for (file <- files) { 33 | if (file.isDirectory) traverse(file) 34 | else if (fileExtensions.isEmpty || fileExtensions.contains(getFileExt(file))) { 35 | buf += file 36 | } 37 | } 38 | } 39 | 40 | traverse(dir) 41 | buf.toIndexedSeq 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/scala/Chapter01Spec.scala: -------------------------------------------------------------------------------- 1 | import Chapter01._ 2 | import org.scalatest.{FlatSpec, Matchers} 3 | 4 | class Chapter01Spec extends FlatSpec with Matchers { 5 | 6 | "computeBigInt" should "compute 2^1024" in { 7 | //when & then 8 | computeBigInt() shouldBe BigInt("1797693134862315907729305190789024733617976978942306572734" + 9 | "300811577326758055009631327084773224075360211201138798713933576587897688144166224928474" + 10 | "3063947412437776789342486548527630221960124609411945308295208500576883815068234246288147" + 11 | "3913110540827237163350510684586298239947245938479716304835356329624224137216") 12 | } 13 | 14 | "randomFileName" should "yield a string such as 'qsnvbevtomcj38o06kul'" in { 15 | //when 16 | val result: String = randomFileName() 17 | 18 | //then 19 | result.length shouldBe 20 20 | result.matches("[a-z0-9]+") shouldBe true 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/scala/Chapter02Spec.scala: -------------------------------------------------------------------------------- 1 | import Chapter02._ 2 | import TestUtils.withOutput 3 | import org.scalatest.{FlatSpec, Matchers} 4 | 5 | class Chapter02Spec extends FlatSpec with Matchers { 6 | 7 | "signum" should "return -1 if n < 0, 1 if n > 0 and 0 if n = 0" in { 8 | //when & then 9 | signum(-2) shouldBe -1 10 | signum(-1) shouldBe -1 11 | signum(0) shouldBe 0 12 | signum(1) shouldBe 1 13 | signum(2) shouldBe 1 14 | } 15 | 16 | "task2" should "return Unit value" in { 17 | //when & then 18 | task2() shouldBe {} 19 | } 20 | 21 | "task3" should "return Unit as result of x = y = 1 expression" in { 22 | //when & then 23 | task3() shouldBe {} 24 | } 25 | 26 | "task4" should "implement simple Java for loop" in { 27 | //when 28 | val scalaOut = withOutput { 29 | task4() 30 | } 31 | 32 | //then 33 | scalaOut shouldBe withOutput { 34 | Chapter02Task4.javaForLoop() 35 | } 36 | } 37 | 38 | "countdown" should "print the numbers from n to 0" in { 39 | //when & then 40 | withOutput(countdown(3)) shouldBe """3 41 | |2 42 | |1 43 | |0 44 | |""".stripMargin 45 | 46 | withOutput(countdown(0)) shouldBe """0 47 | |""".stripMargin 48 | 49 | withOutput(countdown(-1)) shouldBe "" 50 | } 51 | 52 | "productLoop" should "compute product of Unicode codes letters in string using loop" in { 53 | //when & then 54 | productLoop("Hello") shouldBe 825152896 55 | } 56 | 57 | "productNoLoop" should "compute product of Unicode codes letters in string without loop" in { 58 | //when & then 59 | productNoLoop("Hello") shouldBe 825152896 60 | } 61 | 62 | "product" should "compute product of Unicode codes letters in string" in { 63 | //when & then 64 | product("Hello") shouldBe 825152896 65 | } 66 | 67 | "productRecursive" should "compute product of Unicode codes letters in string recursively" in { 68 | //when & then 69 | productRecursive("Hello") shouldBe 825152896 70 | } 71 | 72 | "pow" should "compute x^n recursively" in { 73 | //when & then 74 | pow(-2, 0) shouldBe 1.0 75 | pow(1, 0) shouldBe 1.0 76 | pow(0, 0) shouldBe 1.0 77 | pow(1, 0) shouldBe 1.0 78 | pow(2, 0) shouldBe 1.0 79 | pow(1, 2) shouldBe 1.0 80 | pow(1, 3) shouldBe 1.0 81 | pow(2, -2) shouldBe 0.25 82 | pow(2, -1) shouldBe 0.5 83 | pow(2, 0) shouldBe 1.0 84 | pow(2, 1) shouldBe 2.0 85 | pow(2, 2) shouldBe 4.0 86 | pow(2, 3) shouldBe 8.0 87 | pow(2, 4) shouldBe 16.0 88 | pow(3, 0) shouldBe 1.0 89 | pow(3, 1) shouldBe 3.0 90 | pow(3, 2) shouldBe 9.0 91 | pow(5, 0) shouldBe 1.0 92 | pow(5, 1) shouldBe 5.0 93 | pow(5, 2) shouldBe 25.0 94 | pow(5, 3) shouldBe 125.0 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/test/scala/Chapter03Spec.scala: -------------------------------------------------------------------------------- 1 | import Chapter03._ 2 | import org.scalatest._ 3 | 4 | import scala.collection.mutable 5 | import scala.collection.mutable.ArrayBuffer 6 | 7 | class Chapter03Spec extends FlatSpec with Matchers { 8 | 9 | "Chapter03" should "set a to an array of n random integers" in { 10 | val n = 5 11 | val a: Array[Int] = randomIntArray(n) 12 | a.length should be (n) 13 | a.foreach(v => { 14 | v should be >= 0 15 | v should be < n 16 | }) 17 | } 18 | 19 | it should "swap adjacent elements of integer array" in { 20 | val a = Array(1, 2, 3, 4, 5) 21 | val b = swapAdjacent(a) 22 | b shouldBe theSameInstanceAs(a) 23 | b shouldBe Array(2, 1, 4, 3, 5) 24 | 25 | swapAdjacent(Array()) shouldBe Array() 26 | swapAdjacent(Array(1)) shouldBe Array(1) 27 | swapAdjacent(Array(1, 2, 3, 4)) shouldBe Array(2, 1, 4, 3) 28 | } 29 | 30 | it should "swap adjacent elements of integer array and return new array" in { 31 | val a = Array(1, 2, 3, 4, 5) 32 | val b = swapAdjacentYield(a) 33 | b shouldNot be theSameInstanceAs a 34 | b shouldBe Array(2, 1, 4, 3, 5) 35 | 36 | swapAdjacentYield(Array()) shouldBe Array() 37 | swapAdjacentYield(Array(1)) shouldBe Array(1) 38 | swapAdjacentYield(Array(1, 2, 3, 4)) shouldBe Array(2, 1, 4, 3) 39 | } 40 | 41 | it should "produce new positives then negatives array" in { 42 | val a = Array(1, -2, 3, 0, 5, -4) 43 | val b: Array[Int] = positivesThenNegatives(a) 44 | b shouldNot be theSameInstanceAs a 45 | b shouldBe Array(1, 3, 5, -2, 0, -4) 46 | 47 | positivesThenNegatives(Array()) shouldBe Array() 48 | positivesThenNegatives(Array(1)) shouldBe Array(1) 49 | positivesThenNegatives(Array(1, 2, 3, 4)) shouldBe Array(1, 2, 3, 4) 50 | positivesThenNegatives(Array(-1, -2, 0, -4)) shouldBe Array(-1, -2, 0, -4) 51 | } 52 | 53 | it should "compute the average of an Array[Double]" in { 54 | val a = Array(1.5, -1.5, 3, 0) 55 | val b: Double = computeAverage(a) 56 | b shouldBe 0.75 57 | } 58 | 59 | it should "reverse sort Array[Int] in place" in { 60 | val a = Array(1, -1, 3, 0, 0, 2, 1) 61 | val b: Array[Int] = reverseSortArray(a) 62 | b shouldBe theSameInstanceAs(a) 63 | b shouldBe Array(3, 2, 1, 1, 0, 0, -1) 64 | } 65 | 66 | it should "reverse sort ArrayBuffer[Int] in place" in { 67 | val a = ArrayBuffer(1, -1, 3, 0, 2, 1) 68 | val b: ArrayBuffer[Int] = reverseSortArrayBuffer(a) 69 | b shouldBe theSameInstanceAs(a) 70 | b shouldBe ArrayBuffer(3, 2, 1, 1, 0, -1) 71 | } 72 | 73 | it should "remove duplicates" in { 74 | val a = Array(1, -1, 2, 0, 2, 1) 75 | val b: Array[Int] = removeDuplicates(a) 76 | b shouldNot be theSameInstanceAs a 77 | b shouldBe Array(1, -1, 2, 0) 78 | } 79 | 80 | it should "drop negatives except first" in { 81 | val a = ArrayBuffer(1, -1, -3, 0, -2, 1, -1) 82 | val b: ArrayBuffer[Int] = dropNegativesExceptFirst(a) 83 | b shouldBe theSameInstanceAs(a) 84 | b shouldBe ArrayBuffer(1, -1, 0, 1) 85 | } 86 | 87 | it should "return America time zones" in { 88 | val a: Array[String] = americaTimeZones 89 | a.length should be > 0 90 | a.head(0) shouldBe 'A' 91 | a.last(0) shouldBe 'Y' 92 | } 93 | 94 | it should "return java List as Scala Buffer" in { 95 | val a: mutable.Buffer[String] = javaListAsScalaBuffer 96 | a.length should be > 0 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/test/scala/Chapter04Spec.scala: -------------------------------------------------------------------------------- 1 | import java.util 2 | 3 | import Chapter04._ 4 | import org.scalatest.{FlatSpec, Matchers} 5 | 6 | import scala.collection.mutable 7 | 8 | class Chapter04Spec extends FlatSpec with Matchers { 9 | 10 | "Chapter04" should "set a to an array of n random integers" in { 11 | val gizmos: Map[String, Int] = gizmosWithReducedPrice() 12 | gizmos("iPhone") shouldBe 540 13 | gizmos("iPad") shouldBe 450 14 | gizmos("MacBook Pro") shouldBe 1800 15 | gizmos("ScalaDays 2016 Berlin") shouldBe 675 16 | } 17 | 18 | it should "count words using mutable Map" in { 19 | val words: mutable.Map[String, Int] = countWordsMutableMap() 20 | 21 | assertWordsMap(words) 22 | } 23 | 24 | it should "count words using immutable Map" in { 25 | val words: Map[String, Int] = countWordsImmutableMap() 26 | 27 | assertWordsMap(words) 28 | } 29 | 30 | it should "count words using SortedMap" in { 31 | val words: Map[String, Int] = countWordsSortedMap() 32 | 33 | assertWordsOrderedMap(words.toList) 34 | } 35 | 36 | it should "count words using TreeMap" in { 37 | val words: mutable.Map[String, Int] = countWordsTreeMap() 38 | 39 | assertWordsOrderedMap(words.toList) 40 | } 41 | 42 | it should "visit weekdays in insertion order" in { 43 | val weekdays: mutable.Map[String, Int] = weekdaysLinkedHashMap() 44 | 45 | weekdays.size shouldBe 7 46 | weekdays.toList shouldBe List( 47 | "Monday" -> util.Calendar.MONDAY, 48 | "Tuesday" -> util.Calendar.TUESDAY, 49 | "Wednesday" -> util.Calendar.WEDNESDAY, 50 | "Thursday" -> util.Calendar.THURSDAY, 51 | "Friday" -> util.Calendar.FRIDAY, 52 | "Saturday" -> util.Calendar.SATURDAY, 53 | "Sunday" -> util.Calendar.SUNDAY) 54 | } 55 | 56 | it should "format Java properties" in { 57 | val props: List[String] = formatJavaProperties() 58 | props.size should be > 0 59 | 60 | val separatorIndex = props.head.indexOf('|') 61 | separatorIndex should be > 0 62 | for (prop <- props) { 63 | prop.indexOf('|') shouldBe separatorIndex 64 | } 65 | } 66 | 67 | it should "return min and max values from Array[Int]" in { 68 | val (min: Int, max: Int) = minmax(Array(1, -2, 3, 0, 5, 4)) 69 | 70 | min shouldBe -2 71 | max shouldBe 5 72 | } 73 | 74 | it should "count less than, equal to, and greater than in Array[Int]" in { 75 | val (lt: Int, eq: Int, gt: Int) = lteqgt(Array(1, -2, 3, 0, 5, 4), 1) 76 | 77 | lt shouldBe 2 78 | eq shouldBe 1 79 | gt shouldBe 3 80 | } 81 | 82 | private def assertWordsMap(words: collection.Map[String, Int]): Unit = { 83 | words.size shouldBe 12 84 | words("Simple") shouldBe 1 85 | words("text") shouldBe 1 86 | words("file") shouldBe 2 87 | words("with") shouldBe 1 88 | words("example") shouldBe 1 89 | words("words.") shouldBe 2 90 | words("We") shouldBe 1 91 | words("will") shouldBe 1 92 | words("parse") shouldBe 1 93 | words("the") shouldBe 2 94 | words("and") shouldBe 1 95 | words("count") shouldBe 1 96 | } 97 | 98 | private def assertWordsOrderedMap(mapAsList: List[(String, Int)]): Unit = { 99 | mapAsList.size shouldBe 12 100 | mapAsList shouldBe List( 101 | "Simple" -> 1, 102 | "We" -> 1, 103 | "and" -> 1, 104 | "count" -> 1, 105 | "example" -> 1, 106 | "file" -> 2, 107 | "parse" -> 1, 108 | "text" -> 1, 109 | "the" -> 2, 110 | "will" -> 1, 111 | "with" -> 1, 112 | "words." -> 2) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/test/scala/Chapter05Spec.scala: -------------------------------------------------------------------------------- 1 | import Chapter05._ 2 | import org.scalatest.{FlatSpec, Matchers} 3 | 4 | class Chapter05Spec extends FlatSpec with Matchers { 5 | 6 | "Counter01" should "not turn negative at Int.MaxValue" in { 7 | //when 8 | val counter = new Counter01(Int.MaxValue - 1) 9 | 10 | //then 11 | counter.current shouldBe (Int.MaxValue - 1) 12 | counter.increment() shouldBe Int.MaxValue 13 | a [IllegalStateException] should be thrownBy { 14 | counter.increment() 15 | } 16 | } 17 | 18 | "BankAccount02" should "deposit, withdraw and check balance" in { 19 | val account = new BankAccount02 20 | account.balance shouldBe 0 21 | 22 | account.deposit(10) 23 | account.balance shouldBe 10 24 | 25 | account.withdraw(7) 26 | account.balance shouldBe 3 27 | } 28 | 29 | "Time03" should "check before" in { 30 | new Time03(0, 0).before(new Time03(0, 0)) shouldBe false 31 | new Time03(1, 1).before(new Time03(1, 1)) shouldBe false 32 | new Time03(1, 5).before(new Time03(1, 10)) shouldBe true 33 | new Time03(1, 5).before(new Time03(2, 10)) shouldBe true 34 | new Time03(1, 50).before(new Time03(1, 10)) shouldBe false 35 | } 36 | 37 | "Time04" should "check before" in { 38 | new Time04(0, 0).before(new Time04(0, 0)) shouldBe false 39 | new Time04(1, 1).before(new Time04(1, 1)) shouldBe false 40 | new Time04(1, 5).before(new Time04(1, 10)) shouldBe true 41 | new Time04(1, 5).before(new Time04(2, 10)) shouldBe true 42 | new Time04(1, 50).before(new Time04(1, 10)) shouldBe false 43 | } 44 | 45 | "Student05" should "call the JavaBeans getters and setters in Scala" in { 46 | val student = new Student05 47 | student.setName("Viktor") 48 | student.setId(23) 49 | 50 | student.getName shouldBe "Viktor" 51 | student.getId shouldBe 23 52 | } 53 | 54 | "Person06" should "turn negative ages in constructor to 0" in { 55 | new Person06(-5).age shouldBe 0 56 | new Person06(-1).age shouldBe 0 57 | new Person06(0).age shouldBe 0 58 | new Person06(1).age shouldBe 1 59 | new Person06(5).age shouldBe 5 60 | } 61 | 62 | "Person07" should "split name into firstName and lastName" in { 63 | val person = new Person07("Fred Smith") 64 | person.firstName shouldBe "Fred" 65 | person.lastName shouldBe "Smith" 66 | a [IllegalArgumentException] should be thrownBy { 67 | new Person07("SingleName") 68 | } 69 | } 70 | 71 | "Car08" should "provide 4 constructors" in { 72 | val car1 = new Car08("BMW", "X5") 73 | car1.manufacturer shouldBe "BMW" 74 | car1.modelName shouldBe "X5" 75 | car1.modelYear shouldBe -1 76 | car1.licensePlate shouldBe "" 77 | 78 | val car2 = new Car08("BMW", "X5", 2005) 79 | car2.manufacturer shouldBe "BMW" 80 | car2.modelName shouldBe "X5" 81 | car2.modelYear shouldBe 2005 82 | car2.licensePlate shouldBe "" 83 | 84 | val car3 = new Car08("BMW", "X5", "license") 85 | car3.manufacturer shouldBe "BMW" 86 | car3.modelName shouldBe "X5" 87 | car3.modelYear shouldBe -1 88 | car3.licensePlate shouldBe "license" 89 | 90 | val car4 = new Car08("BMW", "X5", 2005, "license") 91 | car4.manufacturer shouldBe "BMW" 92 | car4.modelName shouldBe "X5" 93 | car4.modelYear shouldBe 2005 94 | car4.licensePlate shouldBe "license" 95 | } 96 | 97 | "Employee10" should "use explicit fields and a default primary constructor" in { 98 | val employee = new Employee10 99 | employee.name shouldBe "John Q. Public" 100 | employee.salary shouldBe 0.0 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/test/scala/Chapter06Spec.scala: -------------------------------------------------------------------------------- 1 | import org.scalatest.{FlatSpec, Matchers} 2 | import task0604.Point 3 | 4 | class Chapter06Spec extends FlatSpec with Matchers { 5 | 6 | "Conversions" should "convert units using separate methods" in { 7 | Conversions.inchesToCentimeters(1).formatted("%.2f") shouldBe "2.54" 8 | Conversions.gallonsToLiters(1).formatted("%.3f") shouldBe "3.785" 9 | Conversions.milesToKilometers(1).formatted("%.6f") shouldBe "1.609347" 10 | } 11 | 12 | "UnitConversion" should "convert units using general super-class" in { 13 | val inchesToCentimeters: UnitConversion = InchesToCentimeters 14 | inchesToCentimeters.convert(1).formatted("%.2f") shouldBe "2.54" 15 | 16 | val gallonsToLiters: UnitConversion = GallonsToLiters 17 | gallonsToLiters.convert(1).formatted("%.3f") shouldBe "3.785" 18 | 19 | val milesToKilometers: UnitConversion = MilesToKilometers 20 | milesToKilometers.convert(1).formatted("%.6f") shouldBe "1.609347" 21 | } 22 | 23 | "Point" should "construct instances without using new" in { 24 | val point = Point(3, 4) 25 | point.x shouldBe 3 26 | point.y shouldBe 4 27 | } 28 | 29 | "Reverse" should "print the command-line arguments in reverse order" in { 30 | //when 31 | val (exit: Int, out: String, err: String) = TestUtils.runApp("Reverse", "Hello", "World") 32 | 33 | //then 34 | exit shouldBe 0 35 | out shouldBe "World Hello\n" 36 | err shouldBe "" 37 | } 38 | 39 | "PlayingCard" should "describe the four playing card suits" in { 40 | PlayingCard.Clubs.toString shouldBe "♣" 41 | PlayingCard.Diamonds.toString shouldBe "♦" 42 | PlayingCard.Hearts.toString shouldBe "♥" 43 | PlayingCard.Spades.toString shouldBe "♠" 44 | } 45 | 46 | it should "implement isRed function" in { 47 | PlayingCard.isRed(PlayingCard.Clubs) shouldBe false 48 | PlayingCard.isRed(PlayingCard.Diamonds) shouldBe true 49 | PlayingCard.isRed(PlayingCard.Hearts) shouldBe true 50 | PlayingCard.isRed(PlayingCard.Spades) shouldBe false 51 | } 52 | 53 | "RGB" should "describe the eight corners of the RGB color cube" in { 54 | assertRGB(RGB.Black, 0x000000, "Black") 55 | assertRGB(RGB.White, 0xffffff, "White") 56 | assertRGB(RGB.Red, 0xff0000, "Red") 57 | assertRGB(RGB.Lime, 0x00ff00, "Lime") 58 | assertRGB(RGB.Blue, 0x0000ff, "Blue") 59 | assertRGB(RGB.Yellow, 0xffff00, "Yellow") 60 | assertRGB(RGB.Cyan, 0x00ffff, "Cyan") 61 | assertRGB(RGB.Magenta, 0xff00ff, "Magenta") 62 | } 63 | 64 | private def assertRGB(rgb: RGB.Value, id: Int, name: String): Unit = { 65 | rgb.id shouldBe id 66 | rgb.toString shouldBe name 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/scala/Chapter07Spec.scala: -------------------------------------------------------------------------------- 1 | import org.scalatest.{FlatSpec, Matchers} 2 | 3 | import scala.collection.mutable 4 | 5 | class Chapter07Spec extends FlatSpec with Matchers { 6 | 7 | "puzzler" should "use a package com that isn’t at the top level" in { 8 | com.FromCom.value shouldBe 1 9 | 10 | import puzzler._ 11 | com.FromCom.value shouldBe 21 12 | } 13 | 14 | "random" should "has nextInt, nextDouble, and setSeed functions" in { 15 | random.nextInt() shouldBe 1013904223 16 | random.nextDouble() shouldBe (((1013904223 * 1664525) + 1013904223)/(Int.MaxValue + 1.0)) 17 | 18 | random.setSeed(1) 19 | random.nextInt() shouldBe 1015568748 20 | } 21 | 22 | "Chapter0706" should "copy all elements from a Java hash map into a Scala hash map" in { 23 | //given 24 | val javaHashMap = new java.util.HashMap[String, Int] 25 | javaHashMap.put("A", 1) 26 | javaHashMap.put("B", 2) 27 | javaHashMap.put("C", 3) 28 | 29 | //when 30 | val scalaHashMap: mutable.HashMap[String, Int] = Chapter0706.fromJavaHashMap(javaHashMap) 31 | 32 | //then 33 | scalaHashMap.size shouldBe javaHashMap.size 34 | scalaHashMap("A") shouldBe 1 35 | scalaHashMap("B") shouldBe 2 36 | scalaHashMap("C") shouldBe 3 37 | } 38 | 39 | "Chapter0709" should "print to error stream if password is not secret" in { 40 | //given 41 | val password = "wrong" 42 | 43 | //when 44 | val (exit, _, err) = TestUtils.runAppWithInput(password, "Chapter0709") 45 | 46 | //then 47 | exit shouldBe 1 48 | err.contains("Wrong password!") shouldBe true 49 | } 50 | 51 | it should "print greeting to standard output if password is secret" in { 52 | //given 53 | val password = "secret" 54 | 55 | //when 56 | val (exit, out, err) = TestUtils.runAppWithInput(password, "Chapter0709") 57 | 58 | //then 59 | exit shouldBe 0 60 | out.contains("Welcome " + System.getProperty("user.name")) shouldBe true 61 | err shouldBe "" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/scala/Chapter08Spec.scala: -------------------------------------------------------------------------------- 1 | import org.scalatest.{FlatSpec, Matchers} 2 | import task0804.{Bundle, Item, SimpleItem} 3 | import task0805.{LabeledPoint, Point} 4 | import task0806.{Circle, Rectangle, Shape} 5 | import task0807.Square 6 | import task0808.{Person, SecretAgent} 7 | 8 | class Chapter08Spec extends FlatSpec with Matchers { 9 | 10 | "CheckingAccount" should "charge $1 for every deposit and withdrawal" in { 11 | val account = new CheckingAccount(100) 12 | account.deposit(5) shouldBe 104 13 | account.withdraw(5) shouldBe 98 14 | } 15 | 16 | "SavingsAccount" should "earn interest every month" in { 17 | val account = new SavingsAccount(100) 18 | account.deposit(5) shouldBe 105 19 | account.withdraw(5) shouldBe 100 20 | account.deposit(3) shouldBe 103 21 | account.withdraw(3) shouldBe 99 22 | account.deposit(2) shouldBe 100 23 | account.earnMonthlyInterest() 24 | account.getBalance shouldBe 101 25 | account.withdraw(5) shouldBe 96 26 | } 27 | 28 | "Item" should "has SimpleItem and Bundle implementations" in { 29 | val item: Item = new SimpleItem(500, "iPhone 5s") 30 | item.price shouldBe 500 31 | item.description shouldBe "iPhone 5s" 32 | 33 | val bundle: Bundle = new Bundle 34 | bundle.isInstanceOf[Item] shouldBe true 35 | bundle.price shouldBe 0 36 | bundle.description shouldBe "" 37 | 38 | bundle.addItem(item).addItem(new SimpleItem(700, "iPhone 6")) 39 | bundle.price shouldBe 1200 40 | bundle.description shouldBe "iPhone 5s\n\niPhone 6" 41 | } 42 | 43 | "Point" should "has LabeledPoint subclass" in { 44 | val point: LabeledPoint = new LabeledPoint("Black Thursday", 1929, 230.07) 45 | 46 | point.isInstanceOf[task0805.Point] shouldBe true 47 | point.label shouldBe "Black Thursday" 48 | point.x shouldBe 1929 49 | point.y shouldBe 230.07 50 | } 51 | 52 | "Shape06" should "has Rectangle and Circle subclasses" in { 53 | val circle: Shape = new Circle(new Point(1, 2), 3) 54 | val circleCenter: Point = circle.centerPoint 55 | circleCenter.x shouldBe 1 56 | circleCenter.y shouldBe 2 57 | 58 | val rectangle: Shape = new Rectangle(1, 2, 3, 4) 59 | val rectangleCenter: Point = rectangle.centerPoint 60 | rectangleCenter.x shouldBe ((1 + 3) / 2) 61 | rectangleCenter.y shouldBe ((2 + 4) / 2) 62 | } 63 | 64 | "Square" should "extend java.awt.Rectangle and has three constructors" in { 65 | import java.awt.Rectangle 66 | 67 | val square1: Rectangle = new Square(1, 2, 3) 68 | square1.x shouldBe 1 69 | square1.y shouldBe 2 70 | square1.width shouldBe 3 71 | square1.height shouldBe 3 72 | 73 | val square2: Rectangle = new Square(4) 74 | square2.x shouldBe 0 75 | square2.y shouldBe 0 76 | square2.width shouldBe 4 77 | square2.height shouldBe 4 78 | 79 | val square3: Rectangle = new Square() 80 | square3.x shouldBe 0 81 | square3.y shouldBe 0 82 | square3.width shouldBe 0 83 | square3.height shouldBe 0 84 | } 85 | 86 | "SecretAgent" should "extend Person and hide name" in { 87 | val person: Person = new SecretAgent("Bond") 88 | person.name shouldBe "secret" 89 | person.toString shouldBe "secret" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/test/scala/Chapter09Spec.scala: -------------------------------------------------------------------------------- 1 | import Chapter09._ 2 | import TestUtils.{printToTmpFile, runApp} 3 | import java.io.File 4 | import org.scalatest.{FlatSpec, Matchers} 5 | import scala.io.Source.fromFile 6 | 7 | class Chapter09Spec extends FlatSpec with Matchers { 8 | 9 | "reverseLines" should "reverse lines in file" in { 10 | //given 11 | val file = printToTmpFile("reverseLines", """line 1 12 | |line 2 13 | |line 3 14 | |""".stripMargin) 15 | 16 | //when 17 | reverseLines(file) 18 | 19 | //then 20 | fromFile(file).mkString shouldBe """line 3 21 | |line 2 22 | |line 1 23 | |""".stripMargin 24 | } 25 | 26 | "replaceTabs" should "replace tabs with spaces using column boundaries" in { 27 | //given 28 | val file = printToTmpFile("replaceTabs", """text text2 text3 29 | | text text2 text3 30 | | text text2 text3 31 | |""".stripMargin) 32 | 33 | //when 34 | replaceTabs(file, 2) 35 | 36 | //then 37 | fromFile(file).mkString shouldBe """text text2 text3 38 | | text text2 text3 39 | | text text2 text3 40 | |""".stripMargin 41 | } 42 | 43 | "printLongWords" should "read a file and print all words longer than 12 chars" in { 44 | //given 45 | val file = printToTmpFile("printLongWords", "text toooooooolong text2 text3texttext2text3") 46 | 47 | //when 48 | val (exit, out, err) = runApp("Chapter09PrintLongWordsApp", file.getAbsolutePath) 49 | 50 | //then 51 | exit shouldBe 0 52 | err shouldBe "" 53 | out shouldBe """toooooooolong 54 | |text3texttext2text3 55 | |""".stripMargin 56 | } 57 | 58 | "printNumbersStat" should "read numbers from file and print sum, average, min, max" in { 59 | //given 60 | val file = printToTmpFile("printNumbersStat", "1 1.2 2.34 -5 25.5 0.0 1.234") 61 | 62 | //when 63 | val (exit, out, err) = runApp("Chapter09PrintNumbersStatApp", file.getAbsolutePath) 64 | 65 | //then 66 | exit shouldBe 0 67 | err shouldBe "" 68 | out shouldBe """sum: 26.274 69 | |average: 3.753 70 | |minimum: -5.000 71 | |maximum: 25.500 72 | |""".stripMargin 73 | } 74 | 75 | "printPowersOf2" should "print powers of 2 and their reciprocals to a file" in { 76 | //given 77 | val file = File.createTempFile("printPowersOf2", "txt") 78 | file.deleteOnExit() 79 | 80 | //when 81 | printPowersOf2(file) 82 | 83 | //then 84 | val res = new StringBuilder 85 | for (i <- 0 to 20) { 86 | res.append("%8.0f %f\n".format(math.pow(2.0, i), math.pow(2.0, -i))) 87 | } 88 | 89 | fromFile(file).mkString shouldBe res.toString 90 | } 91 | 92 | "printQuotedStrings" should "read source file and print all double quoted strings" in { 93 | //given 94 | val file = "src/main/scala/Chapter09.scala" 95 | 96 | //when 97 | val (exit, out, err) = runApp("Chapter09PrintQuotedStringsApp", file) 98 | 99 | //then 100 | exit shouldBe 0 101 | err shouldBe "" 102 | out shouldBe """\\s+ 103 | |\\s+ 104 | |sum: %.3f\n 105 | |average: %.3f\n 106 | |minimum: %.3f\n 107 | |maximum: %.3f\n 108 | |%8.0f %f 109 | |like this, maybe with \" or \\ 110 | |\"(([^\\\\\"]+|\\\\([btnfr\"'\\\\]|[0-3]?[0-7]{1,2}|u[0-9a-fA-F]{4}))*)\" 111 | |(?![\\d]+(\\.[\\d]+)?)\\w+ 112 | |(?i) 137 | | 138 | | 139 | | scala logo 140 | | 141 | | 142 | |""".stripMargin) 143 | 144 | //when 145 | val (exit, out, err) = runApp("Chapter09PrintSrcOfImageTagsApp", file.getAbsolutePath) 146 | 147 | //then 148 | exit shouldBe 0 149 | err shouldBe "" 150 | out shouldBe """http://scala-lang.org/resources/img/scala-logo-white.png 151 | |""".stripMargin 152 | } 153 | 154 | "countClassFiles" should "count .class files in directory and all sub-directories" in { 155 | //given 156 | val dir = "target/scala-2.11/classes/com" 157 | 158 | //when 159 | val count = countClassFiles(dir) 160 | 161 | //then 162 | count shouldBe 16 163 | } 164 | 165 | "Person" should "be serializable" in { 166 | //given 167 | val file = File.createTempFile("personSerialization", "bin") 168 | file.deleteOnExit() 169 | val fred = new Person("Fred") 170 | val bob = new Person("Bob") 171 | val john = new Person("John") 172 | fred.addFriend(bob) 173 | fred.addFriend(john) 174 | bob.addFriend(fred) 175 | bob.addFriend(john) 176 | john.addFriend(fred) 177 | john.addFriend(bob) 178 | val friends = Array[Person](fred, bob, john, new Person("Dummy")) 179 | 180 | //when 181 | savePersons(file, friends) 182 | val result: Array[Person] = readPersons(file) 183 | 184 | //then 185 | result.length shouldBe friends.length 186 | for ((person, friend) <- result.zip(friends)) { 187 | person.size shouldBe friend.size 188 | for ((p, f) <- person.zip(friend)) { 189 | p.name shouldBe f.name 190 | } 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/test/scala/Chapter10Spec.scala: -------------------------------------------------------------------------------- 1 | import Chapter10._ 2 | import java.beans.{PropertyChangeEvent, PropertyChangeListener} 3 | import java.io.{FilterInputStream, InputStream} 4 | import org.scalatest.{FlatSpec, Matchers} 5 | import scala.annotation.tailrec 6 | import scala.collection.mutable.ArrayBuffer 7 | import scala.io.Source 8 | 9 | class Chapter10Spec extends FlatSpec with Matchers { 10 | 11 | "RectangleLike" should "provide translate and grow methods" in { 12 | //given 13 | val egg = new java.awt.geom.Ellipse2D.Double(5, 10, 20, 30) with RectangleLike 14 | 15 | //when & then 16 | egg.translate(10, -10) 17 | egg.getX shouldBe 15 18 | egg.getY shouldBe 0 19 | egg.getWidth shouldBe 20 20 | egg.getHeight shouldBe 30 21 | 22 | //when & then 23 | egg.grow(10, 20) 24 | egg.getX shouldBe 5 25 | egg.getY shouldBe -20 26 | egg.getWidth shouldBe 40 27 | egg.getHeight shouldBe 70 28 | } 29 | 30 | "OrderedPoint" should "use lexicographic ordering" in { 31 | //when & then 32 | new OrderedPoint(1, 1).compare(new OrderedPoint(2, 2)) shouldBe -1 33 | new OrderedPoint(1, 1).compare(new OrderedPoint(2, 1)) shouldBe -1 34 | new OrderedPoint(1, 1).compare(new OrderedPoint(1, 2)) shouldBe -1 35 | new OrderedPoint(1, 1).compare(new OrderedPoint(1, 1)) shouldBe 0 36 | new OrderedPoint(1, 1).compare(new OrderedPoint(0, 1)) shouldBe 1 37 | new OrderedPoint(1, 1).compare(new OrderedPoint(1, 0)) shouldBe 1 38 | new OrderedPoint(1, 1).compare(new OrderedPoint(0, 0)) shouldBe 1 39 | } 40 | 41 | "bitSetLinearization" should "return linearization of BitSet traits" in { 42 | //when 43 | val result: List[String] = bitSetLinearization 44 | 45 | //then 46 | // got from here: 47 | // http://stackoverflow.com/questions/15623498/handy-ways-to-show-linearization-of-a-class 48 | val tpe = scala.reflect.runtime.universe.typeOf[scala.collection.BitSet] 49 | result shouldBe tpe.baseClasses.map(_.fullName) 50 | } 51 | 52 | "CryptoLogger" should "encrypt messages with the Caesar cipher default key" in { 53 | //given 54 | val logger = new TestLogger with CryptoLogger 55 | 56 | //when 57 | logger.log("12345") 58 | 59 | //then 60 | logger.message shouldBe "45678" 61 | } 62 | 63 | it should "encrypt messages with the Caesar cipher key -3" in { 64 | //given 65 | val logger = new TestLogger with CryptoLogger { 66 | override val key = -3 67 | } 68 | 69 | //when 70 | logger.log("45678") 71 | 72 | //then 73 | logger.message shouldBe "12345" 74 | } 75 | 76 | "PointBean" should "extend java.awt.Point with PropertyChangeSupportLike" in { 77 | //given 78 | val point = new PointBean 79 | 80 | //when 81 | var event: PropertyChangeEvent = null 82 | point.addPropertyChangeListener("x", new PropertyChangeListener { 83 | override def propertyChange(evt: PropertyChangeEvent): Unit = { 84 | event = evt 85 | } 86 | }) 87 | point.x += 1 88 | point.firePropertyChange("x", 0, 1) 89 | 90 | //then 91 | point.hasListeners("x") shouldBe true 92 | point.hasListeners("y") shouldBe false 93 | event.getPropertyName shouldBe "x" 94 | event.getOldValue shouldBe 0 95 | event.getNewValue shouldBe 1 96 | } 97 | 98 | "TestEngine" should "extend Engine with InfiniteEngine" in { 99 | //given 100 | val engine = new TestEngine 101 | 102 | //when 103 | engine.start() 104 | engine.stop() 105 | 106 | //then 107 | engine.model shouldBe "test" 108 | } 109 | 110 | "BufferedInputStreamLike" should "add buffering to an input stream" in { 111 | //given 112 | val in = new { 113 | override val bufferSize = 48 114 | } with FilterInputStream(getClass.getResourceAsStream("/myfile.txt")) 115 | with BufferedInputStreamLike 116 | with ConsoleLogger 117 | 118 | try { 119 | //when 120 | val result = Source.fromBytes(readBytes(in)).mkString 121 | 122 | //then 123 | result shouldBe """ 124 | |Simple text file with example words. 125 | |We will parse the file and count the words. 126 | |""".stripMargin 127 | } 128 | finally { 129 | in.close() 130 | } 131 | } 132 | 133 | "IterableInputStream" should "extends java.io.InputStream with the trait Iterable[Byte]" in { 134 | //given 135 | val in = new IterableInputStream(getClass.getResourceAsStream("/myfile.txt")) 136 | 137 | try { 138 | //when 139 | val result = new ArrayBuffer[Byte] 140 | result ++= in 141 | 142 | //then 143 | new String(result.toArray) shouldBe """ 144 | |Simple text file with example words. 145 | |We will parse the file and count the words. 146 | |""".stripMargin 147 | } 148 | finally { 149 | in.close() 150 | } 151 | } 152 | 153 | private def readBytes(in: InputStream): Array[Byte] = { 154 | val buf = new ArrayBuffer[Byte] 155 | 156 | @tailrec 157 | def read(): Unit = { 158 | val byte = in.read() 159 | if (byte == -1) return 160 | 161 | buf += byte.toByte 162 | read() 163 | } 164 | 165 | read() 166 | buf.toArray 167 | } 168 | 169 | class TestLogger extends Logger { 170 | var message = "" 171 | override def log(msg: String): Unit = message = msg 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/test/scala/Chapter11Spec.scala: -------------------------------------------------------------------------------- 1 | import Chapter11._ 2 | import org.scalatest.{FlatSpec, Matchers} 3 | 4 | class Chapter11Spec extends FlatSpec with Matchers { 5 | 6 | "Task1" should "evaluate expressions according to the precedence rules" in { 7 | //when & then 8 | (3 + 4 -> 5) shouldBe ((3 + 4) -> 5) 9 | (3 -> 4 + "5") shouldBe ((3 -> 4) + "5") 10 | } 11 | 12 | "Fraction" should "be normalized and has operations +, -, *, /" in { 13 | //when & then 14 | Fraction(15, -6).toString shouldBe "-5/2" 15 | (Fraction(1, 2) + Fraction(1, 2)).toString shouldBe "1/1" 16 | (Fraction(1, 2) - Fraction(1, 2)).toString shouldBe "0/1" 17 | (Fraction(1, 2) * Fraction(1, 2)).toString shouldBe "1/4" 18 | (Fraction(1, 2) / Fraction(1, 2)).toString shouldBe "1/1" 19 | (Fraction(1, 7) - Fraction(2, 9)).toString shouldBe "-5/63" 20 | 21 | //equals 22 | Fraction(15, -6) == Fraction(15, -6) shouldBe true 23 | Fraction(15, -6) == Fraction(-15, 6) shouldBe true 24 | Fraction(15, 6) == Fraction(-15, 6) shouldBe false 25 | //noinspection ComparingUnrelatedTypes 26 | Fraction(15, 6).equals("") shouldBe false 27 | Fraction(15, 6) == null shouldBe false 28 | } 29 | 30 | "Money" should "be normalized and has operations +, -, ==, <" in { 31 | //when & then 32 | Money(1, 150).toString shouldBe "2.50" 33 | (Money(1, 75) + Money(0, 50)).toString shouldBe "2.25" 34 | (Money(1, 75) + Money(0, 10)).toString shouldBe "1.85" 35 | (Money(2, 25) - Money(0, 50)).toString shouldBe "1.75" 36 | (Money(2, 25) - Money(0, 25)).toString shouldBe "2.00" 37 | Money(2, 25) == Money(0, 50) shouldBe false 38 | Money(2, 25) == Money(2, 25) shouldBe true 39 | Money(2, 25) < Money(2, 25) shouldBe false 40 | Money(1, 50) < Money(2, 25) shouldBe true 41 | } 42 | 43 | "Table" should "provide operators that construct an HTML table" in { 44 | //when 45 | val table = Table() | "Java" | "Scala" || "Gosling" | "Odersky" || "JVM" | "JVM, .NET" 46 | 47 | //then 48 | table.toHtml shouldBe "" + 49 | "" + 50 | "
    JavaScala
    GoslingOdersky
    JVMJVM, .NET
    " 51 | } 52 | 53 | "ASCIIArt" should "provide operators for combining figures horizontally and vertically" in { 54 | //given 55 | val art = new ASCIIArt + 56 | """ /\_/\""" + 57 | """( ' ' )""" + 58 | """( - )""" + 59 | """ | | |""" + 60 | """(__|__)""" 61 | 62 | //when 63 | val result = art | new ASCIIArt + 64 | """ -----""" + 65 | """ / Hello \""" + 66 | """ < Scala |""" + 67 | """ \ Coder /""" + 68 | """ -----""" 69 | 70 | //then 71 | art.toString shouldBe """ /\_/\ 72 | |( ' ' ) 73 | |( - ) 74 | | | | | 75 | |(__|__)""".stripMargin 76 | 77 | result.toString shouldBe """ /\_/\ ----- 78 | |( ' ' ) / Hello \ 79 | |( - ) < Scala | 80 | | | | | \ Coder / 81 | |(__|__) -----""".stripMargin 82 | } 83 | 84 | "BitSequence" should "store 64 bits, packed in Long" in { 85 | //given 86 | val bits = new BitSequence 87 | 88 | //when & then 89 | bits(0) shouldBe 0 90 | bits(0) = 1 91 | bits(0) shouldBe 1 92 | bits(1) shouldBe 0 93 | bits(1) = 1 94 | bits(1) shouldBe 1 95 | bits(63) shouldBe 0 96 | bits(63) = 1 97 | bits(63) shouldBe 1 98 | bits(0) shouldBe 1 99 | bits(0) = 0 100 | bits(0) shouldBe 0 101 | bits(1) shouldBe 1 102 | bits(1) = 0 103 | bits(1) shouldBe 0 104 | bits(63) shouldBe 1 105 | bits(63) = 0 106 | bits(63) shouldBe 0 107 | } 108 | 109 | "Matrix" should "supply operator + and apply method" in { 110 | //when 111 | val result = Matrix(2, 2)( 112 | 1, 2, 113 | 3, 4) + 114 | Matrix(2, 2)( 115 | 5, 6, 116 | 7, 8) 117 | 118 | //then 119 | result(0, 0) shouldBe 6 120 | result(0, 1) shouldBe 8 121 | result.toString shouldBe """[6, 8] 122 | |[10, 12]""".stripMargin 123 | } 124 | 125 | it should "supply operator * for scalars" in { 126 | //when 127 | val result = Matrix(2, 2)( 128 | 1, 2, 129 | 3, 4) * 2 130 | 131 | //then 132 | result.toString shouldBe """[2, 4] 133 | |[6, 8]""".stripMargin 134 | } 135 | 136 | it should "supply operator *" in { 137 | //when 138 | val result = Matrix(3, 2)( 139 | 1, 2, 140 | 3, 4, 141 | 5, 6) * 142 | Matrix(2, 4)( 143 | 1, 2, 3, 4, 144 | 5, 6, 7, 8) 145 | 146 | //then 147 | result.toString shouldBe """[11, 14, 17, 20] 148 | |[23, 30, 37, 44] 149 | |[35, 46, 57, 68]""".stripMargin 150 | } 151 | 152 | it should "be error tolerant" in { 153 | //when & then 154 | a [IllegalArgumentException] should be thrownBy { 155 | Matrix(1, 0)(1) 156 | } 157 | a [IllegalArgumentException] should be thrownBy { 158 | Matrix(1, 1)(1) + Matrix(1, 2)(1, 1) 159 | } 160 | a [IllegalArgumentException] should be thrownBy { 161 | Matrix(1, 1)(1) * Matrix(2, 1)(1, 1) 162 | } 163 | } 164 | 165 | "RichFile" should "extract the file path, name, and extension" in { 166 | //given 167 | val file = "file/home/cay/readme.txt" 168 | 169 | //when 170 | val RichFile(path, name, extension) = file 171 | 172 | //then 173 | path shouldBe "/home/cay" 174 | name shouldBe "readme" 175 | extension shouldBe "txt" 176 | } 177 | 178 | "RichFile2" should "extract the path sequence" in { 179 | //given 180 | val file = "file/home/cay/data/readme.txt" 181 | 182 | //when 183 | val RichFile2(home, user, data, name) = file 184 | 185 | //then 186 | home shouldBe "home" 187 | user shouldBe "cay" 188 | data shouldBe "data" 189 | name shouldBe "readme.txt" 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/test/scala/Chapter12Spec.scala: -------------------------------------------------------------------------------- 1 | import Chapter12._ 2 | import org.scalatest.{FlatSpec, Matchers} 3 | 4 | class Chapter12Spec extends FlatSpec with Matchers { 5 | 6 | "values" should "yield a collection of function inputs and outputs in a given range" in { 7 | //when 8 | val result: Seq[(Int, Int)] = values(x => x * x, -5, 5) 9 | 10 | //then 11 | result shouldBe Seq((-5, 25), (-4, 16), (-3, 9), (-2, 4), (-1, 1), (0, 0), 12 | (1, 1), (2, 4), (3, 9), (4, 16), (5, 25)) 13 | } 14 | 15 | "largestElement" should "return the largest element of an array with reduceLeft" in { 16 | //given 17 | val arr = Array(5, 6, 1, 0, -2, 3, -1) 18 | 19 | //when 20 | val result: Int = largestElement(arr) 21 | 22 | //then 23 | result shouldBe 6 24 | } 25 | 26 | "factorial" should "be implemented using to and reduceLeft" in { 27 | //when & then 28 | factorial(-1) shouldBe 1 29 | factorial(0) shouldBe 1 30 | factorial(1) shouldBe 1 31 | factorial(5) shouldBe 120 32 | } 33 | 34 | "factorial2" should "be implemented using to and foldLeft" in { 35 | //when & then 36 | factorial2(-1) shouldBe 1 37 | factorial2(0) shouldBe 1 38 | factorial2(1) shouldBe 1 39 | factorial2(5) shouldBe 120 40 | } 41 | 42 | "largest" should "return the largest value of a function within a given sequence of inputs" in { 43 | //when 44 | val result: Int = largest(x => 10 * x - x * x, 1 to 10) 45 | 46 | //then 47 | result shouldBe 25 48 | } 49 | 50 | "largestAt" should "return the input at which the output is largest" in { 51 | //when 52 | val result: Int = largestAt(x => 10 * x - x * x, 1 to 10) 53 | 54 | //then 55 | result shouldBe 5 56 | } 57 | 58 | "adjustToPair" should "return the function that operates on a pair" in { 59 | //when & then 60 | adjustToPair(_ * _)((6, 7)) shouldBe 42 61 | } 62 | 63 | "mapPairs" should "compute the sums of the elements in pairs" in { 64 | //given 65 | val pairs = (1 to 10) zip (11 to 20) 66 | 67 | //when 68 | val result: Seq[Int] = mapPairs(pairs, _ + _) 69 | 70 | result shouldBe Seq(12, 14, 16, 18, 20, 22, 24, 26, 28, 30) 71 | } 72 | 73 | "correspondsLen" should "check that strings have the lengths from integers array" in { 74 | //when & then 75 | correspondsLen(Array(""), Array(0)) shouldBe true 76 | correspondsLen(Array(""), Array(1)) shouldBe false 77 | correspondsLen(Array("a"), Array(1)) shouldBe true 78 | correspondsLen(Array("a"), Array(2)) shouldBe false 79 | correspondsLen(Array("a", "bb", "ccc"), Array(1, 2, 3)) shouldBe true 80 | correspondsLen(Array("a", "bb", "ccc"), Array(1, 2, 3, 4)) shouldBe false 81 | correspondsLen(Array("a", "bb", "ccc"), Array(1, 2)) shouldBe false 82 | } 83 | 84 | "corresponds2" should "be implemented without currying" in { 85 | corresponds2(Array("a"), Array(1), (a: String, b: Int) => a.length == b) shouldBe true 86 | } 87 | 88 | "unless" should "work just like if, but with an inverted condition" in { 89 | //when & then 90 | var a = 0 91 | unless(a == 1) { 92 | a = 1 93 | } 94 | 95 | a shouldBe 1 96 | 97 | var b = 0 98 | unless(b == 0) { 99 | b = 1 100 | } 101 | 102 | b shouldBe 0 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/test/scala/Chapter13Spec.scala: -------------------------------------------------------------------------------- 1 | import Chapter13._ 2 | import org.scalatest.{FlatSpec, Matchers} 3 | import scala.collection.mutable 4 | import scala.io.Source 5 | 6 | class Chapter13Spec extends FlatSpec with Matchers { 7 | 8 | "indexes" should "produce a mutable map of mutable set of indexes of all characters" in { 9 | //when 10 | val result: mutable.Map[Char, mutable.Set[Int]] = indexes("Mississippi") 11 | 12 | //then 13 | result('M') shouldBe mutable.Set(0) 14 | result('i') shouldBe mutable.Set(1, 4, 7, 10) 15 | result('s') shouldBe mutable.Set(2, 3, 5, 6) 16 | result('p') shouldBe mutable.Set(8, 9) 17 | } 18 | 19 | "indexes2" should "produce an immutable map of lists of indexes of all characters" in { 20 | //when 21 | val result: Map[Char, List[Int]] = indexes2("Mississippi") 22 | 23 | //then 24 | result('M') shouldBe List(0) 25 | result('i') shouldBe List(1, 4, 7, 10) 26 | result('s') shouldBe List(2, 3, 5, 6) 27 | result('p') shouldBe List(8, 9) 28 | } 29 | 30 | "removeAllZeroes" should "remove all zeroes from a linked list of integers" in { 31 | //when & then 32 | val list: LinkedList[Int] = newLinkedList(1, 2, 3) 33 | val result: LinkedList[Int] = removeAllZeroes(list) 34 | result shouldBe theSameInstanceAs(list) 35 | 36 | removeAllZeroes(newLinkedList(0, 1, 2, 3, 4)) shouldBe newLinkedList(1, 2, 3, 4) 37 | removeAllZeroes(newLinkedList(1, 2, 0, 3, 4)) shouldBe newLinkedList(1, 2, 3, 4) 38 | removeAllZeroes(newLinkedList(1, 2, 3, 4, 0)) shouldBe newLinkedList(1, 2, 3, 4) 39 | removeAllZeroes(newLinkedList(0, 1, 2, 0, 3, 4, 0)) shouldBe newLinkedList(1, 2, 3, 4) 40 | removeAllZeroes(newLinkedList(0, 0, 1, 2, 0, 0, 3, 0, 0, 4, 0, 0)) shouldBe 41 | newLinkedList(1, 2, 3, 4) 42 | } 43 | 44 | private def newLinkedList(elems: Int*): LinkedList[Int] = mutable.LinkedList(elems: _*) 45 | 46 | "mapToValues" should "return collection of corresponding integer values from map" in { 47 | //given 48 | val coll = Array("Tom", "Fred", "Harry") 49 | val map = Map("Tom" -> 3, "Dick" -> 4, "Harry" -> 5) 50 | 51 | //when 52 | val result = mapToValues(coll, map) 53 | 54 | //then 55 | result shouldBe Array(3, 5) 56 | } 57 | 58 | "collToString" should "works just like mkString, using reduceLeft" in { 59 | //when & then 60 | collToString(Nil) shouldBe "" 61 | collToString(Array(1)) shouldBe "1" 62 | collToString(Seq(1, 2, 3)) shouldBe "1, 2, 3" 63 | collToString(List("1", "2", "3")) shouldBe "1, 2, 3" 64 | } 65 | 66 | "reversList" should "revers the given list" in { 67 | //given 68 | val lst = List(1, 2, 3, 4, 5) 69 | 70 | //when 71 | val result: List[Int] = reversList(lst) 72 | 73 | //then 74 | result shouldBe List(5, 4, 3, 2, 1) 75 | } 76 | 77 | "multiply" should "apply Function.tupled to the multiplication function" in { 78 | //given 79 | val prices = List(1, 2, 3) 80 | val quantities = List(10, 20, 30) 81 | 82 | //when 83 | val result = multiply(prices, quantities) 84 | 85 | //then 86 | result shouldBe List(10, 40, 90) 87 | } 88 | 89 | "twoDimensionalArray" should "turn an array of Double values into a two-dimensional array" in { 90 | //given 91 | val arr = Array[Double](1, 2, 3, 4, 5, 6) 92 | val columns = 3 93 | 94 | //when 95 | val result: Array[Array[Double]] = twoDimensionalArray(arr, columns) 96 | 97 | //then 98 | result shouldBe Array[Array[Double]](Array(1, 2, 3), Array(4, 5, 6)) 99 | } 100 | 101 | "getLetterFrequencyMap" should "return letter frequency map for the given files" in { 102 | //given 103 | val files = Array.fill(5)("/myfile.txt") 104 | 105 | //when 106 | val result: Map[Char, Int] = getLetterFrequencyMap(files) 107 | 108 | //then 109 | result shouldBe getTestFrequencyMap(files.head, files.length) 110 | } 111 | 112 | "getLetterFrequencyMap2" should "return letter frequency map for the given string" in { 113 | //given 114 | val str = Source.fromInputStream(getClass.getResourceAsStream("/myfile.txt")).mkString 115 | 116 | //when 117 | val result: Map[Char, Int] = getLetterFrequencyMap2(str) 118 | 119 | //then 120 | result shouldBe getTestFrequencyMap("/myfile.txt", 1) 121 | } 122 | 123 | private def getTestFrequencyMap(file: String, multiplier: Int): Map[Char, Int] = { 124 | val result = new mutable.HashMap[Char, Int]() 125 | for (c <- Source.fromInputStream(getClass.getResourceAsStream(file))) { 126 | result(c) = result.getOrElse(c, 0) + 1 127 | } 128 | 129 | for ((k, v) <- result) { 130 | result(k) = v * multiplier 131 | } 132 | 133 | result.toMap 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/test/scala/Chapter14Spec.scala: -------------------------------------------------------------------------------- 1 | import Chapter14._ 2 | import org.scalatest.{FlatSpec, Matchers} 3 | 4 | class Chapter14Spec extends FlatSpec with Matchers { 5 | 6 | "swap" should "returns the pair with the components swapped" in { 7 | //given 8 | val pair = (1, 2) 9 | 10 | //when 11 | val result: (Int, Int) = swap(pair) 12 | 13 | //then 14 | result shouldBe (2, 1) 15 | } 16 | 17 | "swap2" should "swap the first two elements of an array" in { 18 | //when & then 19 | var arr: Array[Int] = Array() 20 | val r1: Array[Int] = swap2(arr) 21 | r1 shouldBe theSameInstanceAs(arr) 22 | r1 shouldBe Array() 23 | 24 | arr = Array(1) 25 | val r2 = swap2(arr) 26 | r2 shouldBe theSameInstanceAs(arr) 27 | r2 shouldBe Array(1) 28 | 29 | arr = Array(1, 2) 30 | val r3 = swap2(arr) 31 | r3 shouldBe theSameInstanceAs(arr) 32 | r3 shouldBe Array(2, 1) 33 | 34 | arr = Array(1, 2, 3, 4) 35 | val r4 = swap2(arr) 36 | r4 shouldBe theSameInstanceAs(arr) 37 | r4 shouldBe Array(2, 1, 3, 4) 38 | } 39 | 40 | "price" should "handle Multiple class items" in { 41 | //given 42 | val item = Bundle("Father's day special", 20.0, 43 | Article("Scala for the Impatient", 39.95), 44 | Bundle("Anchor Distilley Sampler", 10.0, 45 | Article("Old Potrero Straight Rye Whiskey", 79.95), 46 | Article("Junipero Gin", 32.95)), 47 | Multiple(10, Article("Blackwell Toaster", 29.95))) 48 | 49 | //when 50 | val result: Double = price(item) 51 | 52 | //then 53 | result.formatted("%.2f") shouldBe "422.35" 54 | } 55 | 56 | it should "be able to handle any items, such as bundles or multiples" in { 57 | //when & then 58 | price(Multiple(1, 59 | Multiple(2, 60 | Bundle("Black Friday special", 49.0, 61 | Multiple(3, Article("iPhone 5s", 549.99))))) 62 | ).formatted("%.2f") shouldBe "3201.94" 63 | } 64 | 65 | "leafSum" should "compute the sum of all elements in the leaves" in { 66 | //given 67 | val list: List[Any] = List(List(3, 8), 2, List(5)) 68 | 69 | //when 70 | val result: Double = leafSum(list) 71 | 72 | //then 73 | result shouldBe 18 74 | } 75 | 76 | "Task6.leafSum" should "compute the sum of all elements in the BinaryTree" in { 77 | import Task6._ 78 | 79 | //given 80 | val bt: Task6.BinaryTree = Node(Node(Leaf(3), Leaf(8)), Node(Leaf(2), Leaf(5))) 81 | 82 | //when 83 | val result: Int = leafSum(bt) 84 | 85 | //then 86 | result shouldBe 18 87 | } 88 | 89 | "Task7.leafSum" should "compute the sum of all elements in the multi-node tree" in { 90 | import Task7._ 91 | 92 | //given 93 | val bt: Task7.BinaryTree = Node(Node(Leaf(3), Leaf(8)), Leaf(2), Node(Leaf(5))) 94 | 95 | //when 96 | val result: Int = leafSum(bt) 97 | 98 | //then 99 | result shouldBe 18 100 | } 101 | 102 | "eval" should "compute the value of the operator-node tree" in { 103 | import Task8._ 104 | import Task8.Op._ 105 | 106 | //given 107 | val bt: Task8.BinaryTree = 108 | Node(Plus, Node(Product, Leaf(3), Leaf(8)), Leaf(2), Node(Minus, Leaf(5))) 109 | 110 | //when 111 | val result: Int = eval(bt) 112 | 113 | //then 114 | result shouldBe 21 115 | } 116 | 117 | "sumOfNonNoneValues" should "compute the sum of the non-None values" in { 118 | //given 119 | val list: List[Option[Int]] = List(None, Some(1), None, Some(2), None, Some(3)) 120 | 121 | //when 122 | val result: Int = sumOfNonNoneValues(list) 123 | 124 | //then 125 | result shouldBe 6 126 | } 127 | 128 | "compose" should "yield None if either function does" in { 129 | //given 130 | def f(x: Double) = if (x >= 0) Some(Math.sqrt(x)) else None 131 | def g(x: Double) = if (x != 1) Some(1 / (x - 1)) else None 132 | 133 | //when 134 | val h = compose(f, g) 135 | 136 | //then 137 | h(2) shouldBe Some(1) 138 | h(1) shouldBe None 139 | h(0) shouldBe None 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/test/scala/Chapter15Spec.scala: -------------------------------------------------------------------------------- 1 | import Chapter15._ 2 | import Chapter15Task4.scalaSum 3 | import Chapter15Task5.scalaFileToString 4 | import TestUtils.{ClassPath, runApp, runCmd} 5 | import java.io.IOException 6 | import org.scalatest.{FlatSpec, Matchers} 7 | 8 | class Chapter15Spec extends FlatSpec with Matchers { 9 | 10 | "Task1" should "has four test cases that use the org.junit.Test annotation" in { 11 | //when 12 | val (exit, out, err) = runApp("org.junit.runner.JUnitCore", "Chapter15$Task1") 13 | 14 | //then 15 | exit shouldBe 0 16 | err shouldBe "" 17 | out should include ("OK (4 tests)") 18 | } 19 | 20 | "scalaSum" should "call Scala sum method with variable arguments from Java" in { 21 | //when & then 22 | scalaSum(1, 2, 3) shouldBe 6 23 | sum(1, 2, 3, 4) shouldBe 10 24 | } 25 | 26 | "scalaFileToString" should "call Scala fileToString method" in { 27 | //when & then 28 | scalaFileToString("/myfile.txt") shouldBe """ 29 | |Simple text file with example words. 30 | |We will parse the file and count the words. 31 | |""".stripMargin 32 | a [RuntimeException] should be thrownBy { 33 | scalaFileToString("nonExisting.file") 34 | } 35 | a [IOException] should be thrownBy { 36 | fileToString("nonExisting.file") 37 | } 38 | } 39 | 40 | "Work" should "emulate work with two threads and one shared volatile boolean flag" in { 41 | //when 42 | val (exit, out, err) = runApp("Chapter15WorkApp") 43 | 44 | //then 45 | exit shouldBe 0 46 | err shouldBe "" 47 | out should include ("Work.done flag was set to true") 48 | out should include ("Work is done, exiting") 49 | } 50 | 51 | "TailRecursion" should "provide non-overridden, tail-recursive method" in { 52 | //given 53 | val tr = new TailRecursion 54 | 55 | //when 56 | val result: Int = tr.sum(List(1, 2, 3, 4)) 57 | 58 | //then 59 | result shouldBe 10 60 | } 61 | 62 | "allDifferent" should "be generated for all primitive types" in { 63 | //when 64 | val (exit, out, err) = runCmd("javap", "-classpath", ClassPath, "Chapter15$") 65 | 66 | //then 67 | exit shouldBe 0 68 | err shouldBe "" 69 | out should include ("public boolean allDifferent(T, T, T);") 70 | out should include ("public boolean allDifferent$mZc$sp(boolean, boolean, boolean);") 71 | out should include ("public boolean allDifferent$mBc$sp(byte, byte, byte);") 72 | out should include ("public boolean allDifferent$mCc$sp(char, char, char);") 73 | out should include ("public boolean allDifferent$mDc$sp(double, double, double);") 74 | out should include ("public boolean allDifferent$mFc$sp(float, float, float);") 75 | out should include ("public boolean allDifferent$mIc$sp(int, int, int);") 76 | out should include ("public boolean allDifferent$mJc$sp(long, long, long);") 77 | out should include ("public boolean allDifferent$mSc$sp(short, short, short);") 78 | out should include ("public boolean allDifferent$mVc$sp(scala.runtime.BoxedUnit, scala.runtime.BoxedUnit, scala.runtime.BoxedUnit);") 79 | } 80 | 81 | "factorial" should "throw an exception when assertions are enabled" in { 82 | //when & then 83 | a [AssertionError] should be thrownBy { 84 | factorial(-1) 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/test/scala/Chapter16Spec.scala: -------------------------------------------------------------------------------- 1 | import Chapter16._ 2 | import TestUtils.runApp 3 | import java.io.File 4 | import org.scalatest.{FlatSpec, Matchers} 5 | import scala.io.Source 6 | import scala.xml.{Elem, Node, Text, XML} 7 | 8 | class Chapter16Spec extends FlatSpec with Matchers { 9 | 10 | "task1" should "check results" in { 11 | //when & then 12 | (0).toString() shouldBe "" 13 | (0)(0).toString() shouldBe "" 14 | (0)(0)(0).toString() shouldBe "" 15 | (0)(0)(0)(0).toString() shouldBe "" 16 | } 17 | 18 | "task2" should "escape special characters" in { 19 | //when & then 20 | val result =
      21 |
    • Opening bracket: [
    • 22 |
    • Closing bracket: ]
    • 23 |
    • Opening brace: {{
    • 24 |
    • Closing brace: }}
    • 25 |
    26 | 27 | result.toString() shouldBe """
      28 | |
    • Opening bracket: [
    • 29 | |
    • Closing bracket: ]
    • 30 | |
    • Opening brace: {
    • 31 | |
    • Closing brace: }
    • 32 | |
    """.stripMargin 33 | } 34 | 35 | "task3" should "check expressions" in { 36 | //when & then 37 | val res1 =
  • Fred
  • match { case
  • {Text(t)}
  • => t } 38 | res1.toString shouldBe "Fred" 39 | 40 | a [scala.MatchError] should be thrownBy { 41 |
  • {"Fred"}
  • match { case
  • {Text(t)}
  • => t } 42 | } 43 | 44 | val res2 =
  • {Text("Fred")}
  • match { case
  • {Text(t)}
  • => t } 45 | res2.toString shouldBe "Fred" 46 | } 47 | 48 | "printImgWithoutAlt" should "print all img elements without alt attribute" in { 49 | //given 50 | val file = "/Chapter16Task04.html" 51 | 52 | //when 53 | val (exit, out, err) = runApp("Chapter16PrintImgWithoutAltApp", file) 54 | 55 | //then 56 | exit shouldBe 0 57 | err shouldBe "" 58 | out shouldBe """ 59 | | 60 | |""".stripMargin 61 | } 62 | 63 | "printAllImg" should "print all img src attributes" in { 64 | //given 65 | val file = "/Chapter16Task04.html" 66 | 67 | //when 68 | val (exit, out, err) = runApp("Chapter16PrintAllImgApp", file) 69 | 70 | //then 71 | exit shouldBe 0 72 | err shouldBe "" 73 | out shouldBe """http://test.com/img1.png 74 | |http://test.com/img2.png 75 | |http://test.com/img3.png 76 | |""".stripMargin 77 | } 78 | 79 | "printAllHyperlinks" should "print a table of all hyperlinks together with their URLs" in { 80 | //given 81 | val file = "/Chapter16Task04.html" 82 | 83 | //when 84 | val (exit, out, err) = runApp("Chapter16PrintAllHyperlinksApp", file) 85 | 86 | //then 87 | exit shouldBe 0 88 | err shouldBe "" 89 | out shouldBe """+---------------------------------------+----------------------+ 90 | || | http://test.com/ref1 | 91 | || Ref 2 | http://test.com/ref2 | 92 | || Ref 3 | http://test.com/ref3 | 93 | |+---------------------------------------+----------------------+ 94 | |""".stripMargin 95 | } 96 | 97 | "mapToXml" should "produce XML from the given map" in { 98 | //given 99 | val map = Map("A" -> "1", "B" -> "2") 100 | 101 | //when 102 | val result: Elem = mapToXml(map) 103 | 104 | //then 105 | result shouldBe
    A
    1
    B
    2
    106 | } 107 | 108 | "xmlToMap" should "produce map from the given Xml" in { 109 | //given 110 | val xml =
    A
    1
    B
    2
    111 | 112 | //when 113 | val result: Map[String, String] = xmlToMap(xml) 114 | 115 | //then 116 | result shouldBe Map("A" -> "1", "B" -> "2") 117 | } 118 | 119 | "transformXhtml" should "transform an XHTML document" in { 120 | //given 121 | val root = XML.load(getClass.getResourceAsStream("/Chapter16Task04.html")) 122 | 123 | //when 124 | val result: Node = transformXhtml(root) 125 | 126 | //then 127 | result.toString() shouldBe """ 128 | | 129 | |

    130 | | 131 | | TODO 132 | | 133 | |

    134 | | Ref 2 135 | |
    136 | | TODO 137 | | 138 | | Ref 3 139 | |
    140 | | Some text 141 | | 142 | |""".stripMargin 143 | } 144 | 145 | "transformXhtmlFile" should "transform XHTML file and saves the result to another file" in { 146 | //given 147 | val inFile = "/Chapter16Task10.xhtml" 148 | val tmpFile = File.createTempFile("Chapter16Task10Out", "xhtml") 149 | tmpFile.deleteOnExit() 150 | 151 | //when 152 | transformXhtmlFile(inFile, tmpFile.getAbsolutePath) 153 | 154 | //then 155 | Source.fromFile(tmpFile).mkString shouldBe 156 | """ 157 | | 158 | | 159 | | 160 | | Sample XHTML document 161 | | 162 | | 163 | | 164 | |
    165 | | [CDATA[should be preserved]] 166 | |
    167 | |

    168 | | 169 | | TODO 170 | | 171 | |

    172 | |

    173 | | Ref 2 174 | |

    175 | |
    176 | | TODO 177 | | 178 | | Ref 3 179 | |
    180 | |

    181 | | Some text 182 | |

    183 | | 184 | |""".stripMargin 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/test/scala/Chapter17Spec.scala: -------------------------------------------------------------------------------- 1 | import org.scalatest.{FlatSpec, Matchers} 2 | 3 | class Chapter17Spec extends FlatSpec with Matchers { 4 | 5 | "Pair.swap" should "return a new pair with the components swapped" in { 6 | import task1701._ 7 | 8 | //given 9 | val pair: Pair[Int, String] = new Pair(1, "2") 10 | 11 | //when 12 | val result: Pair[String, Int] = pair.swap() 13 | 14 | //then 15 | result.first shouldBe "2" 16 | result.second shouldBe 1 17 | } 18 | 19 | "MutablePair.swap" should "swap the components of the mutable pair" in { 20 | import task1702._ 21 | 22 | //given 23 | val pair: Pair[Int] = new Pair(1, 2) 24 | 25 | //when 26 | pair.swap() 27 | 28 | //then 29 | pair.first shouldBe 2 30 | pair.second shouldBe 1 31 | } 32 | 33 | "Task03.swap" should "take a pair and return a new pair with the components swapped" in { 34 | import task1701._ 35 | 36 | //given 37 | val pair: Pair[Int, String] = new Pair(1, "2") 38 | 39 | //when 40 | val result: Pair[String, Int] = Chapter17Task03.swap(pair) 41 | 42 | //then 43 | result.first shouldBe "2" 44 | result.second shouldBe 1 45 | } 46 | 47 | "Task04.replaceFirst" should "take a pair and return a new pair with the components swapped" in { 48 | import task1704._ 49 | 50 | //given 51 | val pair: Pair[Person] = new Pair[Person](new Person("First"), new Person("Second")) 52 | 53 | //when 54 | val result: Pair[Person] = pair.replaceFirst(new Student("newFirst")) 55 | 56 | //then 57 | result.first.name shouldBe "newFirst" 58 | result.second.name shouldBe "Second" 59 | } 60 | 61 | "Task06.middle" should "return the middle element from any Iterable[T]" in { 62 | //when & then 63 | Chapter17Task06.middle("World") shouldBe Some('r') 64 | Chapter17Task06.middle("Worl") shouldBe None 65 | Chapter17Task06.middle("Wor") shouldBe Some('o') 66 | Chapter17Task06.middle("Wo") shouldBe None 67 | Chapter17Task06.middle("W") shouldBe Some('W') 68 | Chapter17Task06.middle("") shouldBe None 69 | Chapter17Task06.middle(List(1, 2, 3)) shouldBe Some(2) 70 | Chapter17Task06.middle(Some(1)) shouldBe Some(1) 71 | Chapter17Task06.middle(None) shouldBe None 72 | } 73 | 74 | "Task09.check" should "call replaceFirst on Pair[Any] that is actually NastyDoublePair" in { 75 | import Chapter17Task09._ 76 | 77 | //given 78 | val pair: Pair[Any] = new NastyDoublePair(1.0, 2.0) 79 | 80 | //when 81 | val result: Pair[Any] = check(pair) 82 | 83 | //then 84 | result.first shouldBe 1.0 85 | result.second shouldBe 2.0 86 | } 87 | 88 | "Task10.swap" should "be called if the type parameters are the same" in { 89 | import task1710._ 90 | 91 | //given 92 | val pair = new Pair(1.0, 2.0) 93 | 94 | //when 95 | pair.swap 96 | //new Pair("1.0", 2.0).swap // Error 97 | 98 | //then 99 | pair.first shouldBe 2.0 100 | pair.second shouldBe 1.0 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/test/scala/Chapter18Spec.scala: -------------------------------------------------------------------------------- 1 | import Chapter18._ 2 | import TestUtils.withOutput 3 | import org.scalatest.{FlatSpec, Matchers} 4 | import scala.language.reflectiveCalls 5 | 6 | class Chapter18Spec extends FlatSpec with Matchers { 7 | 8 | "Bug" should "has move, turn, and show methods" in { 9 | //given 10 | val bugsy = new Bug() 11 | 12 | //when 13 | val out = withOutput { 14 | bugsy.move(4).show().move(6).show().turn().move(5).show() 15 | } 16 | 17 | //then 18 | out shouldBe " 4 10 5" 19 | } 20 | 21 | it should "provide a fluent interface" in { 22 | //given 23 | val bugsy = new Bug() with FluentBug 24 | 25 | //when 26 | val out = withOutput { 27 | bugsy move 4 and show and then move 6 and show turn around move 5 and show 28 | } 29 | 30 | //then 31 | out shouldBe " 4 10 5" 32 | } 33 | 34 | "Book" should "provide a fluent interface" in { 35 | //given 36 | val book = new Book() 37 | 38 | //when 39 | book set Title to "Scala for the Impatient" set Author to "Cay Horstmann" 40 | 41 | //then 42 | book.getTitle shouldBe "Scala for the Impatient" 43 | book.getAuthor shouldBe "Cay Horstmann" 44 | } 45 | 46 | "Member.equals" should "return true if two members are in the same network" in { 47 | //given 48 | val network1 = new Network 49 | val member11 = new network1.Member 50 | val member12 = new network1.Member 51 | val network2 = new Network 52 | val member21 = new network2.Member 53 | 54 | //when & then 55 | member11.equals(member12) shouldBe true 56 | member11.equals(member21) shouldBe false 57 | member12.equals(member21) shouldBe false 58 | } 59 | 60 | "process" should "should accept members from any network" in { 61 | //given 62 | val network1 = new Network 63 | val member11 = new network1.Member 64 | val member12 = new network1.Member 65 | val network2 = new Network 66 | val member21 = new network2.Member 67 | 68 | //when & then 69 | process(member11, member12) shouldBe (member11, member12) 70 | process(member11, member21) shouldBe (member11, member21) 71 | process(member12, member21) shouldBe (member12, member21) 72 | } 73 | 74 | "processAny" should "should accept members from any network" in { 75 | //given 76 | val network1 = new Network 77 | val member11 = new network1.Member 78 | val member12 = new network1.Member 79 | val network2 = new Network 80 | val member21 = new network2.Member 81 | 82 | //when & then 83 | processAny(member11, member12) shouldBe (member11, member12) 84 | processAny(member11, member21) shouldBe (member11, member21) 85 | processAny(member12, member21) shouldBe (member12, member21) 86 | } 87 | 88 | "processSame" should "should accept members from the same network" in { 89 | //given 90 | val network1 = new Network 91 | val member11 = new network1.Member 92 | val member12 = new network1.Member 93 | val network2 = new Network 94 | val member21 = new network2.Member 95 | 96 | //when & then 97 | processSame(member11, member12) shouldBe (member11, member12) 98 | //processSame(member11, member21) shouldBe (member11, member21) // Error 99 | //processSame(member12, member21) shouldBe (member12, member21) // Error 100 | } 101 | 102 | "findClosestValueIndex" should "return either index of exact value or closest value" in { 103 | //when & then 104 | findClosestValueIndex(Array(), 0) shouldBe Right(-1) 105 | findClosestValueIndex(Array(1, 2, 4), 0) shouldBe Right(0) 106 | findClosestValueIndex(Array(1, 2, 4), 1) shouldBe Left(0) 107 | findClosestValueIndex(Array(1, 2, 4), 2) shouldBe Left(1) 108 | findClosestValueIndex(Array(1, 2, 4), 3) shouldBe Right(2) 109 | findClosestValueIndex(Array(1, 2, 4), 4) shouldBe Left(2) 110 | findClosestValueIndex(Array(1, 2, 4), 5) shouldBe Right(2) 111 | findClosestValueIndex(Array(1, 2, 4), 6) shouldBe Right(2) 112 | } 113 | 114 | "processAndClose" should "call the function and invoke the close method upon completion" in { 115 | //given 116 | val obj = new Object { 117 | var processed = false 118 | var closed = false 119 | 120 | def process(): Unit = { 121 | processed = true 122 | } 123 | 124 | def close(): Unit = { 125 | closed = true 126 | } 127 | } 128 | 129 | //when 130 | processAndClose(obj)(_.process()) 131 | 132 | //then 133 | obj.processed shouldBe true 134 | obj.closed shouldBe true 135 | } 136 | 137 | it should "call the function and invoke the close method when exception" in { 138 | //given 139 | val obj = new Object { 140 | var closed = false 141 | 142 | def close(): Unit = { 143 | closed = true 144 | } 145 | } 146 | 147 | //when 148 | a [Exception] should be thrownBy { 149 | processAndClose(obj)(_ => throw new Exception()) 150 | } 151 | 152 | //then 153 | obj.closed shouldBe true 154 | } 155 | 156 | "printValues" should "print all values of apply function with inputs from the given range" in { 157 | //when & then 158 | withOutput(printValues((x: Int) => x * x, 3, 6)) shouldBe " 9 16 25 36" 159 | withOutput(printValues(Array(1, 1, 2, 3, 5, 8, 13, 21, 34, 55), 3, 6)) shouldBe " 3 5 8 13" 160 | } 161 | 162 | "Dim" should "not allow meters and seconds to be added" in { 163 | //given 164 | val seconds1 = new Seconds(1.0) 165 | val seconds2 = new Seconds(2.0) 166 | val meters1 = new Meters(3.0) 167 | val meters2 = new Meters(4.0) 168 | 169 | //when & then 170 | (seconds1 + seconds2).toString shouldBe "3.0 s" 171 | (meters1 + meters2).toString shouldBe "7.0 m" 172 | //(seconds1 + meters2).toString shouldBe "" // Error 173 | } 174 | 175 | "selfType" should "demonstrate changes in the initialization and override orders" in { 176 | import task1810._ 177 | 178 | //given 179 | val obj = new A("obj") with Named 180 | 181 | //when & then 182 | obj.toString shouldBe "from Named: Named: null" 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/test/scala/Chapter19Spec.scala: -------------------------------------------------------------------------------- 1 | import Chapter19._ 2 | import TestUtils.withOutput 3 | import java.util.{Calendar, Date} 4 | import org.scalatest.{FlatSpec, Matchers} 5 | 6 | class Chapter19Spec extends FlatSpec with Matchers { 7 | 8 | "ExprEvaluator" should "handle / and % operations" in { 9 | //given 10 | val e = new ExprEvaluator 11 | 12 | //when & then 13 | e.eval("3 - 4 * 5") shouldBe -17 14 | e.eval("3 - 8 / 4 * 5 + 3 % 4") shouldBe -4 15 | } 16 | 17 | "ExprEvaluator2" should "handle ^ operator" in { 18 | //given 19 | val e = new ExprEvaluator2 20 | 21 | //when & then 22 | e.eval("3 - 4 * 5") shouldBe -17 23 | e.eval("3 - 8 / 4 * 5 + 3 % 4") shouldBe -4 24 | e.eval("4 ^ 2 ^ 3") shouldBe 65536 25 | e.eval("1 + 2 * 4 ^ 2 ^ 3 * 2 - 1") shouldBe 262144 26 | } 27 | 28 | "IntListParser" should "parse a list of integers into a List[Int]" in { 29 | //given 30 | val p = new IntListParser 31 | 32 | //when & then 33 | p.parse("()") shouldBe List() 34 | p.parse("(1)") shouldBe List(1) 35 | p.parse("(1, 23, -79)") shouldBe List(1, 23, -79) 36 | } 37 | 38 | "DateTimeParser" should "date and time expressions in ISO 8601" in { 39 | //given 40 | val p = new DateTimeParser 41 | 42 | //when & then 43 | p.parse("2005-08-09T18:31:42.123") shouldBe date(2005, 8, 9, 18, 31, 42, 123) 44 | p.parse("20050809T183142123") shouldBe date(2005, 8, 9, 18, 31, 42, 123) 45 | p.parse("2005-08-09T18:31:42") shouldBe date(2005, 8, 9, 18, 31, 42, 0) 46 | p.parse("20050809T183142") shouldBe date(2005, 8, 9, 18, 31, 42, 0) 47 | p.parse("2005-08-09") shouldBe date(2005, 8, 9, 0, 0, 0, 0) 48 | p.parse("20050809") shouldBe date(2005, 8, 9, 0, 0, 0, 0) 49 | } 50 | 51 | "IdentXMLParser" should "parse a subset of XML" in { 52 | //given 53 | val p = new IdentXMLParser 54 | 55 | //when & then 56 | a [IllegalArgumentException] should be thrownBy { 57 | p.parse("") 58 | } 59 | a [IllegalArgumentException] should be thrownBy { 60 | p.parse("") 61 | } 62 | a [IllegalArgumentException] should be thrownBy { 63 | p.parse("") 64 | } 65 | a [IllegalArgumentException] should be thrownBy { 66 | p.parse("") 67 | } 68 | a [IllegalArgumentException] should be thrownBy { 69 | p.parse("") 70 | } 71 | a [IllegalArgumentException] should be thrownBy { 72 | p.parse("") 73 | } 74 | a [IllegalArgumentException] should be thrownBy { 75 | p.parse("") 76 | } 77 | 78 | p.parse(""" 79 | | text ]]> text ]]> text 81 | | 82 | |""".stripMargin) shouldBe 83 | 84 | p.parse(""" 85 | | text 86 | | text text 87 | | 88 | |""".stripMargin) shouldBe 89 | 90 | p.parse( """ a 91 | | b c 92 | | d 93 | |""".stripMargin) shouldBe 94 | 95 | } 96 | 97 | "ExprParser" should "produce correct expression tree" in { 98 | //given 99 | val p = new ExprParser 100 | 101 | //when & then 102 | p.parse("3-4-5") shouldBe Operator("-", Operator("-", Number(3), Number(4)), Number(5)) 103 | p.parse("3-4+5") shouldBe Operator("+", Operator("-", Number(3), Number(4)), Number(5)) 104 | p.parse("3-4*5") shouldBe Operator("-", Number(3), Operator("*", Number(4), Number(5))) 105 | } 106 | 107 | "FoldExprEvaluator" should "implement expression computation as a fold" in { 108 | //given 109 | val p = new FoldExprEvaluator 110 | 111 | //when & then 112 | p.parse("3-4+5") shouldBe 4 113 | p.parse("3+4-5") shouldBe 2 114 | p.parse("3-4*5") shouldBe -17 115 | } 116 | 117 | "Calculator" should "use variables and assignment when calculating expressions" in { 118 | //given 119 | val c = new Calculator 120 | 121 | //when & then 122 | c.parseAndEval("") shouldBe 0 123 | c.parseAndEval("3-4+5+a") shouldBe 4 124 | c.parseAndEval("""a=6 125 | |3-4+5""".stripMargin) shouldBe 4 126 | c.parseAndEval("""a=6+1 127 | |3-4+5+a""".stripMargin) shouldBe 11 128 | c.parseAndEval("""a=b 129 | |3-4+5+a""".stripMargin) shouldBe 4 130 | c.parseAndEval("""a=b-6 131 | |3-4+5+a""".stripMargin) shouldBe -2 132 | withOutput { 133 | c.parseAndEval("""a=(3+3) 134 | |out=a+1""".stripMargin) 135 | } shouldBe "7" 136 | } 137 | 138 | "Program" should "use variable assignments, Boolean expressions, if/else, while statements" in { 139 | //given 140 | val p = new Program 141 | 142 | //when & then 143 | p.parseAndEval("""a=b-6 144 | |3-4+5+a 145 | |""".stripMargin) shouldBe -2 146 | p.parseAndEval(""" 147 | |a=b-6 148 | | 149 | |if (1 <= 2) { 150 | | 3-4+5+a 151 | |} 152 | |else { 153 | | 5 154 | |} 155 | |""".stripMargin) shouldBe -2 156 | p.parseAndEval("""a=b-6 157 | |if (1 > 2) 158 | |{ 159 | | //3-4+5+a 160 | |} 161 | |else 162 | |{ 163 | | 5 164 | |} 165 | |""".stripMargin) shouldBe 5 166 | p.parseAndEval("""a=b-6 167 | |if (1 > 2) { 168 | | 3-4+5+a 169 | |} else { 170 | | a = 0 171 | | while (a < 10) { 172 | | a = a + 1 173 | | } 174 | | a 175 | |} 176 | |""".stripMargin) shouldBe 10 177 | withOutput { 178 | p.parseAndEval("""a=(3+3) 179 | |if (a < 10) { 180 | | out=a+1 181 | |}""".stripMargin) 182 | } shouldBe "7" 183 | } 184 | 185 | "FuncProgram" should "add function definitions to program evaluator" in { 186 | //given 187 | val f = new FuncProgram 188 | 189 | //when & then 190 | the [IllegalArgumentException] thrownBy { 191 | f.parseAndEval("""a() 192 | |""".stripMargin) 193 | } should have message """function definition not found at 1.1 194 | |a() 195 | | 196 | |^""".stripMargin 197 | the [IllegalArgumentException] thrownBy { 198 | f.parseAndEval("""def a() { 199 | |} 200 | |a(1) 201 | |""".stripMargin) 202 | } should have message """function call with wrong arguments number at 3.1 203 | |a(1) 204 | | 205 | |^""".stripMargin 206 | the [IllegalArgumentException] thrownBy { 207 | f.parseAndEval("""def a(b) { 208 | |} 209 | |a() 210 | |""".stripMargin) 211 | } should have message """function call with wrong arguments number at 3.1 212 | |a() 213 | | 214 | |^""".stripMargin 215 | f.parseAndEval(""" 216 | |def a() { 217 | | b-6 218 | |} 219 | | 220 | |a = 3-4 221 | |if (1 <= 2) { 222 | | a+5+a() 223 | |} 224 | |else { 225 | | 5 226 | |} 227 | |""".stripMargin) shouldBe -2 228 | f.parseAndEval(""" 229 | |def a(b) { 230 | | b = b-6 231 | | if (1 <= 2) { 232 | | 3-4+5+b 233 | | } 234 | |} 235 | |a(1) 236 | |a(2) 237 | |a(3) 238 | |""".stripMargin) shouldBe 1 239 | withOutput { 240 | f.parseAndEval("""def a(a, c) { 241 | | out=a+c+1 242 | |} 243 | |a = 2 244 | |a(3+3, a) 245 | |""".stripMargin) 246 | } shouldBe "9" 247 | } 248 | 249 | private def date(y: Int, m: Int, d: Int, hh: Int, mm: Int, ss: Int, sss: Int): Date = { 250 | val cal = Calendar.getInstance() 251 | cal.set(y, m - 1, d, hh, mm, ss) 252 | cal.set(Calendar.MILLISECOND, sss) 253 | cal.getTime 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/test/scala/Chapter20Spec.scala: -------------------------------------------------------------------------------- 1 | import Chapter20._ 2 | import java.awt.Color 3 | import javax.imageio.ImageIO 4 | import org.scalatest.{FlatSpec, Matchers} 5 | 6 | class Chapter20Spec extends FlatSpec with Matchers { 7 | 8 | "RandCalc" should "compute the average of random numbers" in { 9 | //given 10 | val n = 1000000 11 | 12 | //when 13 | val average: Double = RandCalc.calcAverageFor(n, useActors = true) 14 | 15 | //then 16 | average should be > 0.0 17 | } 18 | 19 | "ImageProgram" should "invert colors of the large image" in { 20 | //given 21 | val tmpFile = TestUtils.resourceToTmpFile("/scaladays.png") 22 | val sourceImage = ImageIO.read(tmpFile) 23 | 24 | //when 25 | ImageProgram.invert(tmpFile, tmpFile) 26 | 27 | //then 28 | val invertedImage = ImageIO.read(tmpFile) 29 | sourceImage.getWidth shouldBe invertedImage.getWidth 30 | sourceImage.getHeight shouldBe invertedImage.getHeight 31 | for (x <- 0 until sourceImage.getWidth) { 32 | for (y <- 0 until sourceImage.getHeight) { 33 | var col = new Color(sourceImage.getRGB(x, y), true) 34 | col = new Color(255 - col.getRed, 255 - col.getGreen, 255 - col.getBlue) 35 | 36 | invertedImage.getRGB(x, y) shouldBe col.getRGB 37 | } 38 | } 39 | } 40 | 41 | "WordsCountProgram" should "count matched words in all files and subdirectories" in { 42 | //given 43 | val dirPath = "src/main/" 44 | val fileExtensions = List("txt", "html", "xhtml") 45 | 46 | //when 47 | val result: Int = WordsCountProgram.calcMatchedWords(dirPath, fileExtensions: _*) 48 | 49 | //then 50 | result shouldBe 3 51 | } 52 | 53 | "WordsPrintProgram" should "print matched words in all files and subdirectories" in { 54 | //given 55 | val dirPath = "src/main/" 56 | val fileExtensions = List("txt", "html", "xhtml") 57 | 58 | //when 59 | val result: String = WordsPrintProgram.printMatchedWords(dirPath, fileExtensions: _*) 60 | 61 | //then 62 | result shouldBe """text 63 | |text 64 | |text 65 | |""".stripMargin 66 | } 67 | 68 | "WordsPrintFilesProgram" should "print matched words in all files and subdirectories" in { 69 | //given 70 | val dirPath = "src/main/" 71 | val fileExtensions = List("txt", "html", "xhtml") 72 | 73 | //when 74 | val result: String = WordsPrintFilesProgram.printMatchedWordsWithFiles(dirPath, 75 | fileExtensions: _*) 76 | 77 | //then 78 | result shouldBe """found "text" in 79 | |src/main/resources/Chapter16Task04.html 80 | |src/main/resources/Chapter16Task10.xhtml 81 | |src/main/resources/myfile.txt 82 | | 83 | |""".stripMargin 84 | } 85 | 86 | "ThreadActorsProgram" should "print threads count of different type of actors" in { 87 | //when 88 | val whileReceiveActorsCount = ThreadActorsProgram.calcActorThreads(whileReceiveActors = true) 89 | println(s"whileReceiveActorsCount: $whileReceiveActorsCount") 90 | 91 | val loopReactActorsCount = ThreadActorsProgram.calcActorThreads(whileReceiveActors = false) 92 | println(s"loopReactActorsCount: $loopReactActorsCount") 93 | 94 | //then 95 | whileReceiveActorsCount should be > loopReactActorsCount 96 | } 97 | 98 | "WordsSupervisorProgram" should "supervise file workers and log any Exception(s)" in { 99 | //given 100 | val dirPath = "src/main/" 101 | val fileExtensions = List("txt", "html", "xhtml") 102 | 103 | //when 104 | var result: Int = 0 105 | val error = TestUtils.withOutput { 106 | result = WordsSupervisorProgram.calcMatchedWords(dirPath, fileExtensions: _*) 107 | } 108 | 109 | //then 110 | result shouldBe 2 111 | error shouldBe "java.lang.IllegalArgumentException: requirement failed: " + 112 | "Given path should represent file: src/main/resources/myfile.txt-not-exist\n\n" 113 | } 114 | 115 | "DeadlockProgram" should "deadlock on synchronous messages" in { 116 | //when 117 | val (millis, result: Int) = TestUtils.withTiming { 118 | DeadlockProgram.run() 119 | } 120 | 121 | //then 122 | result shouldBe -1 123 | millis should be > 3000L 124 | } 125 | 126 | "SharedCounterProgram" should "update a shared counter" in { 127 | //given 128 | val dirPath = "src/main/" 129 | val fileExtensions = List("txt", "html", "xhtml") 130 | 131 | //when 132 | SharedCounterProgram.counter = 0 133 | val result: Int = SharedCounterProgram.calcMatchedWords(dirPath, fileExtensions: _*) 134 | 135 | //then 136 | result shouldBe 3 137 | SharedCounterProgram.counter should not be result 138 | } 139 | 140 | "ChannelCalc" should "use channels to compute the average of random numbers" in { 141 | //given 142 | val n = 1000000 143 | 144 | //when 145 | val average: Double = ChannelCalc.calcAverageFor(n) 146 | 147 | //then 148 | average should be > 0.0 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/test/scala/Chapter21Spec.scala: -------------------------------------------------------------------------------- 1 | import Chapter11.Fraction 2 | import TestUtils.withOutputAndResult 3 | import java.awt.Point 4 | import java.io.StringReader 5 | import org.scalatest.{FlatSpec, Matchers} 6 | import scala.language.postfixOps 7 | 8 | class Chapter21Spec extends FlatSpec with Matchers { 9 | 10 | "PercentAdder" should "define an operator `+%` and use implicit conversion" in { 11 | import Chapter21._ 12 | 13 | 120 +% 10 shouldBe 132 14 | } 15 | 16 | "Int2Factorial" should "define an operator `!` and use implicit conversion" in { 17 | import Chapter21._ 18 | 19 | (5!) shouldBe 120 20 | } 21 | 22 | "FluentReader" should "provide fluent APIs for reading data from the console" in { 23 | //given 24 | import Chapter21._ 25 | 26 | val input = """Viktor 27 | |35 28 | |80 29 | |""".stripMargin 30 | 31 | //when 32 | val (out: String, result: FluentReader) = withOutputAndResult { 33 | Console.withIn(new StringReader(input)) { 34 | Read in aString askingFor "Your name" and 35 | anInt askingFor "Your age" and 36 | aDouble askingFor "Your weight" 37 | } 38 | } 39 | 40 | //then 41 | result.getData.mkString("\n") shouldBe """(Your name,Viktor) 42 | |(Your age,35) 43 | |(Your weight,80.0)""".stripMargin 44 | 45 | out shouldBe """Your name: Your age: Your weight: """.stripMargin 46 | } 47 | 48 | "smaller" should "work with Fraction instances" in { 49 | import Chapter21._ 50 | 51 | //when & then 52 | smaller(Fraction(1, 7), Fraction(2, 9)) shouldBe Fraction(1, 7) 53 | smaller(Fraction(1, 7), Fraction(1, 7)) shouldBe Fraction(1, 7) 54 | smaller(Fraction(0, 7), Fraction(0, 9)) shouldBe Fraction(0, 9) 55 | } 56 | 57 | "LexicographicPointOrdering" should "compare Point objects by lexicographic comparison" in { 58 | //given 59 | import Chapter21.LexicographicPointOrdering 60 | 61 | //when & then 62 | new Point(1, 1).compareTo(new Point(1, 1)) shouldBe 0 63 | new Point(1, 1) should be < new Point(2, 1) 64 | new Point(1, 1) should be < new Point(1, 2) 65 | new Point(1, -3) should be < new Point(1, 2) 66 | } 67 | 68 | "DistancePointOrdering" should "compare Point objects by distance to the origin" in { 69 | //given 70 | import Chapter21.DistancePointOrdering 71 | 72 | //when & then 73 | new Point(1, 1).compareTo(new Point(1, 1)) shouldBe 0 74 | new Point(-2, 1).compareTo(new Point(2, 1)) shouldBe 0 75 | new Point(1, 1) should be < new Point(1, 2) 76 | new Point(1, -3) should be > new Point(1, 2) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/scala/TestUtils.scala: -------------------------------------------------------------------------------- 1 | import java.io.{ByteArrayOutputStream, File, FileOutputStream, PrintWriter} 2 | import scala.collection.JavaConversions.seqAsJavaList 3 | import scala.collection.mutable 4 | import scala.io.Source 5 | 6 | /** 7 | * Contains utility functions and constants used in tests. 8 | */ 9 | object TestUtils { 10 | 11 | val ClassPath = "./target/scala-2.11/classes" 12 | 13 | // should be the same as in build script 14 | private val ScalaVersion = "2.11.7" 15 | 16 | def runApp(mainObj: String, args: String*): (Int, String, String) = { 17 | runAppWithInput("", mainObj, args: _*) 18 | } 19 | 20 | def runAppWithInput(input: String, mainObj: String, args: String*): (Int, String, String) = { 21 | val jars: String = System.getProperty("user.home") + "/.ivy2/cache" 22 | val cmd = mutable.Buffer("java", 23 | "-cp", jars + "/org.scala-lang/scala-library/jars/scala-library-" + ScalaVersion + ".jar" + 24 | File.pathSeparator + jars + 25 | "/org.scala-lang.modules/scala-xml_2.11/bundles/scala-xml_2.11-1.0.2.jar" + 26 | File.pathSeparator + jars + 27 | "/org.scoverage/scalac-scoverage-runtime_2.11/jars/scalac-scoverage-runtime_2.11-1.1.1.jar" + 28 | File.pathSeparator + jars + 29 | "/junit/junit/jars/junit-4.11.jar" + 30 | File.pathSeparator + jars + 31 | "/org.hamcrest/hamcrest-all/jars/hamcrest-all-1.3.jar" + 32 | File.pathSeparator + ClassPath) 33 | cmd += mainObj 34 | cmd ++= args 35 | 36 | runCmdWithInput(input, cmd: _*) 37 | } 38 | 39 | def runCmd(cmd: String*): (Int, String, String) = { 40 | runCmdWithInput("", cmd: _*) 41 | } 42 | 43 | def runCmdWithInput(input: String, cmd: String*): (Int, String, String) = { 44 | val process = new ProcessBuilder(cmd).start() 45 | if (!input.isEmpty) { 46 | val writer = new PrintWriter(process.getOutputStream) 47 | writer.println(input) 48 | writer.flush() 49 | } 50 | 51 | val out = Source.fromInputStream(process.getInputStream).mkString 52 | val err = Source.fromInputStream(process.getErrorStream).mkString 53 | process.waitFor() 54 | 55 | (process.exitValue(), out, err) 56 | } 57 | 58 | def printToTmpFile(fileName: String, text: String): File = { 59 | val file = File.createTempFile(fileName, "txt") 60 | file.deleteOnExit() 61 | 62 | val writer = new PrintWriter(file) 63 | try { 64 | writer.print(text) 65 | file 66 | } 67 | finally { 68 | writer.close() 69 | } 70 | } 71 | 72 | def resourceToTmpFile(resourcePath: String): File = { 73 | val resourceFile = new File(resourcePath) 74 | val file = File.createTempFile(resourceFile.getName, "." + Utils.getFileExt(resourceFile)) 75 | file.deleteOnExit() 76 | 77 | val in = getClass.getResourceAsStream(resourcePath) 78 | val out = new FileOutputStream(file) 79 | try { 80 | val buf = new Array[Byte](2048) 81 | var len = in.read(buf) 82 | while (len > 0) { 83 | out.write(buf, 0, len) 84 | 85 | len = in.read(buf) 86 | } 87 | 88 | file 89 | } 90 | finally { 91 | out.close() 92 | } 93 | } 94 | 95 | def withOutput(block: => Unit): String = { 96 | val (out, _) = withOutputAndResult(block) 97 | out 98 | } 99 | 100 | def withOutputAndResult[T](block: => T): (String, T) = { 101 | val out = new ByteArrayOutputStream() 102 | val result = Console.withOut(out)(block) 103 | (out.toString, result) 104 | } 105 | 106 | def withTiming[T](block: => T): (Long, T) = { 107 | val start = System.currentTimeMillis() 108 | val result = block 109 | (System.currentTimeMillis() - start, result) 110 | } 111 | } 112 | --------------------------------------------------------------------------------