Crear una calculadora de números romanos


18

Crea una calculadora básica para números romanos.

Requisitos

  • Soportes +, -, *,/
  • La entrada y la salida deben esperar solo un prefijo de sustractor por símbolo (es decir, 3 no puede ser IIVporque hay dos Iantes V)
  • Manipulación del principio de sustracción en la entrada y el mosto de salida en apoyo mínimo de las convenciones estándar modernas, en las que sólo potencias de diez se restan de los números más grandes (por ejemplo I, X, Csustractores son necesarios pero no V, L, D) y la resta nunca se hace a partir de un número de más de 10 veces el sustractor (por ejemplo, IXdebe ser compatible pero ICno es obligatorio).
  • La entrada y la salida se deben dejar de izquierda a derecha en orden de valor, comenzando por la más grande (es decir, 19 = XIXno IXX, 10 es mayor que 9)
  • De izquierda a derecha, sin prioridad del operador, como si estuviera usando una calculadora manual.
  • Admite números enteros positivos de entrada / salida entre 1-4999 (sin necesidad de V̅)
  • No hay bibliotecas que hagan conversión de números romanos por usted

Para que usted decida

  • Mayúsculas y minúsculas
  • Espacios o sin espacios en la entrada
  • ¿Qué sucede si obtienes una salida decimal? Truncado, sin respuesta, error, etc.
  • Qué hacer para la salida que no puede manejar. Negativos o números demasiado grandes para imprimir.
  • Ya sea para apoyar un uso más liberal del principio de resta que el requisito mínimo.

Crédito adicional

  • -50 - Manejar hasta 99999 o más grande. Los símbolos deben incluir un vinculo

Entrada / salida de muestra

XIX + LXXX                 (19+80)
XCIX

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

El código más corto gana.


(99 + 1/50 * 500 + 4) = (99 + 10 + 4) = 113, pero su entrada / salida de muestra dice que es MIV (1004).
Victor Stafusa

1
@Victor - operación estricta de izquierda a derecha - sin reglas de precedencia - por lo que 99 + 1/50 * 500 + 4 debe calcularse como ((((99 + 1) / 50) * 500) + 4)

¿Se IM = 999requiere manejar números ?
Kendall Frey

@KendallFrey Esperaría que pudieras ingresar IM. Si la salida es IMo CMXCIXpara 999 depende de usted. Ambos se ajustan a los requisitos.
Danny

2
IM no es estándar para el uso moderno de números romanos. Por lo general, solo se hacen por sustracción los números 4 y 9 de cada orden de magnitud (4, 9, 40, 90, 400, 900, etc.). Para 1999, MCMXCIX sería canónico, no MIM ... mira los créditos de cualquier película de ese año. De lo contrario, ¿dónde termina? ¿También se espera que admitamos otras sustracciones no estándar como VL para 45? ¿Tendría que admitir IC con un vinculo sobre la C como 99999 para la bonificación?
Jonathan Van Matre

Respuestas:


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}

Uso:

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

Versión anotada:

/**
 * 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 bytes

Sé que jugar al golf en SQL es equivalente a jugar 18 hoyos con nada más que una cuña de arena, pero disfruté el desafío de este, y creo que logré hacer algunas cosas interesantes metodológicamente.

Esto admite el vinculum tanto para entrada como para salida. Adopté la convención de usar una tilde final para representarlo, por lo que V ~ es 5000, X ~ es 10000, etc. También debe manejar salidas de hasta 399,999 de acuerdo con el uso estándar moderno de números romanos. Después de eso, hará una codificación romana parcialmente no estándar de cualquier cosa en el rango admitido por INT.

Como todo es matemática entera, cualquier resultado no entero se redondea implícitamente.

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

Todavía estoy jugando con una solución basada en conjuntos para reemplazar algunos de los bucles WHILE que podrían reducir el recuento de bytes y ser un ejemplo más elegante de SQL idiomático. También se pueden obtener algunos bytes reduciendo el uso de alias de tabla a un mínimo. Pero como es esencialmente imposible de ganar en este idioma, en su mayoría solo estoy aquí para mostrar mi atuendo de Don Quijote. :)

SELECT @i en la parte superior repite la entrada:

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

Y el SELECCIONAR al final devuelve:

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

Y puede probarlo usted mismo en este SQLFiddle

Y volveré para agregar algunos comentarios sobre cómo funciona, porque ¿por qué publicar una respuesta obviamente perdida si no va a explotarla por su valor educativo?


2

Javascript - 482 476 caracteres

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))))

La entrada / salida de muestra funciona:

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

También maneja mal grandes números:

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

Y acepta, pero no requiere, espacios también.

Pero, como estaba jugando al golf, tiene algunos problemas:

  • No valida si la entrada está bien formada. Si la entrada no está bien formada, el comportamiento es indefinido (y en la práctica es muy extraño y extraño).
  • Trunca los números de fracción en la salida (pero puede hacer cálculos intermedios con ellos).
  • Realmente abusa de la función eval.
  • No intenta manejar números negativos.
  • Es sensible a mayúsculas y minúsculas.

Esta versión alternativa maneja números de más de 5000 hasta 99999, pero tiene 600 598 584 caracteres:

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))))

No creo que se aplique el -20: ver vinculum
SeanC

De acuerdo con @SeanCheshire aquí. Para el manejo de números más grandes, la intención es agregar un vinculo sobre el número para que sea 1000 veces el valor de lo que normalmente es. Tal vez debería ser más grande que un -20, por lo que vale la pena intentarlo para las personas.
Danny

1
@Danny agregué una versión que maneja el vinculus, pero aumenta el código en 116 caracteres.
Victor Stafusa

2

Javascript 479 361 348 278 253

303 caracteres: 50 para números de soporte de hasta 1 millón, completo con soporte de 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

Uso: p(text)por ejemplo, p('XIX + LXXX')devoluciones XCIX.

Código con comentarios explicativos:

// 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;
}

Esto funciona para las muestras dadas y para todas las demás que he probado. Ejemplos:

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 (y muchas otras iteraciones) , 295 - 50 = 245

El manejo del vinculum agrega ~ 23 caracteres.

Esto maneja "IL" o "VM" en la entrada, y falla sin error en negativos (va a entradas altas) o decimales (trunca), o cualquier espacio. Ahora también maneja un primer número negativo (aunque si el total es negativo, todavía falla mal). También falla mal si comienza con * o / o si el resultado es 4 millones o más.

Utiliza Object # send para la funcionalidad de "calculadora manual".

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))}

Sin golf:

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 caracteres

Lee desde la entrada estándar. Solo maneja mayúsculas (podría hacer que no distinga entre mayúsculas y minúsculas al costo de 8 caracteres adicionales) y requiere espacios. No valida: no he probado para ver cómo se rompe en varios casos. Sin embargo, maneja números como 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

Y la versión sin golf:

# 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

Tengo la sensación de que Perl hubiera sido mejor, pero no sé lo suficiente. Sin embargo, para un primer intento de golf de código, me siento bastante bien con esto.


1

PHP - 549 525 524 520 bytes

Nada demasiado innovador: normaliza los operadores para garantizar la precedencia de izquierda a derecha, convierte el romano al decimal, ejecuta evalla instrucción, por ejemplo, XCIX + I / L * D + IV se convierte en algo así como return ((((((+90 +9) + (+1)) / (+50)) * (+500)) + (+4)); , luego convierte el decimal de nuevo a romano.

  • los resultados finales se truncan
  • responde menos de 1 vuelve en blanco
  • los resultados son indefinidos si se ingresan datos incorrectos
$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";

p.ej

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

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

0

Python - 446 bytes

Esto podría mejorarse considerablemente. Sentí que tenía que dar el primer golpe con Python. Hace 3 cosas en el primer pase

  1. tokeniza los números y operadores
  2. evalúa los números y amplía la tabla de símbolos xpara incluir todas las combinaciones posibles encontradas (incluso si no se usan). Por ejemplo, mientras XIXse está lexed, los valores parciales de "X":10, "XI":11y "XIX":19se añaden a la tabla de símbolos
  3. inserta parens anidadas para hacer cumplir la evaluación de izquierda a derecha

Al final, llama evala la cadena original (excepto con parens agregados) y le da la tabla de símbolos.

Luego simplemente pegué una solución conocida para convertir enteros a romanos, ya que había trabajado en esto lo suficiente ... por favor, siéntase libre de mejorar para que aprenda algo nuevo :)

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', ' YO'))
def doit (s):
 x = {'M': 1e3, 'D': 500, 'C': 100, 'L': 50, 'X': 10, 'V': 5, 'I': 1}; y = [] ; z = ''; a = '0'; s = '+' + s
 para c en s.upper ():
  si c en x:
   z + = c; y.append (x [c])
   si len (y)> 1 e y [-1]> y [-2]: y [-2] * = - 1
   x [z] = suma (y)
  elif c en "+ / * -": a = '(' + a + z + ')' + c; y = []; z = ''
 a + = z; i = eval (a, x); r = ''
 para n, c en m: d = int (i / n); r + = c * d; i- = n * d
 volver r


doit de impresión ("XIX + LXXX")
doit de impresión ("XCIX + I / L * D + IV")
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.