Anfragen für Listen Kapitel 7 des Buches, von Java-Selbstbau nach Scala-Library portiert. 2014-11-14 Christoph Knabe L. Piepmeyer: Funktionale Programmierung - Abfragen für Listen 1 MapReduce-Verfahren Google u.a. verwenden Map-Reduce-Verfahren zur Verarbeitung riesiger Datenmengen ”Our abstraction is inspired by the map and reduce primitives present in Lisp and many other functional languages.” Jeffrey Dean und Sanjay Ghemawat, Google Labs Map kommt gleich Reduce entspricht im Wesentlichen der Faltung L. Piepmeyer: Funktionale Programmierung - Abfragen für Listen 2 Select-Statements Am Ende des Kapitels können wir Listen mit selectähnlicher Funktionalität bearbeiten. Im Kleinen geht das jetzt schon: SQL: Java: Übung IntList select sum(v) from values values.sum() L. Piepmeyer: Funktionale Programmierung - Abfragen für Listen 3 Vorbereitungen Wir werden wieder mit Funktionen höherer Ordnung arbeiten: type Predicate[T] = T => Boolean Beispiel: def even: Predicate[Int] = _ % 2 == 0 Wir benutzen jetzt aber die generische ScalaCollections-Bibliothek. L. Piepmeyer: Funktionale Programmierung - Abfragen für Listen 4 Primitive Listenoperationen Erzeugen einer leeren Liste: Nil List[Int]() List.empty List.empty[Int] //Typ //Typ //Typ //Typ List[Nothing] List[Int] List[Nothing] List[Int] Prepend-Operation heißt :: (rechtsassoziativ) val list12 = 1 :: 2 :: Nil Multi-Arg-Fabrikmethode im Begleitobjekt: val list123 = List(1, 2, 3) Zugriffsmethoden: list123.head == 1 && list123.tail == List(2, 3) L. Piepmeyer: Funktionale Programmierung - Abfragen für Listen 5 Listenelemente filtern Nachbau in Klasse ListTest: def filter[T]( list: List[T], predicate: Predicate[T] ): List[T] = { if (list isEmpty) return list val filteredTail = filter(list.tail, predicate) if(predicate(list.head)){ list.head :: filteredTail } else filteredTail } Ist aber schon in scala.List enthalten. L. Piepmeyer: Funktionale Programmierung - Abfragen für Listen 6 Ein Beispiel Eine Liste aller geraden Zahlen zwischen 1 und 10 erzeugen: (1 to 10).toList filter even L. Piepmeyer: Funktionale Programmierung - Abfragen für Listen 7 Abbildungen Entsprechend der where -Bedingung des folgenden select-Statements können wir bereits filtern. select v*v from values where v mod 2 = 0 Die Umsetzung des select-Teils für Listen erfordert, dass wir Listenelemente auch abbilden können. L. Piepmeyer: Funktionale Programmierung - Abfragen für Listen 8 Abbildungen Nehmen wir eine Int-Funktion: Int => Int Beispiel: def square: Int=>Int = x=>x*x Wir definieren damit eine Funktion höherer Ordnung: L. Piepmeyer: Funktionale Programmierung - Abfragen für Listen 9 Die Funktion map Bildet jedes Element einer Liste mit der Funktion f ab: //In Klasse ListTest: def map[T,R]( list: List[T], f: T => R ): List[R] = { if (list isEmpty) return Nil f(list head) :: map(list.tail, f) } map ist vordefiniert in Klasse List. L. Piepmeyer: Funktionale Programmierung - Abfragen für Listen 10 Beispiel Eine Liste der Quadrate der geraden unter den ersten 10 Zahlen ermitteln: (1 to 10).toList filter even map square L. Piepmeyer: Funktionale Programmierung - Abfragen für Listen 11 Der Werkzeugkasten Die Methoden map und filter bilden zusammen mit der Methode zip (später) einen Werkzeugkasten mit dem wir interessante Beispiele finden können. Übrigens: map und filter können mit Hilfe der Faltung implementiert werden. Versuchen Sie es mal! L. Piepmeyer: Funktionale Programmierung - Abfragen für Listen 12 Primzahlen (Sieb des Eratosthenes) def primes(list: List[Int]): List[Int] = { if (list isEmpty){return list} val head = list.head val filteredTail = list.tail.filter( _ % head != 0 ) head :: primes(filteredTail) } def primes(high: Int): List[Int] = { val numbers: List[Int] = (2 to high) toList; primes(numbers) } L. Piepmeyer: Funktionale Programmierung - Abfragen für Listen 13 Listen verknüpfen: Konkatenation def concat( left: List[Int], right: List[Int] ): List[Int] = left match { case Nil => right case x :: tail => x :: concat(tail, right) } Die Konkatenation ist vordefiniert als Operator ::: val left = List(1, 2, 3, 4) val right = List(5, 6, 7, 8, 9) Left ::: right //1 2 3 4 5 6 7 8 9 L. Piepmeyer: Funktionale Programmierung - Abfragen für Listen 14 Listen verknüpfen: zip Mischen zweier Listen im „Reißverschlussverfahren“: def zip( left: List[Int], right: List[Int] ): List[(Int,Int)] = (left,right) match { case (x :: xs, y :: ys) => (x, y) :: zip(xs, ys) case _ => Nil } Das Mischen ist vordefiniert als Methode List.zip val left = List(1, 2, 3, 4) val right = List(5, 6, 7, 8, 9) left zip right //(1,5), (2,6), (3,7), (4,8) L. Piepmeyer: Funktionale Programmierung - Abfragen für Listen 15 Der Abstand zweier Punkte Ein Punkt sei durch eine Liste seiner Koordinaten dargestellt. def distance( pointA: List[Double], pointB: List[Double] ) = { val paired = pointA zip pointB val squaresOfDiffs = paired.map(p => p._1 - p._2).map(x => x*x) val sum = squaresOfDiffs.foldLeft(0.0)(_ + _) Math.sqrt(sum) } L. Piepmeyer: Funktionale Programmierung - Abfragen für Listen 16 Listen aufteilen: partition partition filtert positive und negative Treffer: val numbers = List( 8, 4, 7, 9, 2, 1, 5, 3, 6 ) val result = numbers.partition(_ < 5) Es wird ein Paar geliefert mit 1. allen Erfüllern des Prädikats 2. allen Nichterfüllern: (List(4, 2, 1, 3), List(8, 7, 9, 5, 6)) L. Piepmeyer: Funktionale Programmierung - Abfragen für Listen 17 Quicksort-Beispiel Das erste Element wird jeweils als Pivot p verwendet. sort(8 4 7 9 2 1 5 3 6) mit p=8 → sort(4 7 2 1 5 3 6) 8 sort(9) mit p=4 → sort(2 1 3) 4 sort(7 5 6) 8 9 mit p=2,7→ sort(1) 2 sort(3) 4 sort(5 6) 7 sort() 8 9 → 1 2 3 4 sort() 5 sort(6) 7 8 9 → 123456789 L. Piepmeyer: Funktionale Programmierung - Abfragen für Listen 18 Quicksort def sort(xs: List[Int]): List[Int] = xs match { case Nil => Nil case pivot :: tail => val (left,right) = tail partition (_ <= pivot) sort(left) ::: pivot :: sort(right) } L. Piepmeyer: Funktionale Programmierung - Abfragen für Listen 19 Alles klar? Mit Hilfe einiger Funktionen können Listen deklarativ verarbeitet werden. Insgesamt bieten die Funktionen einen Komfort, der ähnlich wie bei SQL ist. filter: Ein Prädikat wird als Parameter übergeben. Es wird eine Liste konstruiert, deren Elemente alle dem Prädikat genügen partition: wie filter, aber es werden positive und negative Treffer als getrennte Listen geliefert. L. Piepmeyer: Funktionale Programmierung - Abfragen für Listen 20 Alles klar? map: Eine Abbildung wird als Parameter übergeben. Die Abbildung wird für alle Listenelemente durchgeführt. zip: Mischt zwei Listen nach dem Reißverschlussprinzip zu einer Liste von Paaren. L. Piepmeyer: Funktionale Programmierung - Abfragen für Listen 21