将罗马数字翻译成阿拉伯数字

时间:2014-02-03 22:14:21

标签: math integer prolog converter roman-numerals

我构建了一个谓词,将罗马数字转换为阿拉伯数字。唯一的问题是谓词是有限的:如果我想一次转换超过3个阿拉伯数字,它就不再起作用了。

谓词应该如何运作:

?- convert([v,i,i],Arabic).
Arabic = 7.

到目前为止我的解决方案:

tran([],0).
tran(i,1).
tran(v,5).
tran(x,10).

convert([],X) :- X is 0, !.
convert([T],X) :- tran(T,E), X is E,!.
convert([T|Ts],X) :- tran(T,E), tran(Ts,Es), X is E+Es,!.
convert([T,Ts,Tss],X) :- tran(T,E), tran(Ts,Es), tran(Tss,Ess), X is E+Es+Ess.

我知道为什么谓词不能使用超过3个数字,我也可以扩展转换谓词,但具有与上面所示相同的模式。

如何使convert-predicate更“通用”(这样它可以独立于数字的数量工作)?或者你有其他想法如何编写谓词? 谢谢:))

3 个答案:

答案 0 :(得分:3)

我没有对此进行太多测试,但我已经尝试了几个数字,它似乎有效。

代码服从“减法对规则”,例如在https://projecteuler.net/about=roman_numerals

中描述

代码使用“累加器”技术传递信息,这是以前看到的数字之和。初始调用只是将累加器设置为0.

digit(i, 1).
digit(v, 5).
digit(x, 10).
digit(l, 50).
digit(c, 100).
digit(d, 500).
digit(m, 1000).

convert(Roman, Arabic) :-
    convert(Roman, 0, Arabic).

convert([], Acc, Acc).

convert([A], Acc, Arabic) :-
    digit(A, AVal),
    Arabic is Acc + AVal.

convert([A, B | Rest], Acc, Arabic) :-
    digit(A, AVal), digit(B, BVal),
    AVal < BVal,
    NewAcc is Acc + BVal - AVal,
    convert(Rest, NewAcc, Arabic).

convert([A, B | Rest], Acc, Arabic) :-
    digit(A, AVal), digit(B, BVal),
    AVal >= BVal,
    NewAcc is Acc + AVal,
    convert([B | Rest], NewAcc, Arabic).

一些测试:

convert([v, i, i], Arabic).
Arabic = 7 

?- convert([x, i, x], Arabic).
Arabic = 19 

?- convert([m, d, c, v, i], Arabic).
Arabic = 1606 

可能编写一个谓词convert,它使用约束编程在真正的Prolog精神中有两种方式,但我没有尝试过这种方法。

答案 1 :(得分:3)

如果您认为罗马编号系统中离散“数字”的数量不仅仅是I,X和V,这可能会有所帮助,即:

roman( "M"   , 1000 ) .
roman( "CM"  ,  900 ) .
roman( "D"   ,  500 ) .
roman( "CD"  ,  400 ) .
roman( "C"   ,  100 ) .
roman( "XC"  ,   90 ) .
roman( "L"   ,   50 ) .
roman( "XL"  ,   40 ) .
roman( "X"   ,   10 ) .
roman( "IX"  ,    9 ) .
roman( "V"   ,    5 ) .
roman( "IV"  ,    4 ) .
roman( "I"   ,    1 ) .

然后你可以写点像

roman_to_decimal( R , D ) :-
  roman_to_decimal( R , 0 , D )
  .

roman_to_decimal( [] , D , D ) :- .
roman_to_decimal( R  , T , D ) :-
  roman(P,V) ,
  append(P,S,R) ,
  ! ,
  T1 is T+V ,
  roman_to_decimal(S,T1,D)
  .

将其作为

调用
roman_to_decimal( "MCM" , D ) .

这确实有一些缺点,对于白衣:

  • 它不强制语法:罗马编号系统要求离散组件按值的降序从左到右排序。这不考虑这一点。

  • 它没有考虑到许多变化。是应该将999表示为紧凑型 IM 还是作为更强大的 CMXCIX

答案 2 :(得分:2)

只是为混音添加一个变体,这个版本在谢尔文的答案中使用了这个方案(它也允许更多的任意减法序列),并允许更像人类可读的输入,如尼古拉斯的答案。

numeral('I', 1).
numeral('V', 5).
numeral('X', 10).
numeral('L', 50).
numeral('C', 100).
numeral('D', 100).
numeral('M', 1000).

r2n(R, N) :-
    char_code(A, R),
    lower_upper(A, C),
    numeral(C, N).

trans(R, N) :-
    maplist(r2n, R, Rn),    % Pre-calculate a numeric list representation
    trans(Rn, 0, N).
trans([X,Y|T], Acc, N) :-
    X >= Y,
    Acc1 is Acc + X,
    trans([Y|T], Acc1, N).
trans([X,Y|T], Acc, N) :-
    X < Y,
    Acc1 is Acc - X,
    trans([Y|T], Acc1, N).
trans([X], Acc, N) :-
    N is Acc + X.
trans([], N, N).   % Optional rule: needed only if you want trans("", 0). to succeed

请注意,这些规则将允许任何有效的罗马数字,但也会对某些不正确形成的罗马数字做一些事情并取得成功。因此,验证正确的罗马数字不是一套规则。

示例输出:

| ?- trans("mmxiv", X).

X = 2014 ? ;

no
| ?- trans("CMXCIX", X).

X = 999 ? ;

no
| ?- trans("IM", X).

X = 999 ? ;

no
| ?- trans("IVX", X).   % Not a properly-formed Roman numeral

X = 4 ? ;   % Uh... ok... I guess

no