创建一个罗马数字计算器


18

创建一个用于罗马数字的基本计算器。

要求

  • 支持+-*/
  • 输入和输出应该期望只有一个每个符号减法前缀(即3不能IIV因为有两个I年代以前V
  • 处理的在最小支持现代标准约定的输入和输出必须减法原则,其中只有十的权力是由较大的数字相减(例如IXC需要减法而不是VLD)和减法不会从一些做多减法器的10倍(例如,IX必须支持但IC不是必需的)。
  • 输入和输出应按值的顺序从左到右,从最大值开始(即19 = XIXnot IXX,10大于9)
  • 从左到右,没有操作员优先级,就好像您在使用手持计算器一样。
  • 支持1-4999之间的整个正数输入/输出(不需要V̅)
  • 没有可以为您进行罗马数字转换的库

由您决定

  • 区分大小写
  • 输入上有空格或没有空格
  • 如果获得十进制输出会发生什么。截断,无答案,错误等。
  • 对您无法处理的输出该怎么办。负数或数字要大。
  • 是否支持比最低要求更宽松地使用减法原理。

额外信用

  • -50-最多可处理99999。符号中必须包含外包装

样本输入/输出

XIX + LXXX                 (19+80)
XCIX

XCIX + I / L * D + IV      (99+1/50*500+4)
MIV

最短的代码获胜。


(99 + 1/50 * 500 + 4)=(99 + 10 + 4)= 113,但是您的样本输入/输出说它是MIV(1004)。
Victor Stafusa 2014年

1
@Victor-严格的从左到右操作-没有优先级规则-因此99 +1 / 50 * 500 + 4应计算为(((((99 + 1)/ 50)* 500)+ 4)

是否IM = 999需要处理数字?
肯德尔·弗雷

@KendallFrey我希望您可以输入IM。输出是IM还是CMXCIX999取决于您。两者都符合要求。
丹尼

2
IM对于现代罗马数字用法是非标准的。通常,通过减法运算仅是每个数量级(4、9、40、90、400、900等)的4s和9s。对于1999年,MCMXCIX将是标准的,而不是MIM ...观看当年的任何电影的片酬。否则,它在哪里结束?我们是否还希望支持其他非标准减法,例如VL减45?在C上带有小柱的IC是否必须被支持为99999作为奖金?
乔纳森·范·马特雷

Answers:


9

JavaScript(ES6),238

c=s=>{X={M:1e3,CM:900,D:500,CD:400,C:100,XC:90,L:50,XL:40,X:10,IX:9,V:5,IV:4,I:1}
n=eval('W='+s.replace(/[\w]+/g,n=>(o=0,n.replace(/[MDLV]|C[MD]?|X[CL]?|I[XV]?/g,d=>o+=X[d]),
o+';W=W')));o='';for(i in X)while(n>=X[i])o+=i,n-=X[i];return o}

用法:

c("XIX + LXXX")
> "XCIX"
c('XCIX + I / L * D + IV')
> "MIV"

带注释的版本:

/**
 * Process basic calculation for roman numerals.
 * 
 * @param {String} s The calculation to perform
 * @return {String} The result in roman numerals
 */
c = s => {
  // Create a lookup table.
  X = {
    M: 1e3, CM: 900, D: 500, CD: 400, C: 100, XC: 90, 
    L: 50,  XL: 40,  X: 10,  IX: 9,   V: 5,   IV: 4, I: 1
  };
  // Do the calculation.
  // 
  // The evaluated string is instrumented to as below:
  //   99+1/50*500+4 -> W=99;W=W+1;W=W/50;W=W*500;W=W+4;W=W
  //                 -> 1004
  n = eval('W=' + s.replace(
    // Match all roman numerals.
    /[\w]+/g,
    // Convert the roman number into an integer.
    n => (
      o = 0,
      n.replace(
        /[MDLV]|C[MD]?|X[CL]?|I[XV]?/g,
        d => o += X[d]
      ),
      // Instrument number to operate left-side operations.
      o + ';W=W'
    )
  ));

  // Convert the result into roman numerals.
  o = '';
  for (i in X)
    while (n >= X[i])
      o += i,
      n -= X[i];

  // Return calculation result.
  return o
}

9

T-SQL,1974年-50 = 1924字节

我知道在SQL中打高尔夫球等同于打18个洞,只不过是一个沙坑,但是我很喜欢这个挑战,而且我认为我在方法上做了一些有趣的事情。

这确实为输入和输出提供了支持。我采用了使用尾随波浪号来表示它的约定,因此V〜为5000,X〜为10000,依此类推。根据标准的现代罗马数字用法,它还应该处理高达399,999的输出。之后,它将对INT支持范围内的任何内容进行部分非标准的罗马编码。

因为都是整数运算,所以任何非整数结果都会隐式舍入。

DECLARE @i VARCHAR(MAX)
SET @i='I+V*IV+IX*MXLVII+X~C~DCCVI'
SELECT @i

DECLARE @t TABLE(i INT IDENTITY,n VARCHAR(4),v INT)
DECLARE @u TABLE(n VARCHAR(50),v INT)
DECLARE @o TABLE(n INT IDENTITY,v CHAR(1))
DECLARE @r TABLE(n INT IDENTITY,v INT,r VARCHAR(MAX))
DECLARE @s TABLE(v INT,s VARCHAR(MAX))
DECLARE @p INT,@x VARCHAR(4000)='SELECT ',@j INT=1,@m INT,@y INT,@z VARCHAR(2),@q VARCHAR(50)='+-/*~]%'
INSERT @t(n,v) VALUES('i',1),('iv',4),('v',5),('ix',9),('x',10),('xl',50),('l',50),('xc',90),('c',100),('cd',400),('d',500),('cm',900),('m',1000),('mv~',4000),('v~',5000),('mx~',9000),('x~',10000),('x~l~',40000),('l~',50000),('x~c~',90000),('c~',100000)
INSERT @u VALUES('%i[^i'+@q,-2),('%v[^vi'+@q,-10),('%x[^xvi'+@q,-20),('%l[^lxvi'+@q,-100),('%c[^clxvi'+@q,-200),('%d[^dclxvi'+@q,-1000),('%mx~%',-2010),('%x~l~%',-20060),('%x~c~%',-20110)
WHILE PATINDEX('%[+-/*]%', @i)!=0
BEGIN
    SET @p=PATINDEX('%[+-/*]%', @i)
    INSERT @o(v) SELECT SUBSTRING(@i,@p,1)
    INSERT @r(r) SELECT SUBSTRING(@i,1,@p-1)
    SET @i=STUFF(@i,1,@p,'')
END 
INSERT @r(r) SELECT @i
UPDATE r SET v=COALESCE(q.v,0) FROM @r r LEFT JOIN (SELECT r.r,SUM(u.v)v FROM @u u JOIN @r r ON r.r LIKE u.n GROUP BY r.r)q ON q.r=r.r
UPDATE r SET v=r.v+q.v FROM @r r JOIN (SELECT r.n,r.r,SUM((LEN(r.r)-LEN(REPLACE(r.r,t.n,REPLICATE(' ',LEN(t.n)-1))))*t.v) v FROM @r r JOIN @t t ON CHARINDEX(t.n,r.r) != 0 AND (LEN(t.n)=1 OR (LEN(t.n)=2 AND RIGHT(t.n,1)='~')) GROUP BY r.n,r.r) q ON q.r=r.r AND q.n = r.n
SELECT @m=MAX(n) FROM @o
SELECT @x=@x+REPLICATE('(',@m)+CAST(v AS VARCHAR) FROM @r WHERE n=1
WHILE @j<=@m
BEGIN
    SELECT @x=@x+o.v+CAST(r.v AS VARCHAR)+')'
    FROM @o o JOIN @r r ON r.n=o.n+1 WHERE o.n=@j
    SET @j=@j+1
END 
INSERT @s(v,s) EXEC(@x+',''''')
UPDATE @s SET s=s+CAST(v AS VARCHAR(MAX))+' = '
SET @j=21
WHILE @j>0
BEGIN
    SELECT @y=v,@z=n FROM @t WHERE i = @j
    WHILE @y<=(SELECT v FROM @s)
    BEGIN
        UPDATE @s SET v=v-@y,s=s+@z
    END  
    SET @j=@j-1
END
SELECT @x+' = '+UPPER(s) FROM @s

我仍然在修补基于集合的解决方案,以替换一些WHILE循环,这可能会减少字节数,并且是惯用SQL的一个更优雅的示例。通过将表别名的使用减少到最低限度,还可以获得一些字节。但是由于用这种语言基本上无法取胜,所以我主要是在这里炫耀我的唐吉x德服装。:)

顶部的SELECT @i重复输入:

I+V*IV+IX*MXLVII+X~C~DCCVI

最后的SELECT返回:

SELECT (((((1+5)*4)+9)*1047)+90706) = 125257 = C~X~X~V~CCLVII

您可以在此SQLFiddle中自己进行测试

我将返回关于其工作原理的评论,因为如果您不打算将其用于教育价值,为什么还要发布一个明显失败的答案?


2

Javascript- 482476个字符

String.prototype.m=String.prototype.replace;eval("function r(a){return a>999?'Mk1e3j899?'CMk900j499?'Dk500j399?'CDk400j99?'Ck100j89?'XCk90j49?'Lk50j39?'XLk40j9?'Xk10j8?'IX':a>4?'Vk5j3?'IV':a>0?'Ik1):''}".m(/k/g,"'+r(a-").m(/j/g,"):a>"));s=prompt();h=s.match(/\w+/gi);for(k in h)s=s.m(h[k],eval(eval("'0'+h[k].m(/IVu4pIXu9pXLu40pXCu90pCDu400pCMu900pMu1000pDu500pCu100pLu50pXu10pVu5pIu1')".m(/u/g,"/g,'+").m(/p/g,"').m(/")))+")");for(k in h)s="("+s;alert(r(Math.floor(eval(s))))

示例输入/输出工作原理:

XIX + LXXX -> XCIX
XCIX + I / L * D + IV -> MIV

它也处理大量数字:

MMM+MMM -> MMMMMM
M*C -> MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM

它也接受但不要求空格。

但是,自从我打高尔夫球以来,它存在一些问题:

  • 它不会验证输入的格式是否正确。如果输入的格式不正确,则行为是不确定的(实际上,这是非常奇怪和奇怪的)。
  • 它会截断输出上的分数数字(但可以使用它们进行中间计算)。
  • 它确实滥用了评估功能。
  • 它不会尝试处理负数。
  • 区分大小写。

这种替代版本处理数字超过5000高达99999,但它有600个 598 584字符:

String.prototype.m=String.prototype.replace;eval("function r(a){return a>8zz?'XqCqk9e4j4zz?'Lqk5e4j3zz?'XqLqk4e4jzz?'Xqk1e4j89z?'IqXqk9e3j49z?'Vqk5e3j9z?'Mk1e3j8z?'CMk900j4z?'Dk500j3z?'CDk400jz?'Ck100j89?'XCk90j49?'Lk50j39?'XLk40j9?'Xk10j8?'IX':a>4?'Vk5j3?'IV':a>0?'Ik1):''}".m(/k/g,"'+r(a-").m(/j/g,"):a>").m(/q/g,"\u0305").m(/z/g,"99"));s=prompt();h=s.match(/\w+/gi);for(k in h)s=s.m(h[k],eval(eval("'0'+h[k].m(/IVu4pIXu9pXLu40pXCu90pCDu400pCMu900pMu1000pDu500pCu100pLu50pXu10pVu5pIu1')".m(/u/g,"/g,'+").m(/p/g,"').m(/")))+")");for(k in h)s="("+s;console.log(r(Math.floor(eval(s))))

我不认为在-20适用:看到钮带
SeanC

在这里同意@SeanCheshire。对于较大的数字处理,其目的是在数字上添加一个Vinculum,使其等于通常值的1000倍。也许它应该大于-20,所以值得人们尝试。
丹尼

1
@Danny我添加了一个处理vinculus的版本,但是它增加了116个字符的代码。
Victor Stafusa 2014年

2

使用Javascript 479 361 348 278 253

303个字符-50个字符,最多支持100万个数字,并带有vinculum支持:

function p(s){s=s[r](/(^|[*\/+-])/g,"0;s$1=");for(i in v){f=R("\\b"+i);while(f.test(s))s=s[r](f,v[i]+"+")}eval(s+"0");h="";for(i in v)while(s>=v[i]){h+=i;s-=v[i]}return h}v={M̅:1e6,D̅:5e5,C̅:1e5,L̅:5e4,X̅:1e4,V̅:5e3,M:1e3,CM:900,D:500,CD:400,C:100,XC:90,L:50,XL:40,X:10,IX:9,V:5,IV:4,I:1};r="replace";R=RegExp

用法:p(text)例如p('XIX + LXXX')return XCIX

带有解释性注释的代码:

// Array mapping characters to values
v={M¯:1e6,D¯:5e5,C¯:1e5,L¯:5e4,X¯:1e4,V¯:5e3,M:1e3,CM:900,D:500,CD:400,C:100,XC:90,L:50,XL:40,X:10,IX:9,V:5,IV:4,I:1};
// Shortcut for String.replace
r='replace';
R=RegExp;

// The heart of the program
function p(s) {
    // Replace operators with ";s+=", ";s-=", and so on
    s=s[r](/(^|[*\/+-])/g,'0;s$1=');
    // Loop over the character map and replace all letters with numbers
    for(i in v){
        f=R('\\b'+i);
        while(f.test(s))
            s=s[r](f, v[i]+'+')
    }
    eval(s+'0');
    // Set up our return string
    h='';
    // Replace digits with characters
    for(i in v)
        while(s>=v[i]) {
            h+=i;
            s-=v[i];
        }
    return h;
}

这适用于给定的样本以及我尝试过的所有其他样本。例子:

XIX + LXXX = XCIX
XCIX + I / L * D + IV = MIV
XL + IX/VII + II * XIX = CLXXI
CD + C + XL + X + I = DLI
M̅ + I = M̅I
MMMM + M = V̅

2

Ruby 2.1、353(以及许多其他迭代),295-50 = 245

包膜处理增加了〜23个字符。

这处理输入中的“ IL”或“ VM”,并且在负数(变为高整数)或小数(截断)或任何空格时均无错误地失败。现在还处理负的第一个数字(尽管如果总数为负,则失败的可能性仍然很小)。如果以*或/开头,或者结果等于或大于400万,则失败也很严重。

使用Object#send进行“手动计算器”功能。

m=%w{I V X L C D M V̅ X̅ L̅ C̅ D̅ M̅};n=m.zip((0..12).map{|v|(v%2*4+1)*10**(v/2)}).to_h
d=0
gets.scan(/([-+*\/])?([A-Z̅]+)/){|o,l|f=t=0
l.scan(/.̅?/){t-=2*f if f<v=n[$&]
t+=f=v}
d=d.send o||:+,t}
7.downto(1){|v|z=10**v
y=(d%z)*10/z
q,w,e=m[v*2-2,3]
$><<(y>8?q+e : y<4?q*y : y<5?q+w : w+q*(y-5))}

取消高尔夫:

m=%w{I V X L C D M V̅ X̅ L̅ C̅ D̅ M̅} # roman numerals
n=m.zip((0..12).map{|v|(v%2*4+1)*10**(v/2)}).to_h # map symbols to values
d=0
gets. # get input and...
  scan(/([-+*\/])?([A-Z̅]+)/) { |l,o|  # for each optional operator plus number
    f=t=0
    l.scan(/.̅?/){                           # read the number in one letter at a time
      t -= 2 * f if f < (v=n[$&])           # if the number's greater than the prev, subtract the prev twice since you already added it
      t += (f = v)                          # add this, and set prev to this number
    }
    d = d.send((o || :+), t)                # now that we've built our number, "o" it to the running total (default to +)
}
7.upto(1) { |v|                        # We now print the output string from left to right
  z = 10**v                            # z = [10, 100, 1000, etc.]
  y = (d%z)*10/z                       # if d is 167 and z is 100, y = 67/10 = 6 
  q,w,e = m[v*2-2,3]                   # q,w,e = X, L, C
  $><< (                               # print: 
    y>8 ? q+e :                        # if y==9,    XC
      y<4 ? q*y :                      # if y<4,     X*y
        y>3 ? q+w :                    # if y==4,    XL
          q*(y-5)                      # else,       L + X*(y-5)
  )
}

2

Python的2 - 427 418 404 401 396 395 392字符

从标准输入读取。它仅处理大写字母(可以使其不区分大小写,但要额外增加8个字符),并且需要空格。不进行验证-我尚未测试过在各种情况下如何破解。但是,它确实处理VC = 95之类的数字。

N=['?M','DC','LX','VI'];t=0;o='+'
for q in raw_input().split():
 if q in"+-*/":o=q;continue
 n=s=0;X=1
 for l in q:
  x=''.join(N).find(l);v=(5-x%2*4)*10**(3-x/2)
  if X<x:n+=s;s=v;X=x
  elif X>x:n+=v-s;s=0
  else:n+=v+s;s=0
 exec"t"+o+"=n+s"
r=t/1000*'M'
for p,d in enumerate("%04d"%(t%1e3)):
 i="49".find(d);g=N[p]
 if i<0:
  if'4'<d:r+=g[0]
  r+=int(d)%5*g[1]
 else:r+=g[1]+N[p-i][i]
print r

和非高尔夫版本:

# Numerals grouped by powers of 10
N = ['?M','DC','LX','VI']
# Start with zero plus whatever the first number is
t = 0
o = '+'
for q in raw_input().split():
    if q in "+-*/":
        # An operator; store it and skip to the next entry
        o = q
        continue
    # n holds the converted Roman numeral, s is a temp storage variable
    n = s = 0
    # X stores our current index moving left-to-right in the string '?MDCLXVI'
    X = 1
    for l in q:
        # x is the index of the current letter in '?MDCLXVI'
        x = ''.join(N).find(l)
        # Calculate the value of this letter based on x
        v = (5 - x%2 * 4) * 10 ** (3 - x/2)
        if X < x:
            # We're moving forward in the list, e.g. CX
            n += s      # Add in any previously-stored value
            s = v       # Store this value in case we have something like CXL
            X = x       # Advance the index
        elif X > x:
            # Moving backward, e.g. XC
            n += v - s  # Add the current value and subtract the stored one
            s=0
        else:
            # Same index as before, e.g. XX
            n += v + s  # Add the current value and any stored one
            s = 0
    # Update total using operator and value (including leftover stored value
    # if any)
    exec "t" + o + "=n+s"

# Now convert the answer back to Roman numerals
# Special-case the thousands digit
r = t / 1000 * 'M'
# Loop over the number mod 1000, padded with zeroes to four digits (to make
# the indices come out right)
for p, d in enumerate("%04d" % (t % 1e3)):
    i = "49".find(d)
    g = N[p]
    if i < 0:
        # i == -1, thus d isn't '4' or '9'
        if '4' < d:
            # >= 5, so add the 5's letter
            r += g[0]
        # ... plus (digit % 5) copies of the 1's letter
        r += int(d) % 5 * g[1]
    else:
        # If it's a 4 or 9, add the 1's letter plus the appropriate
        # larger-valued letter
        r += g[1] + N[p-i][i]
print r

我觉得Perl会更好,但是我还不够了解。不过,对于代码高尔夫的第一次尝试,我对此感到非常满意。


1

PHP — 549 525 524 520字节

没什么太创新的:标准化运算符以确保从左到右的优先级,将Roman转换为十进制,eval在语句上运行,例如XCIX + I / L * D + IV转换为return((((((++ 90 +9) +(+1))/(+50))*(+500))+(+4)); ,然后将小数转换回罗马。

  • 最终结果被截断
  • 答案少于1则返回空白
  • 如果输入不正确,则结果不确定
$f='str_replace';$g='str_split';$c=array('M'=>1e3,'CM'=>900,'D'=>500,'CD'=>400,'C'=>100,'XC'=>90,'L'=>50,'XL'=>40,'X'=>10,'IX'=>9,'V'=>5,'IV'=>4,'I'=>1);$j='['.$f(array('+','-','*','/'),array('])+[','])-[','])*[','])/['), $argv[1]).'])';$j=str_repeat('(',substr_count($j,')')).$j;$j=$f('[','(',$j);$j=$f(']',')',$j);foreach($g('IVIXXLXCCDCM',2)as$w)$j=$f($w,'+'.$c[$w],$j);foreach($g('IVXLCDM')as$w)$j=$f($w,'+'.$c[$w],$j);$k=eval('return '.$j.';');$l='';foreach($c as$a=>$b){while($k>=$b){$l.=$a;$k-=$b;}}print$l."\n";

例如

$ php roman.php 'XCIX + I / L * D + IV' — test case
MIV                                     — 1004

$ php roman.php 'XXXII * LIX'           — 32 × 59
MDCCCLXXXVIII                           — 1888

0

Python-446字节

这可以大大改善。我觉得我不得不使用Python进行第一次尝试。它在第一遍做3件事

  1. 标记数字和运算符
  2. 评估数字,并放大符号表x以包括遇到的所有可能的组合(即使未使用它们)。例如,在进行XIX词法分析时"X":10"XI":11和的部分值"XIX":19会添加到符号表中
  3. 插入嵌套的括号以强制执行从左到右的评估

最后,它调用eval原始字符串(添加的括号除外)并为其提供符号表。

然后,我刚刚粘贴了一个已知的解决方案,将整数转换为罗马,因为我已经从事了如此长时间的研究……请随时进行改进,以便我学到新的知识:)

m = zip((1000,900,500,400,100,90,50,40,10,9,5,4,1),
('M','CM','D','CD','C','XC','L','XL','X','IX','V','IV','一世'))
def doit(s):
 x = {'M':1e3,'D':500,'C':100,'L':50,'X':10,'V':5,'I':1}; y = [] ; z =''; a ='0'; s ='+'+ s
 对于s.upper()中的c:
  如果c在x中:
   z + = c; y.append(x [c])
   如果len(y)> 1和y [-1]> y [-2]:y [-2] * =-1
   x [z] = sum(y)
  elif c in“ + / *-”:a ='('+ a + z +')'+ c; y = []; z =''
 a + = z; i = eval(a,x); r =''
 对于n,c in m:d = int(i / n); r + = c * d; i- = n * d
 返回r


打印doit(“ XIX + LXXX”)
打印doit(“ XCIX + I / L * D + IV”)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.