Display Temperature Averages with JavaScript Functional Programming

image for 'Display Temperature Averages with JavaScript Functional Programming' post

See Demo »

As mentioned both here and here, I try to write as much JavaScript Functional Programming when possible. Even when it’s just for practice.

I came across a pretty neat challenge on a Facebook beginning developers group I help to administer. The challenge provided some good FP practice.

Table of Contents

  1. The Challenge
  2. The Solution
  3. The Basic Code
  4. Determine the Inner Arrays
  5. The Reducer Helper
  6. Display the Temperature Information
  7. Load All the Content Onto the Page
  8. The Final Code
  9. Conclusion

The Challenge

There was an array of arrays. Each inner array represented either column header info or a list containing both temperatures for a city and the city name itself.

The array of arrays looked like this:


[
  ["City", "00-08", "08-16", "16-24", "Average"],
  ["Malmö", 12, 16, 9],
  ["Mariestad", 13, 15, 10],
  ["Stockholm", 13, 15, 13],
  ["Upphärad", 14, 16, 15],
  ["Göteborg", 13, 14, 11]
]

I had to calculate the average temperature for each city, then run code that displayed the array data like this: There were a few challenges here:

  • How do I look at just the numbers in the array to get the average?
  • How do I do that while ignoring the city in this array?
  • How do I display all these arrays with reusable functions, while understanding that the one array is header content and the rest is temperature/city content?

The Solution

The solution was, well, to use functional programming. In other words, I had to create separate functions that implemented specific parts of the above-described tasks.

I did this by:

  • creating a function that determines whether or not the inner array has temperatures.
  • creating a function that calculates the temperature average.
  • creating a function that displays the content on the page.
  • getting all these functions to work together as a team to display the array content on the page.

The Basic Code

The HTML will look like this:


<div class="temperature-info__container">
  <div id="temperatureHeader" class="temperature-info__header"></div>
  <div id="temperatureInfo"></div>
</div>

The BEM-like CSS will look like this:


.temperature-info__container {
  width: 650px;
}

.temperature-info__header {
  font-weight: bold;
  font-style: italic;
  text-transform: uppercase;
}

.temperature-info__single-temp-row {
  margin-bottom: 10px;
}

.temperature-info__single-temp {
  width: 100px;
  display: inline-block;
  margin-right: 30px;
}

Determine the Inner Arrays

First, I created a function that checked if the inner array had numeric temp values. If it did, the function created a new array containing:

  • this inner array's temperatures.
  • their average temperature.
  • their respective city.

If that array didn’t have temperature values, I assumed it was the array that contained strings but no numbers. This would be the array above starting with "City" so it should load onto the page as column headers.

The array of arrays was stored in a constant called temperatureInfo:


const temperatureInfo = [
  ["City", "00-08", "08-16", "16-24", "Average"],
  ["Malmö", 12, 16, 9],
  ["Mariestad", 13, 15, 10],
  ["Stockholm", 13, 15, 13],
  ["Upphärad", 14, 16, 15],
  ["Göteborg", 13, 14, 11]
]

The first function is called formatData(): it’s important to note that running this function against our data is the catalyst for loading all the content onto the page. In other words, when we run formatData(temperatureInfo), it runs other functions that help display and format the content.

formatData() looks like this:


function formatData(outerArray) {

  outerArray.map(innerArray => {

    let numbersOnlyList = []

    innerArray.map(index => {
      if(typeof index === "number") {
        return numbersOnlyList.push(index)
      }
    })

    return numbersOnlyList.length
    ?
    displayTemperatureInfo(numbersOnlyList, innerArray[0])
    :
    displayArrayContent(innerArray, "#temperatureHeader")
  })
}

Breaking all this down…


function formatData(outerArray) {
 ...
}

formatData takes one parameter: outerArray. Eventually, the temperatureInfo const will be the passed parameter.


outerArray.map(innerArray => {
  ...
})

Loop through the array of arrays with the .map() method. The innerArray param will represent one of the single arrays inside temperatureInfo at various times.


let numbersOnlyList = []

Inside this loop, create an empty array that will eventually contains numbers only.


innerArray.map(index => {
  if(typeof index === "number") {
    return numbersOnlyList.push(index)
  }
})

Run another .map() function inside the first loop, which loops over each item in an inner array. If the item is a number, place it inside the numbersOnlyList array, otherwise do nothing.

In other words, when looking at the inner arrays, the index param will look like this at some point:

["Malmö", 12, 16, 9]

And when it does, the innerArray.map() loop will make numbersOnlyList look like this:

[12, 16, 9]

It’s REAAAAAAALY important to note that the index param will also look like this at some point:

["City", "00-08", "08-16", "16-24", "Average"]

But this param has no numbers. Consequently, this will produce an empty numbersOnlyList array, giving it a length of “0”.


return numbersOnlyList.length
?
displayTemperatureInfo(numbersOnlyList, innerArray[0])
:
displayArrayContent(innerArray, "#temperatureHeader")

As seen, numbersOnlyList can have a length, where each of its array items represents a list of temperatures. If it does have a length, pass it as a parameter to the displayTemperatureInfo() function that we haven’t built yet.

We’ll create displayTemperatureInfo() later and discuss in depth. But for now, know that it does the following:

  • calculates the average temperature.
  • creates a new array containing that average temperature, the already-existing temperature list and city name.
  • loads this new array's content onto the page.

displayTemperatureInfo() takes a second param called innerArray[0]: again, innerArray will look like ["Malmö", 12, 16, 9] at some point. We know that the city name is at the beginning of the array, so innerArray[0] points directly to that.

If numbersOnlyList does NOT have a length, we’ll assume that we’re looking at an empty array…created by that inner array starting with "City". In that case, run that array using the displayArrayContent() function that we also haven’t built yet.

displayArrayContent() loads the array content on the page and takes two params: the current innerArray index and a reference to page element where this array content will load. In this instance, that’s the <div id="temperatureHeader" /> element.

The Reducer Helper

The .reduce() method calculates the total sum of an array. We’ll need to do this to get the average temperatures but can’t do it without something called an “accumulator function.”

This accumulator function returns the sum and we’ll create a basic one like this:


function reducerHelper(accumulator, currentValue) {
  return accumulator + currentValue
}

Display the Temperature Information

Again, displayTemperatureInfo() calculates the average temperature, creates a new array with all the temperatures, average temperature and city name, then loads the array content onto the page. It looks like this:


function displayTemperatureInfo(temperatureArray, getCity) {

  const arrayLength = temperatureArray.length
  const getTemperatureSum = temperatureArray.reduce(reducerHelper)
  const temperatureAverage = getTemperatureSum/arrayLength

  temperatureArray.push(Math.round(temperatureAverage))

  temperatureArray.unshift(getCity)

  return displayArrayContent(temperatureArray, "#temperatureInfo")

}

Breaking this one down now…


function displayTemperatureInfo(temperatureArray, getCity) {
 ...
}

It takes two parameters: temperatureArray and getCity. As discussed, the first param is whatever the current value is of numbersOnlyList while the second param is the city name.


const arrayLength = temperatureArray.length
const getTemperatureSum = temperatureArray.reduce(reducerHelper)
const temperatureAverage = getTemperatureSum/arrayLength

The arrayLength const represents the amount of temperatures in the numbersOnlyList. In the case of this code, that value will always be 3.

The getTemperatureSum const represents the sum of those values in the array. We tell the .reduce() method to look at that array, then get this sum by applying our accumulator function to it.

So if that array looks like [12, 16, 9], then getTemperatureSum will equal 31.

The temperatureAverage const represents the array’s average. Basic algebra here: you always determine an average by dividing the combined value of set of numbers by the amount of numbers in the set.

So if that array looks like [12, 16, 9], then temperatureAverage will divide 31 by 3.


temperatureArray.push(Math.round(temperatureAverage))

As mentioned, displayTemperatureInfo() adds this average to array we’re working with. We add it to the end of the array using .push().

temperatureAverage returns the average value as a decimal, so we’ll use Math.round() to convert it to the nearest whole number.


temperatureArray.unshift(getCity)

Also as mentioned, displayTemperatureInfo() adds the city name to array we’re working with. That’s available via the getCity parameter we passed so we’ll add it to the beginning of the array using .unshift().


return displayArrayContent(temperatureArray, "#temperatureInfo")

Using the previously-discussed-but-not-yet-created displayArrayContent() function, we’ll load this new array to the <div id="temperatureInfo" /> element on our page.

Load All the Content Onto the Page

We’re using a displayArrayContent() function to load data onto the page. I guess we should build it now.

This is all basic DOM manipulation and look like this:


function displayArrayContent(arrayContent, target) {

  const getTargetElement = document.querySelector(target)
  const parentElement = document.createElement('div')
  parentElement.setAttribute('class', 'temperature-info__single-temp-row')

  arrayContent.map(index => {
    const childElement = document.createElement('span');
    childElement.setAttribute('class', 'temperature-info__single-temp')
    childElement.innerHTML = index
    parentElement.appendChild(childElement)
  })

  return getTargetElement.appendChild(parentElement)

}

And…breaking this one down…


function displayArrayContent(arrayContent, target) {
  ...
}

The function takes two parameters: arrayContent and target. Because of how we’ve coded stuff, arrayContent always represents one of the arrays we dynamically built using displayTemperatureInfo() while target represents where on the page we want to place it.


const getTargetElement = document.querySelector(target)
const parentElement = document.createElement('div')
parentElement.setAttribute('class', 'temperature-info__single-temp-row')

We create two constants: getTargetElement and parentElement. getTargetElement is a DOM reference to the element we want to load content into (represented by target) while parentElement creates a div tag in memory that we’ll use later.

From there, we give this div tag a class name of temperature-info__single-temp-row to apply some basic styling to it.


arrayContent.map(index => {
  const childElement = document.createElement('span');
  childElement.setAttribute('class', 'temperature-info__single-temp')
  childElement.innerHTML = index
  parentElement.appendChild(childElement)
})

Another .map() function loops over the array and does the following with each array item:

  • creates a span tag.
  • gives the span a class name of temperature-info__single-temp.
  • loads each array item into the span with the help of innerHTML.
  • places the span at the bottom of div we created earlier with the help of the appendChild() method.

return getTargetElement.appendChild(parentElement)

Take our div with all the array content and place it at the bottom of target element which, again, is an element already on our web page.

Run All This Code

As mentioned earlier, formatData() is a catalyst: running this function runs all the code needed to load the temperatureInfo array on the page. So all we need to do is this:


formatData(temperatureInfo)

Neat, huh?

The Final Code

Our final, complete code looks like this:


const temperatureInfo = [
  ["City", "00-08", "08-16", "16-24", "Average"],
  ["Malmö", 12, 16, 9],
  ["Mariestad", 13, 15, 10],
  ["Stockholm", 13, 15, 13],
  ["Upphärad", 14, 16, 15],
  ["Göteborg", 13, 14, 11]
]

function formatData(outerArray) {

  outerArray.map(innerArray => {

    let numbersOnlyList = []

    innerArray.map(index => {
      if(typeof index === "number") {
        return numbersOnlyList.push(index)
      }

    })

    return numbersOnlyList.length
    ?
    displayTemperatureInfo(numbersOnlyList, innerArray[0])
    :
    displayArrayContent(innerArray, "#temperatureHeader")
  })
}

function reducerHelper(accumulator, currentValue) {
  return accumulator + currentValue
}

function displayTemperatureInfo(temperatureArray, getCity) {

  const arrayLength = temperatureArray.length
  const getTemperatureSum = temperatureArray.reduce(reducerHelper)
  const temperatureAverage = getTemperatureSum/arrayLength

  temperatureArray.push(Math.round(temperatureAverage))

  temperatureArray.unshift(getCity)

  return displayArrayContent(temperatureArray, "#temperatureInfo")

}


function displayArrayContent(arrayContent, target) {

  const getTargetElement = document.querySelector(target)
  const parentElement = document.createElement('div')
  parentElement.setAttribute('class', 'temperature-info__single-temp-row')

  arrayContent.map(index => {
    const childElement = document.createElement('span');
    childElement.setAttribute('class', 'temperature-info__single-temp')
    childElement.innerHTML = index
    parentElement.appendChild(childElement)
  })

  return getTargetElement.appendChild(parentElement)

}

formatData(temperatureInfo)

Conclusion

This code has brittle spots:

  • Pointing to the city name using innerArray[0] assumes that the city name will always be the first item in the array...and it may not be. I should do something like use a regular expression to pick out the strings and numbers and then load stuff, or send an error message to the console indicating that the array needs to be properly formatted.
  • Using appendChild() to load in the array data places it at the bottom of the page element. The way I wrote the code displayed the column header data first: it may have loaded second or third if I wrote it a different way. I should've written code that detects that specific array, then prepends its data instead of appends

It also should be said that, based on modern web dev techniques, temperatureInfo would be built with more stricter rules. It would be built using back-end web services and be returned in a JSON format.

But none of that’s the point. The point is that I did some JavaScript functional programming and did a job I’m proud of. The practice is what counts.

Feel free to suggest changes, ask questions, etc.

Would you like to Tweet this page?