Создать калькулятор римской цифры

18

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

Требования

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

Это поддерживает Vinculum для ввода и вывода. Я принял соглашение об использовании конечной тильды для ее представления, поэтому 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 в конце возвращает:

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 применяется: см уздечка
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̅
elixenide
источник
2

Ruby 2.1, 353 (и многие другие итерации) , 295 - 50 = 245

Обработка Vinculum добавляет ~ 23 символа.

Это обрабатывает «IL» или «VM» во входных данных и завершается с ошибкой без ошибок для отрицательных значений (переход к старшим целым числам) или десятичных дробей (усечений) или любых пробелов. Теперь также обрабатывает отрицательное первое число (хотя, если сумма отрицательна, она все равно плохо работает). Также плохо работает, если вы начинаете с * или / или если результат 4 миллиона или больше.

Использует 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))}

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 символов

Читает со стандартного ввода. Он обрабатывает только верхний регистр (может сделать его нечувствительным к регистру за счет 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 был бы лучше, но я не знаю этого достаточно. Впрочем, для первого удара в кодовом гольфе я чувствую себя довольно хорошо.

DLosc
источник
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

Python - 446 байт

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

  1. токенизирует числа и операторы
  2. оценивает числа и увеличивает таблицу символов, xчтобы включить все возможные комбинации (даже если они не используются). Например, в то время как XIXв настоящее время lexed, частичные значения "X":10, "XI":11и "XIX":19добавляются в таблицу символов
  3. вставляет вложенные парены для обеспечения оценки слева направо

В конце он вызывает evalисходную строку (кроме добавленных паренов) и дает ей таблицу символов.

Затем я просто вставил известное решение для преобразования целого числа в римское, так как я работал над этим достаточно долго ... пожалуйста, не стесняйтесь улучшать, чтобы я узнал что-то новое :)

т = застежка-молния ((1000,900,500,400,100,90,50,40,10,9,5,4,1),
( 'М', 'КМ', 'D', 'компакт', 'C', 'ХС', 'L', 'XL', 'X', 'IX', 'V', 'IV',» Я'))
def doit (s):
 х = { 'М': 1e3, 'D': 500, 'C': 100, 'L': 50, 'X': 10, 'V': 5, 'Я': 1}; у = [] ; г = ''; а = '0'; s = '+' + s
 для c в s.upper ():
  если с в х:
   г + = с; y.append (х [с])
   если len (y)> 1 и y [-1]> y [-2]: y [-2] * = - 1
   х [г] = сумма (у)
  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
 возврат г


печать doit ("XIX + LXXX")
печать doit ("XCIX + I / L * D + IV")
Марк Лаката
источник