Is there global warming?  

Activity 2: Building the case

Learning objectives:

  • Swift Playgrounds Modules – Introduction to access control (public, private)
  • writing useful functions
  • wrapper functions

Introduction Activity:  Cleaning up our project!  

Our code is getting quite bulky.  There is a very long time series array defined as a let constant and at least three functions that we will be using over and over again.  The functions are well-constructed and, as such, we will not need to modify them much in the future.

Swift Playgrounds on iPad allows you to put some code in a separate file, isolated from your working code but still accessible to your working code.

In the upper-left menu of Swift Playgrounds, locate the different modules.  Copy and paste the long let urbanaRaw = [ … ] array definition and paste it into the ******.swift file.  You will also need to add the keyword ‘public’ in front of each of the array and function definitions:

public let urbanaRaw = [ 9.53, 11.43, ..., ]

public func average(inputData: [Double], ...

public func createAnomalies(from input: [Double], ...

“public” and “private” are access control keywords.  Labeling a constant, variable or function as public allows it to be used anywhere in the project – even if it is defined in another file.  A private label requires that the function or variable be defined in the same file as it is used.

Moving these arrays and functions to another file will keep our main working area much more organized and reduce the clutter.  In addition, it will let us simply use these powerful functions like average() and createAnomalies() in other projects without having to touch the code by just copying this module file into another project.  Software engineers love saving time by reusing code!

Start writing or type / to choose a block

Activity 2a:  Supercharging our average() function!

Start with review of homework question:  “How does the average of the first half of the record compare to the average of the second half of the record?”

Ask: “Did anyone reused the average function we wrote last time to solve this problem?”  Most everyone will have likely used the approach below. Quickly code it up and discuss the algorithm as you work through it:

let firstHalf = [Double]()  // create a new array that will hold a copy of the first half of the data
let secondHalf = [Double]()  // create a new array that will hold a copy of the first half of the data
let halfWayIndex = urbanaRaw.count / 2      // index corresponding to the middle value of the array


// load up the first half of the data
for index in 0 ..< halfWayIndex {
   firstHalf.append(urbanaRaw[index])
}

// load up the second half of the data
for index in halfWayIndex ..< urbanaRaw.count {
   secondHalf.append(urbanaRaw[index])
}

let firstHalfAve = average(inputData: firstHalf, missingValue: 999.9)
let secondHalfAve = average(inputData: secondHalf, missingValue: 999.9)

show("The average of temps in the first half is \(firstHalfAve); the second half avg. is \(secondHalfAve)")

This is a nice solution to the problem and definitely gets the answers we need to provide compelling information for our congressional testimony.

Ask the students if they can see any potential problem with this approach?  Is there anything they can do to make it more efficient?  If students do not come up with this, remind them that there are two ways to define efficiency: 1) reducing the time a program takes to run, and 2) reducing the amount of space (memory) a program needs to run.

Hopefully, a student will volunteer that this algorithm effectively doubles the amount of memory needed to store the data in the program because it copies the entire array again (separated into first- and second-halves).

Suggest to students that this may not be a problem with an array of only 150 data values like this, but what if we had billions of values we wanted to process in an array?  The program might not even be able to run if it had to make a copy of the array of this size.

Ask students to work with you to come up with a way to solve the problem without copying the array.  (We may need to alter or rewrite our existing average() function!)

We will solve this problem by altering our average() function so that it can compute a subset of the array.  We will pass in two new parameters, a startIndex: Int and an endIndex: Int.  The loop used to compute the average will only then sum the values between startIndex and endIndex.

Work on this together, working directly in our module file *******.swift and modify the average() function to something like this:Preformatted

public func average(inputData: [Double], fromIndex startIndex:Int, toIndex endIndex: Int, missingValue: Double) -> Double {
    var count = 0.0
    var sum = 0.0
    for index in startIndex ... endIndex {
        if inputData[index] != missingValue {
            sum += inputData[index]
            count += 1.0
        }
    }
    if count == 0.0 {
        return 0.0
    }
    return sum / count
}This simplifies our main program calculation by calling the average function on the raw data array this:

This simplifies our main program calculation by calling the average function on the raw data array this:

let firstHalf = [Double]()  // create a new array that will hold a copy of the first half of the data
let firstHalfAve = average(inputData: urbanaRaw, fromIndex: 0, toIndex: halfWay, missingValue: 999.9)
let secondHalfAve = average(inputData: secondHalf, fromIndex: halfWay, toIndex: urbanaRaw.count, missingValue: 999.9)

show("The average of temperatures in the first half is \(firstHalfAve); the second half avg. is \(secondHalfAve)")

A simpler main program and it doesn’t waste space by copying the data!

BUT!!!  I don’t know about you, but I miss the simplicity of getting the mean of an entire array by using our old simpler function:

let average = average(inputData: series, missingValue: 999.9)  // This EASY call no longer exists

Instead, to get the average of an entire array we would have to call the function like this:

let average = average(inputData: series, fromIndex: 0, toIndex: series.count, missingValue: 999.9)  // Oh bother

There is a simple solution however so that we can have our cake and eat it too!  We will write a second average() function that is a more simple “wrapper” function that calls our more complicated function.  Add this simpler version of average() that computes an average of an entire array to our module ******.swift file:

//  Wrapper function to give us the functionality of asking for the average of the entire array
public func average(inputData: [Double], missingValue: Double) -> Double {
   return average(inputData: inputData, fromIndex: 0, toIndex: inputData.count - 1, missingValue: missingValue)}

Now we have two functions named average().  This is perfectly OK in Swift, as long as the number and/or types of the function arguments are different.  In this case, our simple version takes only an array and a missing value, then when it is executed, it calls our more complicated version that performs the ‘guts’ of the averaging computation.  The wrapper function calls the more complicated average() function by passing in startIndex of 0 and endIndex of series.count so that the entire array is processed and all values are included in the average.

Again, software engineers avoid extra work whenever possible.  In this case, we get two very useful functions for the basic effort of just one.  It’s always nice to impress your boss and say “yea, I went ahead and wrote two useful versions as long as I was working on it.  😉

Section 2 – Homework ideas:

What are the odds that any given year is warmer than the previous?

What are the odds that any given year is cooler than the previous?

Next week, be ready to talk about how you might answer the questions:  

What are the odds that any given decade is warmer than the previous?

What are the odds that any given decade is colder than the previous?

Optional challenge:  code up part or all of the last question above!  Remember mod operator (%)?  … it might be helpful.

Part 2 of Swift Data Science Lessons: Building the case