Створіть калькулятор римських чисел


18

Створіть основний калькулятор для римських цифр.

Вимоги

  • Опори +, -, *,/
  • Введення і виведення слід очікувати тільки один віднімаючий префікс на символ (тобто 3 не може бути , IIVтому що є два Iперед тим, щоб V)
  • Звернення принципу вирахування на вході і виході повинен при мінімальній підтримці сучасних стандартних конвенцій, в яких тільки повноваження десяти віднімаються з великих чисел (наприклад I, X, Cпотрібне вирахування , але не V, L, D) і віднімання ніколи не робиться з цілого ряду більш 10 разів віднімайте (наприклад, він IXповинен підтримуватися, але ICце не потрібно).
  • Введення та вихід повинні бути зліва направо в порядку значення, починаючи з найбільшого (тобто 19 = XIXне 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).
Віктор Стафуса

1
@Victor - сувора робота зліва направо - немає правил пріоритету - тому 99 + 1/50 * 500 + 4 слід обчислювати як ((((99 + 1) / 50) * 500) + 4)

Чи IM = 999потрібна обробка номерів ?
Кендалл Фрей

@KendallFrey, я б очікував, що ти зможеш зробити свій внесок IM. Буде чи вихід IMабо CMXCIXна 999 до вас. Обидва відповідають вимогам.
Денні

2
IM є нестандартним для сучасного вживання римських цифр. Зазвичай відніманням лише 4 і 9 з кожного порядку величин (4, 9, 40, 90, 400, 900 тощо). Для 1999 року MCMXCIX був би канонічним, а не MIM ... дивіться кредити будь-якого фільму того року. Інакше, де це закінчується? Чи очікується також, що ми будемо підтримувати інші нестандартні віднімання, такі як VL на 45? Чи потрібно підтримувати ІС з винкулом над С як 99999 за бонус?
Джонатан Ван Матре

Відповіді:


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. Також є кілька байтів, які можна отримати за рахунок зменшення використання псевдонімів таблиці до мінімального рівня. Але так як це по суті не виграшно на цій мові, я здебільшого просто тут, щоб показати свій наряд Дон Кіхота. :)

SELECT @i вгорі повторює вхід:

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

І ВИБІР в кінці повертається:

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

І ви можете перевірити його самостійно на цьому SQLFiddle

І я повернусь, щоб додати коментар до того, як це працює, бо навіщо публікувати явно програшну відповідь, якщо ти не збираєшся використовувати її для навчальної цінності?


2

Javascript - 482 476 символів

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

І це також приймає пробіли, але не вимагає цього.

Але, оскільки я займався гольфом, у нього є деякі проблеми:

  • Він не підтверджує, якщо вхід добре сформований. Якщо вхід недостатньо сформований, поведінка не визначена (а на практиці це дуже химерно і дивно).
  • Він обрізає числа дробів на виході (але він може робити проміжні обчислення з ними).
  • Це дійсно зловживає функцією eval.
  • Він не намагається обробити негативні числа.
  • Це залежно від регістру.

Ця альтернативна версія обробляє цифри понад 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: див. Vinculum
SeanC

Погодьтеся з @SeanCheshire тут. Для більшої кількості операцій, що спрямовується на більшу кількість, наміром є додавання винкову ямку над цифрою, яка в 1000 разів перевищує значення, яке воно зазвичай є. Можливо, вона повинна бути більшою за -20, тому варто спробувати людей.
Денні

1
@Danny Я додав версію, яка обробляє vinculus, але вона збільшує код на 116 символів.
Віктор Стафуса

2

Javascript 479 361 348 278 253

303 символи - 50 для підтримки чисел до 1 мільйона в комплекті з підтримкою 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')повертає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" на вході, і виходить з ладу без помилок на негативах (переходить до високих точок) або десяткових знаків (скорочення) або будь-яких пробілах. Тепер також обробляє негативне перше число (хоча якщо загальна сума від’ємна, вона все одно погано працює). Також погано виходить з ладу, якщо ви починаєте з * або / або якщо результат становить 4 мільйони або більше.

Використовує Object # send для функцій "hand-Calculator".

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

Пітон 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 байт

Нічого надто інноваційного: нормалізує операторів, щоб забезпечити пріоритет зліва направо, перетворює римський у десятковий, працює 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

Пітон - 446 байт

Це можна значно покращити. Я відчув, що мені потрібно було скористатися першим гойдалкою за допомогою Python. Це робить 3 речі з першого проходу

  1. токенізує номери та оператори
  2. оцінює числа та збільшує таблицю символів, xщоб включати всі можливі комбінації (навіть якщо вони не використовуються). Наприклад, в той час як XIXв даний час lexed, часткові значення "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
 для c в s.upper ():
  якщо c в x:
   z + = c; y.додаток (x [c])
   якщо len (y)> 1і y [-1]> y [-2]: y [-2] * = - 1
   x [z] = сума (y)
  elif c в "+ / * -": a = '(' + a + z + ')' + c; y = []; z = ''
 a + = z; i = eval (a, x); r = ''
 для n, c в m: d = int (i / n); r + = c * d; i- = n * d
 повернути r


print doit ("XIX + LXXX")
print doit ("XCIX + I / L * D + IV")
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.