Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[B02]阿拉伯数字转换为罗马数字 | JavaScript 算法实现 #34

Open
hylerrix opened this issue Sep 19, 2017 · 0 comments
Open

Comments

@hylerrix
Copy link
Owner

hylerrix commented Sep 19, 2017

昨日,在 FCC 平台整整用了两三小时,才刷出一道 JS 算法题,回首而看,最终的代码也就那么多行,记录过程,写文以促进之后改进,也顺便整理一下自己在 FCC 上遇到问题后的解决思路。

阿拉伯数字转换为罗马数字 -- 做题传送门,来试试~

想看答案的可以直接拉至文章后方~~


数字转换规则


罗马数字我们都不陌生,IIIVIX 等字符见文知意,可是当阿拉伯数字达到百千级别的时候,用什么字母表示?字母的排列顺序又应该是什么样子的呢?下面诺列了几个规则:

基本字符

I、V、X、L、C、D、M

分别代表

1、5、10、50、100、500、1000

计数规则:

  • 若干相同数字连写表示的数是这些罗马数字的和,如III=3;
  • 小数字在大数字前面表示的数是用大数字减去小数字,如IV=4;
  • 小数字在大数字后面表示的数是用大数字加上小数字,如VI=6;

组合规则:

  • 基本数字Ⅰ、X 、C 中的任何一个,自身连用构成数目,或者放在大数的右边连用构成数目,都不能超过三个;放在大数的左边只能用一个。
  • 不能把基本数字 V 、L 、D 中的任何一个作为小数放在大数的左边采用相减的方法构成数目;放在大数的右边采用相加的方式构成数目,只能使用一个。
  • V 和 X 左边的小数字只能用 Ⅰ。
  • L 和 C 左边的小数字只能用 X。
  • D 和 M 左 边的小数字只能用 C 。

可见该字符只适用于 4000 以下的正整数阿拉伯数字。


思路历程


按照规则来说,看来也就是要满足类似如下输入和输出:

首先想到的是找不同字符:最终也就只有 I、V、X、L、C、D、M 这七个字符分别代表几个数字,进行一定的左右顺序排列后完成转换。那么在这里首先要生成一个装有七个元素的数组,这时输入 9 的话,去拼装 I 和 X,输入 97 的话,去拼装 L 或者 C 等等。

var Roman = {
  1: 'I',
  5: 'V',
  10: 'X',
  50: 'L',
  100: 'C',
  500: 'D',
  1000: 'M'
};

就像 97 ,选择从 L (50)向右拼装呢还是选择 C (100)向左拼装,看来要去找输入数字左右两侧有对应罗马字符的数字,例如 97 的左边有代表 'L' 的 50 ,右边有代表 'C' 的 100。

开始设计算法,处处断点跟踪。

function convert(num) {
    if (isNaN(num)) return num;
    var str = "";
    var left, right; // 找到输入数字的左值和右值
    var p;
    for (p in Roman) {
      if (num > p) {
        left = p; // 左边的值一直在跟踪
      } else {
        right = p; // 知道左边的值找到后,左边的值得下一个赋给右边的值
        break;
      }
    }
    console.log("(left: " + left + ")----" + num + "----(right: " + right + ")");
    return str;
}

断点跟踪情况来看。发现确实能找到输入数字左右两边的值。


变得更复杂了,及时收手,重新思考


哇塞,找到输入数字左右两个数字后呢?在之前的转换规则里有说过,最大的罗马数字(例如‘V’),左侧的数字(例如‘I’)代表减少,右侧的数字代表(例如‘II’)增加。这时,罗马数字‘IV’、‘VII’即为阿拉伯数字 4 和 7 了。

当这时只用单个字符左右拼装 3999 的时候,一切都复杂了。

3999 --> MMMCMXCIX

要判断 9 应该转换为 VIIII 还是 IX ,必然会多出很多选择循环分支,变得更复杂了,及时收手,重新查找规律。

原来,像 97 这样的数字,不必纠结于是从 L(50) 开始拼装还是从 C(100) 开始拼装,90 转换成 XC ,7 转换成 VII 即可。看了看别的数字,都是这个规律呢,于是制定出如下对应表,直接查找到 个十百千 的任何一个。

var Roman = [["","I","II","III","IV","V","VI","VII","VIII","IX"],
             ["","X","XX","XXX","XL","L","LX","LXX","LXXX","XC"],
             ["","C","CC","CCC","CD","D","DC","DCC","DCCC","CM"],
             ["","M","MM","MMM","  "," ","  ","   ","    ","  "]];

3999 就可以理解为:3000 -> MMM, 900 -> CM, 90 -> XC, 9 -> IX 了。

3999 --> MMMCMXCIX


最终算法实现


其实你只用从这里正式看就可以了,前面的情景铺设不利于直接解答你的困惑呢。最终实现功能的函数才只有短短的 8 行。

  • ReverseArr 数组用来将输入数字转换成字符数组后倒序保存,这样 ReverseArr[0] 永远是输入数字的个位数,便于处理。
  • CorrectArr 数组用来将每个 ReverseArr 数组的数字查表转换成对应的罗马数字。
  • 最终返回 CorrectArr 数组经过 join 方法后连接起来的字符串。
var Roman = [["","I","II","III","IV","V","VI","VII","VIII","IX"],
             ["","X","XX","XXX","XL","L","LX","LXX","LXXX","XC"],
             ["","C","CC","CCC","CD","D","DC","DCC","DCCC","CM"],
             ["","M","MM","MMM","  "," ","  ","   ","    ","  "]];

// 只能转换 4000 以下的正整数阿拉伯数字
function convert(num) {
    if(isNaN(num)) return num;
    var ReverseArr = num.toString().split("").reverse();
    var CorrectArr = [];
    for (var i = 0; i < ReverseArr.length; i++) {
      CorrectArr.unshift(Roman[i][ReverseArr[i]]);
    }
    return CorrectArr.join("");
}

俩小时后的这一瞬间,感动死我了思密达:


�整理自己的思考过程


  • 查看 MDN 文档(重要),MDN 文档由网上所有人参与编辑维护,里面有几乎全部的 JS 对象及其方法的使用用例。
  • 随时 Chrome 开发者工具测试 demo 和调试,处处 console.log “人肉”看状态。
  • 搜索引擎 -- 百度 or 谷歌(辅助),算法这块,到瓶颈之处真不好意思一直不查资料想下去。
  • FCC 微信群,记得“提问的智慧”哦,人人都很忙呢。
@hylerrix hylerrix changed the title [B0E]中国首届开发者关系大会分会场 | freeCodeCamp西安线下编程活动体验#06 [B02]阿拉伯数字转换为罗马数字 | JavaScript 算法实现 Sep 20, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant