Display Temperature Averages with JavaScript Functional Programming

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
- The Challenge
- The Solution
- The Basic Code
- Determine the Inner Arrays
- The Reducer Helper
- Display the Temperature Information
- Load All the Content Onto the Page
- The Final Code
- 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 oftemperature-info__single-temp
. - loads each array item into the
span
with the help ofinnerHTML
. - places the
span
at the bottom ofdiv
we created earlier with the help of theappendChild()
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.