/* 22c22: Object Oriented Software Development Fall 2012 The University of Iowa Instructor: Cesare Tinelli */ /* Scala examples seen in class */ /* Pattern matching */ val t = (3,5,2) // declare three new variables x1,x2,x3 // the value of xi is the value of t's i-th component // the pattern (x1,x2,x3) is matched against (3,5,2) val (x1,x2,x3) = t // _ is for "don't care" positions val (y1,y2,_) = t // pattern matching can be used with nested patterns val t = ((2,3),5,(6,2)) val (_, x, _) = t val (_, _, (x,_)) = t // patterns are extremely useful with the // // (_ match {case _ => _ ...}) // // construct. // each expression between case and => is a pattern // matched against the firt argument of match // patterns are checked one at a time, top-down var n = 1 n match { case 0 => "zero" case 1 => "one" case _ => "other" } // if all cases fail an exception is raised n match { case 0 => "zero" } // match and patterns allow one to write cleaner and more compact code def convert (n:Int) = n match { case 0 => "zero" case 1 => "one" case _ => "other" } // the bodies of the case statements can be any expression (in particular a block) // but, like in the the two branches of the if construct, they all have to be of // same type def p (n: Int) = n match { case 0 => { print("You entered ") println("zero") } // type of this is Unit case _ => { print("You did not enter ") println("zero") } // type of this is Unit } // patterns can be complex and nested var v = (1, 2) v match { case (0, 0) => "a" case (0, _) => "b" case (_, 0) => "c" } // bothZero returns true iff both of its arguments are 0 // common (bad) coding style def bothZero (x1:Int, x2:Int) = { if (x1 == 0) { if (x2 == 0) true else false } else false } // same method but using match def bothZero (x1:Int, x2:Int) = (x1, x2) match { case (0, 0) => true case _ => false } // the input here is already a pair that can be pattern matched // directly def bz (p:(Int, Int)) = p match { case (0, 0) => true case _ => false } def and (p:(Boolean, Boolean)) = p match { case (true, y) => y case (false, _) => false } // the scope of a pattern variable in a case is the body of that case def and (p:(Boolean, Boolean)) = p match { case (true, y) => y case (false, _) => y // y is undefined here } // factorial with match def fact (n:Int):Int = n match { case 0 => 1 case _ => n * fact(n - 1) } // the match construct builds expressions val r = (0 < 1, 1 == 1) match { case (true, y) => y case (false, _) => false } /* Collection types: Arrays */ // creates an array with 5 elements and calls it a val a = new Array(5) // more typically, one specifies the element type as well; // the array elements start with a type-dependent default value val a = new Array[Double](5) a[1] // standard notation *not* used in Scala a(1) // Scala's notation for array access a(0) = 2.5 // array update a(5) // generates out of bound error // generates an Array[Int] of length 3 and elements 10,11, and 12; // element type Int is inferred Array(10, 11, 12) val a = Array(10, 11, 12) // 3 different ways to generate an Array[Long] // from Int initial values val a = Array[Long](10, 11, 12) // preferred one val a:Array[Long] = Array(10, 11, 12) val a = Array(10:Long, 11, 12) // note the tricky difference val a = Array[Long](10) // a is an Array[Long] with one element: 10 val b = new Array[Long](10) // n is an Array[Long] with 10 elements // array length a.length // multidimentional arrays are arrays of arrays val m = new Array[Array[Int]](10) val m = Array(Array(1, 2, 3), Array(4, 5, 6), Array(7, 8, 9), Array(10, 11, 12)) m(2)(1) // no special syntax for matrix access /* Collections: Lists */ // lists are created similarly to arrays val l = List(1, 2, 3) // Main list methods l.head l.tail l.length // same element access syntax as with arrays l(0) l(3) // a list can be built with elements of *any* type, // but all elements must be of the *same* type List('a', 'b') List("hi", "there") List((1, 2), (1, 4)) List(List(1, 2), List(1, 4)) List(1, "a") // types as a list of Any val l1 = List(1, 2, 3) val l2 = List(1, 2, 3) // identity l1 eq l2 // structural equality l1 == l2 // order and number of occurrences of elements matters for equality List(1,2,3) == List(2,1,3) List(1,2) == List(1,2,2) // Empty list, prints as List() Nil // Evaluates to Nil List() Nil eq List() // bad practice, gives you a list of elements of type Nothing. val l = List() // recommended, declare the element type of an empty list explicitly val il = List[Int]() var x = List("a", "b") // Causes an implicit conversion of 1 and 2 to Long. var y = List[Long](1, 2) // if T1 and T2 are different types, // List[T1] is a different type from List[T2] // no implict conversions are defined for them x = il y = il // Lists are built by increasingly adding elements in front // of Nil with the "cons" operator :: val x = 6 :: Nil val y = 5 :: x // note that x is unchanged x y.tail eq x // :: is right associative. 3 :: 4 :: Nil // same as 3 :: (4 :: Nil) // List(3,4) can be understood as syntact sugar for 3 :: (4 :: Nil) 3 :: 4 // error, right-hand argument not a list // can mix and match notation 3 :: List(4) // note type here List(7) :: Nil val l = List(1, 2, 3) // invariant defining head and tail in terms of :: // for any non-empty list (l.head :: l.tail) == l // pattern matching also works with :: val h :: t = l // why it works: l is really 1 :: (2 :: (3 :: Nil)) val h :: t = 1 :: (2 :: (3 :: Nil)) // deeper patters for lists with at least two elements val x1 :: x2 :: t = l val x1 :: x2 :: x3 :: t = l val x1 :: x2 :: x3 :: x4 :: t = l // fails because l has < 4 elements // "List" syntax can also be used for pattern matching val List(x1, x2, x3) = l // fails because l has length 3 val List(x1) = l // also fails val List(x1, x2) = List("a") // recursive definition of length function for lists // based on navigating the list structure // If l is empty its length is 0. If l is non-empty, // and so has a tail t, its lenght is 1 + the length of t def len (l:List[Int]):Int = l match { case Nil => 0 case _ :: t => 1 + len(t) } len(List(1, 1, 1)) // min returns the minimun element of an non-empty integer list, // it raises an exception with empty lists // // if l has only one element n, the minimum is n // if l has at least two elements its miminum is the smaller // between l's head and the minimun of l's tail. def min (l:List[Int]):Int = l match { case Nil => throw new IllegalArgumentException case n :: Nil => n case n :: t => { val m = min(t) if (n < m) n else m } } // concat contatenates two lists def concat(l:List[Int], m:List[Int]):List[Int] = l match { // when l is empty, its concatenation with m is just m case Nil => m // when l has a head h and a tail t, its contatenation with m // is obtained by inserting h in front of the contatenation // of t with m case h :: t => h :: concat(t, m) } concat(List(1, 2), List(3, 4, 5)) // the effect of concat is achieved with the predefined overloaded operator ++ List(1,2) ++ List(3,4,5) // reverse reverses a list def reverse (l:List[Int]):List[Int] = l match { // when l is empty it is its own reverse case Nil => l // when l has a head h and a tail t, its reverse is the contatenation // of the reverse of t with a list containing just h case h :: t => { val rt = reverse(t) val lh = List(h) concat(rt, lh) } } reverse(List(3, 4, 5)) // more compact version of the implementation above // both version are inefficient because each reverse(t) is // scanned linearly by concat each time def reverse (l:List[Int]):List[Int] = l match { case Nil => l case h :: t => concat(reverse(t), List(h)) } // this reverse uses a local auxiliary function rev // rev is "tail-recursive", it builds the reverse of l as it scans it // by incrementally moving its elements into an initially empty list rl. // l is scanned only once def reverse (x:List[Int]):List[Int] = { def rev (l:List[Int], rl:List[Int]):List[Int] = l match { // when l has a head h and a tail t, put h in front of rl // and continue with t and h::rl case h :: t => rev(t, h :: rl) // when l is Nil, rl contains by construction the reverse // of the original list case Nil => rl } rev(x, Nil) }