We're going to shorten the code from this post. This will be a step-by-step guide, so it's easier for us to know how we get the final code. The final code looks like this:
const windDirections = [
"N", "NNE", "NE", "ENE",
"E", "ESE", "SE", "SSE",
"S", "SSW", "SW", "WSW",
"W", "WNW", "NW", "NNW",
];
export function windDirectionFromDegree(degree) {
degree = (degree + 11.25) % 360;
return windDirections[Math.floor(degree / 22.5)];
}
All the code can be found in this repository. Also check this page to see an illustration of wind direction and degrees.
Here is the code we want to shorten:
if (wind_deg >= 348.75 && wind_deg < 11.25) wind_direction.innerText = "N"
else if (wind_deg >= 11.25 && wind_deg < 33.75) wind_direction.innerText = "NNE";
else if (wind_deg >= 33.75 && wind_deg < 56.25) wind_direction.innerText = "NE";
else if (wind_deg >= 56.25 && wind_deg < 78.75) wind_direction.innerText = "ENE";
else if (wind_deg >= 78.75 && wind_deg < 101.25) wind_direction.innerText = "E";
else if (wind_deg >= 101.25 && wind_deg < 123.75) wind_direction.innerText = "ESE";
else if (wind_deg >= 123.75 && wind_deg < 146.25) wind_direction.innerText = "SE";
else if (wind_deg >= 146.25 && wind_deg < 168.75) wind_direction.innerText = "SSE";
else if (wind_deg >= 168.75 && wind_deg < 191.25) wind_direction.innerText = "S";
else if (wind_deg >= 191.25 && wind_deg < 213.75) wind_direction.innerText = "SSW";
else if (wind_deg >= 213.75 && wind_deg < 236.25) wind_direction.innerText = "SW";
else if (wind_deg >= 236.25 && wind_deg < 258.75) wind_direction.innerText = "WSW";
else if (wind_deg >= 258.75 && wind_deg < 281.25) wind_direction.innerText = "W";
else if (wind_deg >= 281.25 && wind_deg < 303.75) wind_direction.innerText = "WNW";
else if (wind_deg >= 303.75 && wind_deg < 326.25) wind_direction.innerText = "NW";
else if (wind_deg >= 326.25 && wind_deg < 348.75) wind_direction.innerText = "NNW";
See this commit
First let's extract a function windDirectionFromDegree, so the original code can be replaced with:
wind_direction.innerText = windDirectionFromDegree(wind_deg);
The function looks like this:
function windDirectionFromDegree(degree) {
if (degree >= 348.75 && degree < 11.25) return "N"
else if (degree >= 11.25 && degree < 33.75) return "NNE";
else if (degree >= 33.75 && degree < 56.25) return "NE";
else if (degree >= 56.25 && degree < 78.75) return "ENE";
else if (degree >= 78.75 && degree < 101.25) return "E";
else if (degree >= 101.25 && degree < 123.75) return "ESE";
else if (degree >= 123.75 && degree < 146.25) return "SE";
else if (degree >= 146.25 && degree < 168.75) return "SSE";
else if (degree >= 168.75 && degree < 191.25) return "S";
else if (degree >= 191.25 && degree < 213.75) return "SSW";
else if (degree >= 213.75 && degree < 236.25) return "SW";
else if (degree >= 236.25 && degree < 258.75) return "WSW";
else if (degree >= 258.75 && degree < 281.25) return "W";
else if (degree >= 281.25 && degree < 303.75) return "WNW";
else if (degree >= 303.75 && degree < 326.25) return "NW";
else if (degree >= 326.25 && degree < 348.75) return "NNW";
}
Commit is here.
Before we go any further, it would be nice to have a unit test to cover the function:
describe("windDirectionFromDegree", () => {
it("should recognize wind from all degrees", () => {
const windDirections = [
"N", "NNE", "NE", "ENE",
"E", "ESE", "SE", "SSE",
"S", "SSW", "SW", "WSW",
"W", "WNW", "NW", "NNW",
]
const spannedDegreesOfADirection = 360 / windDirections.length;
const spanPrecision = 0.01;
const startingDegree = 360 - spannedDegreesOfADirection / 2;
for (let i = 0; i < windDirections.length; i++) {
for (let j = 0; j < spannedDegreesOfADirection; j += spanPrecision) {
const degree = (startingDegree + i * spannedDegreesOfADirection + j) % 360;
expect(windDirectionFromDegree(degree)).toEqual(windDirections[i])
}
}
})
})
It actually finds a bug in our current implementation, let's fix that:
diff --git a/wind-direction-from-degree.ts b/wind-direction-from-degree.ts
index 8e91eab..bcf3438 100644
--- a/wind-direction-from-degree.ts
+++ b/wind-direction-from-degree.ts
@@ -1,5 +1,5 @@
export function windDirectionFromDegree(degree) {
- if (degree >= 348.75 && degree < 11.25) return "N"
+ if (degree >= 348.75 || degree < 11.25) return "N"
else if (degree >= 11.25 && degree < 33.75) return "NNE";
else if (degree >= 33.75 && degree < 56.25) return "NE";
else if (degree >= 56.25 && degree < 78.75) return "ENE";
Notice we changed the operator from && to || inside if condition.
Let's rotate the degree a little bit so north wind starts from 0 degree instead of 348.75, thus makes the calculation simpler.
Here is the code:
export function windDirectionFromDegree(degree) {
degree = (degree + 11.25) % 360;
if (degree < 22.5) return "N";
else if (degree >= 22.5 && degree < 45) return "NNE";
else if (degree >= 45 && degree < 67.5) return "NE";
else if (degree >= 67.5 && degree < 90) return "ENE";
else if (degree >= 90 && degree < 112.5) return "E";
else if (degree >= 112.5 && degree < 135) return "ESE";
else if (degree >= 135 && degree < 157.5) return "SE";
else if (degree >= 157.5 && degree < 180) return "SSE";
else if (degree >= 180 && degree < 202.5) return "S";
else if (degree >= 202.5 && degree < 225) return "SSW";
else if (degree >= 225 && degree < 247.5) return "SW";
else if (degree >= 247.5 && degree < 270) return "WSW";
else if (degree >= 270 && degree < 292.5) return "W";
else if (degree >= 292.5 && degree < 315) return "WNW";
else if (degree >= 315 && degree < 337.5) return "NW";
else if (degree >= 337.5) return "NNW";
}
Commit is here.
We have some redundant checks in if statements. For example, in the code below:
if (degree < 22.5) return "N";
else if (degree >= 22.5 && degree < 45) return "NNE";
When we reach else if (degree >= 22.5 ..., we can know for sure that degree is always bigger than or equal to 22.5, because if it wasn't, then we'd returned in the previous if, thus the code can be written as:
if (degree < 22.5) return "N";
if (degree < 45) return "NNE";
See the commit here.
Looks like we have a lot of if statements, now it is a good time to apply table-driven methods:
const endingDegrees = [
22.5, 45, 67.5, 90,
112.5, 135, 157.5, 180,
202.5, 225, 247.5, 270,
292.5, 315, 337.5, 360,
];
const windDirections = [
"N", "NNE", "NE", "ENE",
"E", "ESE", "SE", "SSE",
"S", "SSW", "SW", "WSW",
"W", "WNW", "NW", "NNW",
];
for (let i = 0; i < endingDegrees.length; i++) {
if (degree < endingDegrees[i]) return windDirections[i];
}
Here we use a for loop to check conditions one by one. The logic is the same as the bunch of if statements, but when we need to add more conditions, we just need to change endingDegrees and windDirections, no need to add more if statements.
Commit is here.
We can also combine the two arrays into one array of objects.
We can use binary search to replace the for loop, so it is a little bit faster:
const i = binarySearchFindHeadOfSecondHalf(endingDegrees.length, i => {
return degree < endingDegrees[i];
});
return windDirections[i];
See this commit.
Actually, since each slot has the same size, we can calculate the index directly:
const i = Math.floor(degree / 22.5);
Commit is here.
So far we've shortened the code to a reasonably concise version. More refactor commits can be found in this repository. They're not included here because those don't shorten the code.
Table-Driven is a good way to solve this kind of problem, and further optimizations are based on that.