0

I've created a simple function that is supposed to translate a number into a Roman Numeral. It appears that everything is working properly except one thing.

Through every recursion in my code, the string that holds the roman numerals is reset to "".

What is the best practice for maintaining variables like this through recursions in a function?

I tried declaring variable romanStr = "" in the global scope, and removing the conditional declaration within the program, and of course that worked. I however know that is the worst practice.

For example, take the number 1234, which converted into roman numerals is "MCCXXXIV". My program will only return "IV", the result of the last recursion.

function convertToRoman(num) {
  console.log(`START FUNCTION FROM THE BEGINNING`);
  console.log(`current num: ${num}`);
  if (typeof romanStr === "undefined") {
    var romanStr = "";
  }
  const bNumbers = [1000, 500, 100, 50, 10, 5, 1];
  const romanSymbols = {
    0: ["M"],
    2: ["C", "D", "M"],
    4: ["X", "L", "C"],
    6: ["I", "V", "X"]
  };
  const arraySelector = arrNum => num >= arrNum;
  let symbolSetIndex = bNumbers.findIndex(arraySelector);
  console.log(`symbolSetIndex: ${symbolSetIndex}`);
  let symbolSet = romanSymbols[symbolSetIndex];
  console.log(`symbolSet: [${symbolSet}]`);
  let numString = num.toString();
  let numeral = parseInt(numString[0]);
  console.log(`numeral: ${numeral}`);
  let nextNum = parseInt(numString.substr(1));
  console.log(`nextNum: ${nextNum}`);

  // CONDITIONAL STATEMENTS //
  if (symbolSetIndex === 0) {
    for (let i = 1; i <= numeral; i++) {
      romanStr = `${romanStr}${symbolSet[0]}`;
    }
    return convertToRoman(nextNum);
  }
  if (numeral < 4) {
    for (let i = 1; i <= numeral; i++) {
      romanStr = `${romanStr}${symbolSet[0]}`;
    }
  }
  if (numeral === 4) {
    romanStr = `${romanStr}${symbolSet[0]}${symbolSet[1]}`;
  }
  if (numeral === 5) {
    romanStr = `${romanStr}${symbolSet[1]}`;
  }
  if (numeral === 6) {
    romanStr = `${romanStr}${symbolSet[1]}${symbolSet[0]}`;
  }
  if (numeral > 6) {
    romanStr = `${romanStr}${symbolSet[1]}`; // requires the 5 numeral first
    for (let i = 1; i <= numeral - 6; i++) {
      romanStr = `${romanStr}${symbolSet[0]}`;
    }
  }
  if (numeral === 9) {
    romanStr = `${romanStr}${symbolSet[2]}${symbolSet[1]}`;
  }

  if (numString.length === 1) {
    return romanStr;
  }
  return convertToRoman(nextNum);
}

console.log(convertToRoman(5214));

1
  • Why not pass the ongoing value as a parameter to the next call? Commented Mar 30, 2018 at 23:52

2 Answers 2

2

You don't need to keep the variable through all the calls. Concatenate the current value to the value returned by the recursion.

function convertToRoman(num) {
  console.log(`START FUNCTION FROM THE BEGINNING`);
  console.log(`current num: ${num}`);
  var romanStr;
  const bNumbers = [1000, 500, 100, 50, 10, 5, 1];
  const romanSymbols = {
    0: ["M"],
    2: ["C", "D", "M"],
    4: ["X", "L", "C"],
    6: ["I", "V", "X"]
  };
  const arraySelector = arrNum => num >= arrNum;
  let symbolSetIndex = bNumbers.findIndex(arraySelector);
  console.log(`symbolSetIndex: ${symbolSetIndex}`);
  let symbolSet = romanSymbols[symbolSetIndex];
  console.log(`symbolSet: [${symbolSet}]`);
  let numString = num.toString();
  let numeral = parseInt(numString[0]);
  console.log(`numeral: ${numeral}`);
  let nextNum = parseInt(numString.substr(1));
  console.log(`nextNum: ${nextNum}`);

  // CONDITIONAL STATEMENTS //
  if (symbolSetIndex === 0) {
    for (let i = 1; i <= numeral; i++) {
      romanStr = `${romanStr}${symbolSet[0]}`;
    }
    return romanStr + convertToRoman(nextNum);
  }
  if (numeral < 4) {
    for (let i = 1; i <= numeral; i++) {
      romanStr = `${romanStr}${symbolSet[0]}`;
    }
  }
  if (numeral === 4) {
    romanStr = `${romanStr}${symbolSet[0]}${symbolSet[1]}`;
  }
  if (numeral === 5) {
    romanStr = `${romanStr}${symbolSet[1]}`;
  }
  if (numeral === 6) {
    romanStr = `${romanStr}${symbolSet[1]}${symbolSet[0]}`;
  }
  if (numeral > 6) {
    romanStr = `${romanStr}${symbolSet[1]}`; // requires the 5 numeral first
    for (let i = 1; i <= numeral - 6; i++) {
      romanStr = `${romanStr}${symbolSet[0]}`;
    }
  }
  if (numeral === 9) {
    romanStr = `${romanStr}${symbolSet[2]}${symbolSet[1]}`;
  }

  if (numString.length === 1) {
    return romanStr;
  }
  return romanStr + convertToRoman(nextNum);
}

console.log(convertToRoman(5214));

Sign up to request clarification or add additional context in comments.

3 Comments

This is a great solution. No need to create an entirely new wrapper- just return the string concatenated with the function call. This is one of those solutions where I don't know "HOW" or "WHY" it works, just that it works. Could you please explain why concatenating the string works?
If the number is 532, you calculate the roman numeral for 500, with is D. Then you call it recursively for 32, which returns XXXII. The result you want is the concatenation of them, DXXXII.
This is always how you should think about recursion: How do I simplify the problem, and how do I combine the current case with the result of the simpler problem.
1

Recursion is a functional heritage and so using it with functional style will yield the best results. The Roman Numeral algorithm was one of those things that really blew my mind when I first saw it expressed in functional style: CodeReview.SE: Converting to Roman Numerals.

@sdcvvc provides a beautiful encoding

toRoman :: Integer -> String
toRoman 0 = "N"
toRoman x | x > 0 = snd $ foldl f (x,[]) convMap
  where f (n,s) (rn, rs) = (l, s ++ concat (genericReplicate k rs))
              where (k,l) = divMod n rn

The simplicity is truly astonishing. I cannot take credit for any part of the algorithm provided above, but I can translate it into JavaScript for you.

I share this because it shows you a way to approach your problem from a completely different angle. The other answers provided on the CodeReview thread provide even further insight. I highly encourage you to check them out :D

const divMod = (n, d, k) =>
  k (n / d >> 0, n % d)

const foldl = (f, init, xs) =>
  xs.reduce (f, init)

const replicate = (n, s) =>
  s.repeat (n)

const snd = ([ _, x ]) =>
  x

const convMap =
  [ [1000,"M"], [900,"CM"], [500,"D"], [400,"CD"], [100,"C"]
  , [90,"XC"], [50,"L"], [40,"XL"], [10,"X"], [9,"IX"], [5,"V"]
  , [4,"IV"], [1,"I"]
  ]

const toRoman = (x = 0) =>
  x === 0
    ? "N"
    : snd ( foldl ( ([ n, s ], [ rn, rs ]) =>
                      divMod (n, rn, (k, l) =>
                        [ l, s + replicate (k, rs) ])
                  , [ x, [] ]
                  , convMap
                  )
          )

console.log
  ( toRoman (0)       // N
  , toRoman (7)       // VII
  , toRoman (66)      // LXVI
  , toRoman (99)      // XCIX
  , toRoman (1984)    // MCMLXXXIV
  )

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.