Understanding js reduce with Roman Numerals
Tags: javascript, reduce, inject
Published: 2019-09-09 22:22:13

There are several ways to fix new concepts in your head, to use them, to repeat, to combine with new circumstances...

To do so, we will build a Roman numerals to Arabic conversion form, and the corresponding Arabic numerals to roman.

I will take learnings from previous posts and other sources to try to solidify the use of one of my favourite ruby constructs: `reduce` (a.k.a: `inject`), but in its javascript version.

Here are our three sources:

## I)
I will take [this post from @sandimetz](http://www.sandimetz.com/blog/2016/6/9/make-everything-the-same) as a starting point. Please take a moment to read it so you can follow along.

## II)
The IIFE's from [my own post](https://dev.to/oinak/understanding-iife-s-step-by-step-2hd6), to separate conversion logic from interface/behaviour.

## III)
A **very minimalistic** interface by using what we saw on [this other post of mine](https://dev.to/oinak/are-you-sure-you-need-jquery-ej8) about omiting jQuery.

I suggest reading them beforehand, but you might prefer to wait until you feel the _need_ of them as you may already now what is explained in any or all of them.

So, HTML for this is absolutely minimal. I am not going to do steps, bear with me. There are two inputs, identified as _roman_ and _arabic_. Each one of the inputs has an accompanying span, called respectively: _toArabic_ and _toRoman_.

We load to _mysterious_ files `numeral.js` and `conversor.js`, and then an inline script that invokes something called `Oinak.Coversor.init` and passes the id's of the inputs and spans to it. 

```html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Roman Numerals</title>
  </head>
  <body>
    <div>
    Roman:<input name="roman" id="roman"> = <span id="toArabic">
    </div>
    <div>
    Arabic: <input name="arabic" id="arabic"> = <span id="toRoman">
    </div>
    <script src="numeral.js"></script>
    <script src="conversor.js"></script>
    <script>
      Oinak.Conversor.init({
        arabic: '#arabic',
        toArabic: '#toArabic',
        roman: '#roman',
        toRoman: '#toRoman',
      })
    </script>
  </body>
</html>
```
It is _unsurprisingly_ not very spectacular:
![Coversor GUI](https://thepracticaldev.s3.amazonaws.com/i/1er8prno7oig1vzz3tno.png)

The idea is that as you write roman numbers (I, IX, CDVII...) on the `roman`  input, arabic digits appear on the `toArabic` span. On the same spirit, if you input arabic numbers (1, 9, 407...) on the `arabic` input, the `toRoman` span updates with the conversion.

There is no error control, for brevity, but you might want yo add it yourself at the end as an extra-credit exercise :).

## IIFE's and not-jQuery

In the `conversor.js` we have an IIFE like those we talked about on the aforementioned post.

Let's see if from the outside-in:
```js
// namespace
window.Oinak = window.Oinak || {}

window.Oinak.Conversor = ((expose) => {
  // private vars
  let roman, arabic, toRoman, toArabic;

  // auxiliar methods
  observeRoman = () => {...}
  observeArabic = () => {...}

  //public interface
  expose.init = (options) => {
    roman = options.roman;
    arabic = options.arabic;
    toRoman = options.toRoman;
    toArabic = options.toArabic;
    observeRoman();
    observeArabic();
  }
  
  return expose;
})({}) // immediate invocation
```
If you ignore the auxiliar methods, this is just copy & paste & rename from the IIFE's post.

Now, the auxiliar functions are the ones that connect this with the other file. They are almost identical so I will comment (inline) just the first one:

```js
  observeRoman = () => {
    // get the elements as we learnt on the no-jQuery post:
    let source = document.querySelector(roman);    // arabic on the other
    let target = document.querySelector(toArabic); // toRoman on the other

    // observe the event natively:
    source.addEventListener('input', e => {
      let from = e.target.value;

      // call to "something" magic

      let to = Oinak.Numeral.r_to_i(from); // use i_to_r for reverse converison

      // ...and show the result on the span
      target.innerText = to;
    })
  }
```

So far we have seen IIFEs and jQuery-avoidance in action, so you shall be asking: where are my `reduce`s?

## Reduce like there is no tomorrow:

So, first of all, what is `reduce`?

As a simplification, is a function that 
- takes an initial value
- stores it on an accumulator
- iterates over a list (or object, or iterable...) and
- for each item in the list, performs a custom operation(between accumulator and item)
- stores the result as the new value for accumulator
- and finally returns the last value of the accumulator

```js
function reduce(combine, initialValue){
  let accumulator = initialValue;
  for (let item in list) {
    accumulator = combine(accumulator, item);
  }
  return accumulator;
}
```
This pattern is so common, that most modern languages provide it.

[Javascript Array does too now](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#Syntax).

But, as it requires you to hold both the concept of `reduce` itself, and the indirection of a callback, it can be daunting for some people.

In this example, I have purposely avoided the use of anonymous callbacks for `reduce` to try to make it more legible.

I am omitting the explanation from the conversion logic because that's what Sandi's post is about and I am not going to explain anything better than [@sandimetz](https://dev.to/sandimetz) ever, no matter how early I get up in the morning.

Look at these examples of `reduce`, specially the one in `to_roman` which is using a complex accumulator to be able to use **and** modify a second external value from within the callback, without strange hoisting stuff. 

I kept `accumulator` and `reducer` names fixed so it is easier for you to refer to the documentation (linked before) and analyse what each of them is doing.

So, without further ceremony:

```js
window.Oinak = window.Oinak || {}

window.Oinak.Numeral = ((expose) => {
  const ROMAN_NUMERALS = { 
    1000: 'M', 500: 'D', 100: 'C', 50: 'L', 10: 'X', 5: 'V', 1: 'I'
  };

  const LONG_TO_SHORT_MAP = {
    'DCCCC': 'CM', // 900
    'CCCC':  'CD', // 400
    'LXXXX': 'XC', // 90
    'XXXX':  'XL', // 40
    'VIIII': 'IX', // 9
    'IIII':  'IV'  // 4
  };

  to_roman = (number) => {
    const reducer = (accumulator, [value, letter]) => {
      let times = Math.floor(accumulator.remaining / value);
      let rest = accumulator.remaining % value;

      accumulator.remaining = rest;
      accumulator.output += letter.repeat(times); // 30/10 = 'X' 3 times
      
      return accumulator;
    }

    let initialValue = { remaining: number, output: '' };
    let list = Object.entries(ROMAN_NUMERALS).reverse(); // bigger nums first
    let reduction = list.reduce(reducer, initialValue);

    return reduction.output;
  };
  
  to_number = (roman) => {
    let additive = to_additive(roman);
    reducer = (total, letter) => total + parseInt(invert(ROMAN_NUMERALS)[letter]);
    return additive.split('').reduce(reducer, 0);
  }
  
  convert = (map, string) => {
    const reducer = (accumulator, [group, replacement]) => {
      return accumulator.replace(group, replacement)
    }
    return Object.entries(map).reduce(reducer, string);
  }

  // return a new object with values of the original as keys, and keys as values
  invert = (obj) => {
    var new_obj = {};
    for (var prop in obj) {
      if(obj.hasOwnProperty(prop)) {
        new_obj[obj[prop]] = prop;
      }
    }
    return new_obj;
  };

  // to understand the two step conversion, read Sandi's post!
  to_additive = (string) => convert(invert(LONG_TO_SHORT_MAP), string) 
  to_substractive = (string) => convert(LONG_TO_SHORT_MAP, string)

  expose.i_to_r = (number) => to_substractive(to_roman(number))
  expose.r_to_i = (string) => to_number(to_additive(string)) 

  return expose;
})({})
```

That's it, with that you have a Roman to Arabic and an Arabic to Roman number conversion.

I hope you like it. If you want to play with it [you can find it here](https://repl.it/@FernandoMarti10/romannumerals).

Were you using `reduce` already? If that's the case, do you have other interesting examples. If not, do you feel better prepared to use it now?

Tell me in the comments!