Display Temperature Averages with JavaScript Functional Programming

By Kai Gittens tags:  reading time: 7 min
image for the '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:

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:

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:

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:

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

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#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:


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:

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.