1 // regular expressions |
1 // Scala Lecture 3 |
2 // ?? polymorphism |
2 //================= |
3 |
3 |
4 A function should do one thing, and only one thing. |
4 |
5 |
5 // One of only two places where I conceded to mutable |
6 Make your variables immutable, unless there's a good reason not to. |
6 // data structures: The following function generates |
7 |
7 // new labels |
8 You can be productive on Day 1, but the language is deep, so as you go |
8 |
9 along you’ll keep learning, and finding newer, better ways to write |
9 var counter = -1 |
10 code. Scala will change the way you think about programming (and |
10 |
11 that’s a good thing). |
11 def fresh(x: String) = { |
12 |
12 counter += 1 |
13 Of all of Scala’s benefits, what I like best is that it lets you write |
13 x ++ "_" ++ counter.toString() |
14 concise, readable code |
14 } |
15 |
15 |
16 |
16 fresh("x") |
17 |
17 fresh("x") |
18 ScalaTest download page: http://www.scalatest.org/download |
18 |
|
19 // this can be avoided, but would have made my code more |
|
20 // complicated |
|
21 |
|
22 |
|
23 // Tail recursion |
|
24 //================ |
|
25 |
|
26 def my_contains(elem: Int, lst: List[Int]) : Boolean = lst match { |
|
27 case Nil => false |
|
28 case x::xs => |
|
29 if (x == elem) true else my_contains(elem, xs) |
|
30 } |
|
31 |
|
32 my_contains(4, List(1,2,3)) |
|
33 my_contains(2, List(1,2,3)) |
|
34 |
|
35 my_contains(1000000, (1 to 1000000).toList) |
|
36 my_contains(1000001, (1 to 1000000).toList) |
|
37 |
|
38 |
|
39 //factorial 0.1 |
|
40 |
|
41 def fact(n: Long): Long = |
|
42 if (n == 0) 1 else n * fact(n - 1) |
|
43 |
|
44 fact(10000) |
|
45 |
|
46 |
|
47 def factT(n: BigInt, acc: BigInt): BigInt = |
|
48 if (n == 0) acc else factT(n - 1, n * acc) |
|
49 |
|
50 |
|
51 factT(10000, 1) |
|
52 |
|
53 // my_contains and factT are tail recursive |
|
54 // you can check this with |
|
55 |
|
56 import scala.annotation.tailrec |
|
57 |
|
58 // and the annotation @tailrec |
|
59 |
|
60 // for tail-recursive functions the compiler |
|
61 // generates a loop-like code, which does not |
|
62 // to allocate stack-space in each recursive |
|
63 // call; scala can do this only for tail-recursive |
|
64 // functions |
|
65 |
|
66 // consider the following "stupid" version of the |
|
67 // coin exchange problem: given some coins and a, |
|
68 // total, what is the change can you get |
|
69 |
|
70 val coins = List(4,5,6,8,10,13,19,20,21,24,38,39, 40) |
|
71 |
|
72 def first_positive[B](lst: List[Int], f: Int => Option[B]): Option[B] = lst match { |
|
73 case Nil => None |
|
74 case x::xs => |
|
75 if (x <= 0) first_positive(xs, f) |
|
76 else { |
|
77 val fx = f(x) |
|
78 if (fx.isDefined) fx else first_positive(xs, f) |
|
79 } |
|
80 } |
|
81 |
|
82 |
|
83 def search(total: Int, coins: List[Int], cs: List[Int]): Option[List[Int]] = { |
|
84 if (total < cs.sum) None |
|
85 else if (cs.sum == total) Some(cs) |
|
86 else first_positive(coins, (c: Int) => search(total, coins, c::cs)) |
|
87 } |
|
88 |
|
89 search(11, coins, Nil) |
|
90 search(111, coins, Nil) |
|
91 search(111111, coins, Nil) |
|
92 |
|
93 val junk_coins = List(4,-2,5,6,8,0,10,13,19,20,-3,21,24,38,39, 40) |
|
94 search(11, junk_coins, Nil) |
|
95 search(111, junk_coins, Nil) |
|
96 |
|
97 |
|
98 import scala.annotation.tailrec |
|
99 |
|
100 @tailrec |
|
101 def asearch(total: Int, coins: List[Int], acc_cs: List[List[Int]]): Option[List[Int]] = acc_cs match { |
|
102 case Nil => None |
|
103 case x::xs => |
|
104 if (total < x.sum) asearch(total, coins, xs) |
|
105 else if (x.sum == total) Some(x) |
|
106 else asearch(total, coins, coins.filter(_ > 0).map(_::x) ::: xs) |
|
107 } |
|
108 |
|
109 val start_acc = coins.filter(_ > 0).map(List(_)) |
|
110 asearch(11, junk_coins, start_acc) |
|
111 asearch(111, junk_coins, start_acc) |
|
112 asearch(111111, junk_coins, start_acc) |
|
113 |
|
114 // moral: whenever a recursive function is resource-critical |
|
115 // (i.e. works on large recursion depth), then you need to |
|
116 // write it in tail-recursive fashion |
|
117 |
|
118 |
|
119 // Polymorphism |
|
120 //============== |
|
121 |
|
122 def length_int_list(lst: List[Int]): Int = lst match { |
|
123 case Nil => 0 |
|
124 case x::xs => 1 + length_int_list(xs) |
|
125 } |
|
126 |
|
127 length_int_list(List(1, 2, 3, 4)) |
|
128 |
|
129 |
|
130 def length[A](lst: List[A]): Int = lst match { |
|
131 case Nil => 0 |
|
132 case x::xs => 1 + length(xs) |
|
133 } |
|
134 |
|
135 |
|
136 def map_int_list(lst: List[Int], f: Int => Int): List[Int] = lst match { |
|
137 case Nil => Nil |
|
138 case x::xs => f(x)::map_int_list(xs, f) |
|
139 } |
|
140 |
|
141 map_int_list(List(1, 2, 3, 4), square) |
|
142 |
|
143 |
|
144 // Remember? |
|
145 def first[A, B](xs: List[A], f: A => Option[B]): Option[B] = ... |
|
146 |
|
147 |
|
148 // polymorphic classes |
|
149 //(trees with some content) |
|
150 |
|
151 abstract class Tree[+A] |
|
152 case class Node[A](elem: A, left: Tree[A], right: Tree[A]) extends Tree[A] |
|
153 case object Leaf extends Tree[Nothing] |
|
154 |
|
155 def insert[A](tr: Tree[A], n: A): Tree[A] = tr match { |
|
156 case Leaf => Node(n, Leaf, Leaf) |
|
157 case Node(m, left, right) => |
|
158 if (n == m) Node(m, left, right) |
|
159 else if (n < m) Node(m, insert(left, n), right) |
|
160 else Node(m, left, insert(right, n)) |
|
161 } |
|
162 |
|
163 |
|
164 // the A-type needs to be ordered |
|
165 |
|
166 abstract class Tree[+A <% Ordered[A]] |
|
167 case class Node[A <% Ordered[A]](elem: A, left: Tree[A], right: Tree[A]) extends Tree[A] |
|
168 case object Leaf extends Tree[Nothing] |
|
169 |
|
170 |
|
171 def insert[A <% Ordered[A]](tr: Tree[A], n: A): Tree[A] = tr match { |
|
172 case Leaf => Node(n, Leaf, Leaf) |
|
173 case Node(m, left, right) => |
|
174 if (n == m) Node(m, left, right) |
|
175 else if (n < m) Node(m, insert(left, n), right) |
|
176 else Node(m, left, insert(right, n)) |
|
177 } |
|
178 |
|
179 |
|
180 val t1 = Node(4, Node(2, Leaf, Leaf), Node(7, Leaf, Leaf)) |
|
181 insert(t1, 3) |
|
182 |
|
183 val t2 = Node('b', Node('a', Leaf, Leaf), Node('f', Leaf, Leaf)) |
|
184 insert(t2, 'e') |
|
185 |
|
186 |
|
187 |
|
188 // Regular expressions - the power of DSLs |
|
189 //========================================= |
|
190 |
|
191 |
|
192 abstract class Rexp |
|
193 case object ZERO extends Rexp |
|
194 case object ONE extends Rexp |
|
195 case class CHAR(c: Char) extends Rexp |
|
196 case class ALT(r1: Rexp, r2: Rexp) extends Rexp |
|
197 case class SEQ(r1: Rexp, r2: Rexp) extends Rexp |
|
198 case class STAR(r: Rexp) extends Rexp |
|
199 |
|
200 |
|
201 // (ab)* |
|
202 val r0 = ?? |
|
203 |
|
204 |
|
205 // some convenience for typing in regular expressions |
|
206 import scala.language.implicitConversions |
|
207 import scala.language.reflectiveCalls |
|
208 |
|
209 def charlist2rexp(s: List[Char]): Rexp = s match { |
|
210 case Nil => ONE |
|
211 case c::Nil => CHAR(c) |
|
212 case c::s => SEQ(CHAR(c), charlist2rexp(s)) |
|
213 } |
|
214 implicit def string2rexp(s: String): Rexp = charlist2rexp(s.toList) |
|
215 |
|
216 |
|
217 val r1 = STAR("ab") |
|
218 val r2 = STAR("") |
|
219 val r3 = STAR(ALT("ab", "ba")) |
|
220 |
|
221 |
|
222 implicit def RexpOps (r: Rexp) = new { |
|
223 def | (s: Rexp) = ALT(r, s) |
|
224 def % = STAR(r) |
|
225 def ~ (s: Rexp) = SEQ(r, s) |
|
226 } |
|
227 |
|
228 implicit def stringOps (s: String) = new { |
|
229 def | (r: Rexp) = ALT(s, r) |
|
230 def | (r: String) = ALT(s, r) |
|
231 def % = STAR(s) |
|
232 def ~ (r: Rexp) = SEQ(s, r) |
|
233 def ~ (r: String) = SEQ(s, r) |
|
234 } |
|
235 |
|
236 val digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" |
|
237 val sign = "+" | "-" | "" |
|
238 val number = sign ~ digit ~ digit.% |
|
239 |
|
240 |
|
241 |
|
242 // Lazyness with style |
|
243 //===================== |
|
244 |
|
245 // The concept of lazy evaluation doesn’t really exist in |
|
246 // non-functional languages, but it is pretty easy to grasp. |
|
247 // Consider first |
|
248 |
|
249 def square(x: Int) = x * x |
|
250 |
|
251 square(42 + 8) |
|
252 |
|
253 // this is called strict evaluation |
|
254 |
|
255 |
|
256 def expensiveOperation(n: BigInt): Boolean = expensiveOperation(n + 1) |
|
257 val a = "foo" |
|
258 val b = "foo" |
|
259 |
|
260 val test = if ((a == b) || expensiveOperation(0)) true else false |
|
261 |
|
262 // this is called lazy evaluation |
|
263 // you delay compuation until it is really |
|
264 // needed; once calculated though, does not |
|
265 // need to be re-calculated |
|
266 |
|
267 // a useful example is |
|
268 def time_needed[T](i: Int, code: => T) = { |
|
269 val start = System.nanoTime() |
|
270 for (j <- 1 to i) code |
|
271 val end = System.nanoTime() |
|
272 ((end - start) / i / 1.0e9) + " secs" |
|
273 } |
|
274 |
|
275 |
|
276 // streams (I do not care how many) |
|
277 // primes: 2, 3, 5, 7, 9, 11, 13 .... |
|
278 |
|
279 def generatePrimes (s: Stream[Int]): Stream[Int] = |
|
280 s.head #:: generatePrimes(s.tail filter (_ % s.head != 0)) |
|
281 |
|
282 val primes: Stream[Int] = generatePrimes(Stream.from(2)) |
|
283 |
|
284 primes.filter(_ > 100).take(2000).toList |
|
285 |
|
286 time_needed(1, primes.filter(_ > 100).take(2000).toList) |
|
287 time_needed(1, primes.filter(_ > 100).take(2000).toList) |
|
288 |
|
289 |
|
290 |
|
291 // streams are useful for implementing search problems ;o) |
|
292 |
|
293 |
|
294 |
|
295 |
|
296 // The End |
|
297 //========= |
|
298 |
|
299 // A function should do one thing, and only one thing. |
|
300 |
|
301 // Make your variables immutable, unless there's a good |
|
302 // reason not to. |
|
303 |
|
304 // You can be productive on Day 1, but the language is deep. |
|
305 |
|
306 // I like best about Scala that it lets you write |
|
307 // concise, readable code |