Functions

We have functions today:)

Custom Sorting

  • We can sort any type with any comparatror
  • But what if we want to sort points by their distance from a reference point
    • In general: what if the comparator needs more parameters than just the two elements?
  • We can dynamically create a new function with the additional parameters “built-in”

Returning Functions

  • We can write a function / method that takes all the needed parameters and returns a funcrtion that fits the signature of a comparator
  • The distanceComparator method returns a comparator that compares the distance to a reference point

1
2
3
4
5
6
7
8
9
def distacne(v1: PhysicsVector, v2: PhysicsVector): Double = {
Math.sqrt(Math.pow(v1.x - v2.x, 2.0) + Math.pow(v1.y - v2.y, 2.0) + Math.pow(v1.z - v2.z, 2.0))
}

def distanceComparator(referencePoint: PhysicsVector): (PhysicsVector, PhysicsVector) => Boolean = {
(v1: PhysicsVector, v2: PhysicsVector) => {
distance(v1, referencePoint) < distance(v2, referencePoint)
}
}
  • Use distanceComparator to create a comparator function when needed
  • Can create different comparators with different reference points
    • Global state would only allow one comparator at a time
      1
      2
      val referencePoint = new PhysicsVector(0.5, 0.5, 0.0)
      val sortedPoints = NergeSort.mergeSort(points, distanceComparator(referencePoint))

Collection Methods

  • We can apply first-order functions to compress our code when working with data structures
  • We’ll see a variety of methods that take functions as parameters to help us work with data

For Each

  • Call a function on each elemnents of a List
  • Only use for the side-effects
    • ie. Not too useful when embracing immutability
1
2
3
4
5
6
7
8
9
val words: List[String] = List("zero", "one", "two", "three")
words.foreach(println)
/***
Will print:
zero
one
two
three
***/

Filter

  • Takes a function that returns a Boolean
  • Returns a new List containing only the elements for which the function returns true
1
2
3
4
5
6
7
8
val words: List[String] = List("zero", "one", "two", "three")
val filteredWords: List[String] = words.filter(_.length > 3)
filteredWords.foreach(printtln)
/***
Will print:
zero
three
***/

Map

  • Takes a function of the data type to another data type
  • Returns a new List containing the retun values of the function with each element as an input
1
2
3
4
5
6
7
8
9
10
11
val numbers: List[Double] = List(1.0, 2.0, 3.0, 4.0, 5.0)
val numberSquared: List[Double] = numbers.map(Math.pow(_, 2.0))
numbersSquared.foreach(println)
/***
Will print:
1.0
4.0
9.0
16.0
25.0
***/

  • The map method takes 2 type parameters
  • We can provide a function that “maps” the elements to a different type
    • The types can be inferred by the types of the provided function
1
2
3
4
5
6
7
8
9
10
val words: List[String] = List("zero", "one", "two", "three")
val wordLengths: List[Int] = words.map(_.length)
wordLengths.foreach(println)
/***
Will print:
4
3
3
5
***/

Yield

  • As alternate syntax to map, we can use the yield keyword
  • Add the keyword yield before the body of a loop
  • The last expression of the loop body will be “collected” at each iteration
1
2
3
4
5
6
7
8
9
10
11
12
13
val numbers: List[Double] = List(1.0, 2.0, 3.0, 4.0, 5.0)
val numbersSquared: List[Double] = for (number <- numbers) yield {
Math.pow(number, 2.0)
}
numbersSquared.foreach(println)
/***
Will print:
1.0
4.0
9.0
16.0
25.0
***/

  • Using yield will create a data structure of the same type as the one being iterated over
  • It’s not always possible to match the type exactly
  • Scala will default to certain data structure
    • Use toList tp convert the default type to a List
1
2
3
4
5
6
7
8
9
10
11
12
val numberSquared: List[Double] = (for(number <- 1 to 5) yield {
Math.pow(number, 2.0)
}).toList
numbersSquared.foreach(println)
/***
Will print:
1.0
4.0
9.0
16.0
25.0
***/

Reduce

  • Takes a function that combines two values of the data type intyto a single value of that type
  • Calls this function on all elements
    • Combines the data into a single value
  • The first parameter of the function is the accumulator
    • Stores the total value accumulated so far
    • Initialized as the first element (Note: This example breaks if 1.0 is not the first elementa)
1
2
3
4
val numbers: List[Double] = List(1.0, 2.0, 3.0, 4.0, 5.0)
val sumSquares: (Double, Double) => Double = (a: Double, b: Double) => a + Math.pow)(b, 2.0)
val sumOfSquares: Double = numbers.reduce(sumSquares)
println(sumOfSquares) // 55.0

  • We can use the _ shorthand with two parameters
    • The order of appearance of the _’s is the parameter order
  • Can not use _ shorthand if you need to use a input twice
1
2
3
val numbers: List[Double] = List(1.0, 2.0, 3.0, 4.0, 5.0)
val sumOfSquares: Double = numbers.reduce(_ + Math.pow(_, 2.0))
println(sumOfSquares) // 55.0

Fold

  • Similar to reduce
  • Use fold if you need to initialize your accumulator
  • Use fold if you are reducing a different type than the data type
1
2
3
val numbers: List[Double] = List(1.0, 2.0, 3.0, 4.0, 5.0)
val mult: Double = numbers.fold(1.0)(_ * _)
println(mult) // 1*2*3*4*5 = 120.0

  • To accumulate to a type different than the data type
    • Use the left / right version of fold
  • Initial value determines the accumulator type
    • This value is returned if the input is the empty list
1
2
3
4
5
val words: List[String] = List("zero", "one", "two", "three")
val totalLength: Int = words.foldLeft(0)(_ + _.length)
val totalLength2: Int = words.foldRight(0)(_.length + _)
println(totalLength) // 15
println(totalLength2) // 15

  • Using fold defaults to foldLeft
    • Start with the first (left=most) element
  • To accumulate from the end of the List use foldRight
    • Must reverse the parameter order when using foldRight / reduceRight
      • Accumulator is second parameter, data is first element

Example - Polunomials

———–Check the Lecture Recording—————

Lecture Question

Restriction:
No state is allowed in this question. Specifically, the keyword “var” is banned. (ie. You are expected to use a recursive solution)

Question:
In a package named “functions” add to the object named Numbers (The object with your fib method) a method named averageInRange that:

  • Takes a List of Doubles as a parameter
  • Returns a functions that takes 2 Doubles and returns a Double
    • This function will return the average of all the numbers in the List that are between the two input Double
    • Exclude the endpoints (ie. Use < and >, not <= and >=)
    • The first parameter of the function is the min value and the second is the max value of the range
    • Ex. Averaging the list (1.0, 4.0, 2.0, 5.0, 3.0) with endpoints 1.5 and 4.9
      • Average in range is the average of (2.0, 3.0, 4.0) == 3.0

Testing:
In a package named “tests” create a class named “TestAverageInRange” as a test suite that tests all the functionality listed above. (You don’t have to test end point exclusion since Doubles are not reliably equal)