Crea un calcolatore di numeri romani


18

Crea una calcolatrice di base per i numeri romani.

Requisiti

  • Supporti +, -, *,/
  • L'input e l'output dovrebbero prevedere solo un prefisso del sottrattore per simbolo (ovvero 3 non può essere IIVperché ce ne sono due Iprima V)
  • Manipolazione del principio di sottrazione in ingresso e non vanno uscita al supporto minimo moderne convenzioni standard, in cui solo potenze di dieci vengono sottratti dai grandi numeri (ad esempio I, X, Csottrattori sono necessarie, ma non V, L, D) e la sottrazione non è mai fatto da un numero più che 10 volte il sottrattore (ad es. IXDeve essere supportato ma ICnon è necessario).
  • Ingresso e uscita devono essere lasciati a destra in ordine di valore, a partire dal più grande (ovvero 19 = XIXno IXX, 10 è maggiore di 9)
  • Da sinistra a destra, nessuna precedenza per l'operatore, come se si stesse utilizzando una calcolatrice manuale.
  • Supporta numeri interi positivi in ​​ingresso / uscita tra 1-4999 (non è necessario per V̅)
  • Nessuna libreria che esegue la conversione numerica per te

Per te decidere

  • Case sensitive
  • Spazi o nessun spazio sull'input
  • Cosa succede se si ottiene un risultato decimale. Tronca, nessuna risposta, errore, ecc.
  • Cosa fare per un output che non è possibile gestire. Negativi o numeri troppo grandi da stampare.
  • Se sostenere un uso più liberale del principio di sottrazione rispetto al requisito minimo.

Credito extra

  • -50 - Gestire fino a 99999 o più. I simboli devono includere un vincolo

Ingresso / uscita campione

XIX + LXXX                 (19+80)
XCIX

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

Vince il codice più corto.


(99 + 1/50 * 500 + 4) = (99 + 10 + 4) = 113, ma l'input / output del tuo campione dice che è MIV (1004).
Victor Stafusa,

1
@Victor - operazione rigorosa da sinistra a destra - nessuna regola di precedenza - quindi 99 + 1/50 * 500 + 4 devono essere calcolati come (((((99 + 1) / 50) * 500) + 4)

La gestione dei numeri è IM = 999obbligatoria?
Kendall Frey,

@KendallFrey Mi aspetto che tu possa inserire IM. Se l'output è IMo CMXCIXper 999 dipende da te. Entrambi soddisfano i requisiti.
Danny,

2
IM non è standard per l'utilizzo dei numeri romani moderni. In genere sono solo i 4 e i 9 di ciascun ordine di grandezza (4, 9, 40, 90, 400, 900, ecc.) Che vengono fatti per sottrazione. Per il 1999, MCMXCIX sarebbe canonico, non MIM ... guarda i crediti di qualsiasi film di quell'anno. Altrimenti, dove finisce? Ci aspettiamo anche di supportare altre sottrazioni non standard come VL per 45? IC con un vincolo sopra la C dovrebbe essere supportato come 99999 per il bonus?
Jonathan Van Matre,

Risposte:


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"

Versione annotata:

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

So che giocare a golf in SQL equivale a giocare a 18 buche con nient'altro che un cuneo di sabbia, ma ho assaporato la sfida di questa e penso di essere riuscito a fare alcune cose interessanti metodologicamente.

Questo supporta il vincolo sia per l'input che per l'output. Ho adottato la convenzione di utilizzare una tilde finale per rappresentarla, quindi V ~ è 5000, X ~ è 10000, ecc. Dovrebbe anche gestire output fino a 399.999 in base all'utilizzo dei numeri romani moderni standard. Dopodiché, eseguirà una codifica romana parzialmente non standard di qualsiasi cosa nell'intervallo supportato da INT.

Poiché è tutto matematica intera, tutti i risultati non interi sono implicitamente arrotondati.

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

Sto ancora armeggiando con una soluzione basata su set per sostituire alcuni dei loop WHILE che potrebbero ridurre il conteggio dei byte ed essere un esempio più elegante di SQL idiomatico. Ci sono anche alcuni byte da guadagnare riducendo al minimo l'utilizzo degli alias di tabella. Ma dato che è essenzialmente impossibile da vincere in questa lingua, sono principalmente qui solo per sfoggiare il mio vestito di Don Chisciotte. :)

SELECT @i in alto ripete l'input:

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

E il SELEZIONA alla fine restituisce:

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

E puoi provarlo tu stesso in questo SQLFiddle

E tornerò per aggiungere alcuni commenti su come funziona, perché perché pubblicare una risposta ovviamente persa se non la sfrutterai per valore educativo?


2

Javascript - 482 476 caratteri

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

L'input / output di esempio funziona:

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

Gestisce male anche grandi numeri:

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

E accetta, ma non richiede, anche spazi.

Ma, dal momento che stavo giocando a golf, ha alcuni problemi:

  • Non convalida se l'input è ben formato. Se l'input non è ben formato, il comportamento è indefinito (e in pratica è molto bizzarro e strano).
  • Tronca i numeri di frazione sull'output (ma è in grado di eseguire calcoli intermedi con essi).
  • Abusa davvero della funzione eval.
  • Non tenta di gestire numeri negativi.
  • Fa distinzione tra maiuscole e minuscole.

Questa versione alternativa gestisce numeri da 5000 a 99999, ma ha 600 598 584 caratteri:

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

Non credo che si applichi il -20: vedi vinculum
SeanC

Accetto con @SeanCheshire qui. Per la gestione di un numero maggiore, l'intenzione è quella di aggiungere un vincolo sul numero pari a 1000 volte il valore di quello che è normalmente. Forse dovrebbe essere più grande di un -20 quindi vale la pena provare per le persone.
Danny,

1
@Danny Ho aggiunto una versione che gestisce il vinculus, ma aumenta il codice di 116 caratteri.
Victor Stafusa,

2

Javascript 479 361 348 278 253

303 caratteri - 50 per numeri di supporto fino a 1 milione, completi di supporto per vincolo:

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

Utilizzo: p(text)ad es . p('XIX + LXXX')Resi XCIX.

Codice con commenti esplicativi:

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

Questo funziona per i campioni forniti e per tutti gli altri che ho provato. Esempi:

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 (e molte altre iterazioni) , 295-50 = 245

La gestione del vincolo aggiunge ~ 23 caratteri.

Gestisce "IL" o "VM" nell'input e fallisce senza errori su negativi (va a valori alti) o decimali (troncati) o spazi. Ora gestisce anche un primo numero negativo (anche se se il totale è negativo, fallisce ancora male). Inoltre fallisce male se inizi con * o / o se il risultato è 4 milioni o più.

Utilizza l'oggetto # invia per la funzionalità "calcolatrice manuale".

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

Ungolfed:

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 caratteri

Legge da input standard. Gestisce solo lettere maiuscole (potrebbe renderlo insensibile alle maiuscole al costo di 8 caratteri extra) e richiede spazi. Non convalida: non ho testato per vedere come si rompe in vari casi. Tuttavia, gestisce numeri come 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

E la versione non golfata:

# 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

Ho la sensazione che Perl sarebbe stato meglio, ma non ne so abbastanza. Per una prima pugnalata al code golf, però, mi sento abbastanza bene.


1

PHP - 549 525 524 520 byte

Niente di troppo innovativo: normalizza gli operatori per garantire la precedenza da sinistra a destra, converte il romano in decimale, viene eseguito sull'istruzioneeval , ad esempio XCIX + I / L * D + IV viene convertito in qualcosa di simile a return ((((((+90 +9) + (+1)) / (+50)) * (+500)) + (+4)); , quindi converte i decimali in romani.

  • i risultati finali vengono troncati
  • le risposte meno di 1 tornano vuote
  • i risultati non sono definiti se viene fornito un input errato
$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";

per esempio

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

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

0

Python - 446 byte

Questo potrebbe essere notevolmente migliorato. Sentivo di dover fare il primo swing usando Python. Fa 3 cose al primo passaggio

  1. tokenizza i numeri e gli operatori
  2. valuta i numeri e allarga la tabella dei simboli xper includere tutte le possibili combinazioni incontrate (anche se non vengono utilizzate). Ad esempio, mentre XIXsi sta lexed, i valori parziali "X":10, "XI":11e "XIX":19vengono aggiunti alla tabella dei simboli
  3. inserisce parentesi annidate per applicare la valutazione da sinistra a destra

Alla fine, chiama evalla stringa originale (tranne con parentesi aggiunte) e gli dà la tabella dei simboli.

Poi ho appena incollato una soluzione nota per convertire numeri interi in romani, dal momento che avevo lavorato su questo abbastanza a lungo ... non esitate a migliorare in modo da imparare qualcosa di nuovo :)

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


print doit ("XIX + LXXX")
print doit ("XCIX + I / L * D + IV")
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.