# Comparing Two Temperatures

It doesn't matter what temperature the room is — it's always room temperature.

Steven Wright

We're at work, and we are tasked with writing two functions that compare any two temperatures:

• One that gets the minimum value
• One that gets the maximum value

We're not sure why they don't use `Math.min` and `Math.max` themselves, but we do what they ask.

``````const minTemperature = (a, b) => Math.min(a, b)
const maxTemperature = (a, b) => Math.max(a, b)

minTemperature(100, 200) // 100
maxTemperature(100, 200) // 200
``````

Once we share that solution, we are reminded that task is to compare any two temperatures; for example, `100`, `200`, `'50F'`, `'25C'`.

Dangit... now we look silly. Okay, let's try this again to make it handle strings:

``````const minTemperature = (a, b) => a > b ? b : a
const maxTemperature = (a, b) => a < b ? b : a

minTemperature(100, 200)       // 100
maxTemperature(100, 200)       // 200
minTemperature('200F', '100F') // '100F'
maxTemperature('200F', '100F') // '200F'
``````

Then we realize that Ramda has `min` and `max` functions, so we use those and pat ourselves on the back:

``````import { max, min } from 'ramda'

min(100, 200)       // 100
max(100, 200)       // 200
min('200F', '100F') // '100F'
max('200F', '100F') // '200F'
``````

We go back and tell our team to use Ramda's functions, but we're wrong again, and we realize that we probably should have asked for a thorough list of inputs.

But what could we have missed?

Compare any two temperatures.

Oh, no... They might not be part of the same measurement system! This means that `'50F'` could be compared with `'15C'`! Luckily, we're told that we don't have to worry about Kelvin, Rankine, nor Réaumur; we only care about Celsius and Fahrenheit. There will also never be any unit-less numbers, so we can ignore that case.

This problem just took on a whole new dimension!

Good news: in the last chapter, we wrote our `celsiusToFahrenheit` and `fahrenheitToCelsius` functions, so we have those functions we can use to do those calculations for us.

Let's try again:

``````import { max, min } from 'ramda'

const celsiusToFahrenheit =

const minTemperature = (a, b) => {
const unitA = a.slice(-1) // get the last character, like C or F
const unitB = b.slice(-1)
const floatA = parseFloat(a)
const floatB = parseFloat(b)

// same temperature type
if (unitA === unitB) {
const maxT = min(floatA, floatB)
return maxT === floatA ? a : b
}

// a is Fahrenheit but b is Celsius
if (unitA === 'F') {
const floatBAsF = celsiusToFahrenheit(floatB)
const minF = min(floatA, floatBAsF)

return minF === floatA ? a : b
}

// a is Celsius but b is Fahrenheit
const floatAAsF = celsiusToFahrenheit(floatA)
const minF = min(floatAAsF, floatB)

return minF === floatB ? b : a
}

const maxTemperature = (a, b) => {
const unitA = a.slice(-1) // get the last character, like C or F
const unitB = b.slice(-1)
const floatA = parseFloat(a)
const floatB = parseFloat(b)

// same temperature type
if (unitA === unitB) {
const maxT = max(floatA, floatB)
return maxT === floatA ? a : b
}

// a is Fahrenheit but b is Celsius
if (unitA === 'F') {
const floatBAsF = celsiusToFahrenheit(floatB)
const maxF = max(floatA, floatBAsF)

return maxF === floatA ? a : b
}

// a is Celsius but b is Fahrenheit
const floatAAsF = celsiusToFahrenheit(floatA)
const maxF = max(floatAAsF, floatB)

return maxF === floatB ? b : a
}

minTemperature('200F', '100F') // '100F'
maxTemperature('200F', '100F') // '200F'
minTemperature('50F', '25C')   // '50F'
maxTemperature('50F', '25C')   // '25C'
``````

Goodness gracious! While we could extract some common functionality into many small functions, there must be a simpler way. After all, isn't functional programming supposed to help us simplify things?

(Hint: yes!)

Ramda has `minBy` and `maxBy` functions that will compare two values after they have been transformed by some transformation function. Here's their example from the docs:

``````import { maxBy } from 'ramda'

const square = n => n * n
maxBy(square, -3, 2) // -3
``````

In this example, `maxBy` will call `square` with `-3` and `2`, and it will then compare each of those results. Whatever value has the largest result after being applied to `square` will be the returned value. Here, `-3 * -3` is `9`, whereas `2 * 2` is `4`, so since `9 > 4`, `-3` is our result.

Let's refactor our functions:

``````import { maxBy, minBy } from 'ramda'

const asF = x => {
if (x.slice(-1) === 'C') {
return celsiusToFahrenheit(parseFloat(x))
}

return x
}

const minTemperature = (a, b) => minBy(asF, a, b)
const maxTemperature = (a, b) => maxBy(asF, a, b)

minTemperature('200F', '100F') // '100F'
maxTemperature('200F', '100F') // '200F'
minTemperature('50F', '25C')   // '50F'
maxTemperature('50F', '25C')   // '25C'
maxTemperature('50C', '25C')   // '50C'
``````

By casting all temperatures as a single unit (I chose Fahrenheit here), we can compare any temperatures and get back their original values! `25C` is indeed hotter than `50F`!

But wait – there's more.

Just like we noticed in the last chapter, our `minTemperature` and `maxTemperature` functions are taking in `(a, b)` and are merely forwarding those arguments on. If you recall, Ramda doesn't care when you provide arguments, so check this out...

``````// before
const minTemperature = (a, b) => minBy(asF, a, b)
const maxTemperature = (a, b) => maxBy(asF, a, b)

// after
const minTemperature = minBy(asF)
const maxTemperature = maxBy(asF)
``````

Oh, and one more thing; here's a preview of some upcoming chapters' content retroactively applied to making our `asF` function cleaner:

``````// before
const asF = x => {
if (x.slice(-1) === 'C') {
return celsiusToFahrenheit(parseFloat(x))
}

return x
}

// after

// Celsius     = String // "100C"
// Fahrenheit  = String // "100F"
// Temperature = Celsius | Fahrenheit

// isC :: Temperature -> Bool
const isC = compose(equals('C'), slice(-1))

// stringCToF :: Celsius -> Number
const stringCToF = compose(celsiusToFahrenheit, parseFloat)

// asF :: Temperature -> Fahrenheit
const asF = when(isC, stringCToF)
``````

And here is what all of the new functions together could look like!

``````// Celsius     = String // "100C"
// Fahrenheit  = String // "100F"
// Temperature = Celsius | Fahrenheit

// isC :: Temperature -> Bool
const isC = compose(equals('C'), slice(-1))

// stringCToF :: Celsius -> Number
const stringCToF = compose(celsiusToFahrenheit, parseFloat)

// asF :: Temperature -> Fahrenheit
const asF = when(isC, stringCToF)

// minTemperature :: Temperature -> Temperature -> Temperature
const minTemperature = minBy(asF)

// maxTemperature :: Temperature -> Temperature -> Temperature
const maxTemperature = maxBy(asF)
``````

Cool, right?

Fear not! We'll cover `when` in the "Unshakeable Logic" section, `slice` in the "All About ['L','i','s','t','s']" section, and the pseudo-type signatures in the "Core Ramda Ideas" section, but you should know the pseudo-types are optional, not exact, and aren't a substitute for tools like jsdoc.

## Wrapping Up

The person who requested these functions is going to be blown away!

What started as a simple "which temperature is smaller or larger?" question turned out to be an exercise in asking good questions about requirements up front.

We also walked through implementing a naïve solution and refactored it to a more elegant one by leveraging Ramda and functional programming.