Просто для удовольствия, альтернатива, которая разбивает строку на группы одиночных и смежных цифр, позволяя использовать стандартную вычитающую нотацию (спасибо, Википедия ; преобразует каждую одиночную или смежные пары или цифры в их десятичные числа эквиваленты, а затем суммирует их:
with t (str) as (select 'MCMLXXXIV' from dual)
select sum(
case regexp_substr(str, '(CM|M|CD|D|XC|C|XL|L|IX|X|IV|V|I)', 1, level)
when 'M' then 1000
when 'CM' then 900
when 'D' then 500
when 'CD' then 400
when 'C' then 100
when 'XC' then 90
when 'L' then 50
when 'XL' then 40
when 'X' then 10
when 'IX' then 9
when 'V' then 5
when 'IV' then 4
when 'I' then 1
end) as decimals
from t
connect by regexp_substr(str, '(CM|M|CD|D|XC|C|XL|L|IX|X|IV|V|I)', 1, level) is not null;
DECIMALS
----------
1984
Обратите внимание, что порядок поисковых терминов в регулярном выражении не совпадает с их эквивалентным десятичным порядком; вам нужно соответствовать CM до M из-за вычитающей записи.
Если это то, что вам нужно сделать, то, вероятно, стоит создать детерминированную функцию (что, конечно же, верно и для метода рекурсивного CTE). В этом случае вы можете переключиться на цикл PL / SQL, чтобы сократить переключение контекста:
create or replace function roman_to_decimal(p_roman varchar2)
return number deterministic is
l_decimal number := 0;
begin
for i in 1..regexp_count(p_roman, '(CM|M|CD|D|XC|C|XL|L|IX|X|IV|V|I)') loop
l_decimal := l_decimal +
case regexp_substr(p_roman, '(CM|M|CD|D|XC|C|XL|L|IX|X|IV|V|I)', 1, i)
when 'M' then 1000
when 'CM' then 900
when 'D' then 500
when 'CD' then 400
when 'C' then 100
when 'XC' then 90
when 'L' then 50
when 'XL' then 40
when 'X' then 10
when 'IX' then 9
when 'V' then 5
when 'IV' then 4
when 'I' then 1
end;
end loop;
return l_decimal;
end;
/
select roman_to_decimal('DXV'), roman_to_decimal('MCMLXXXIV')
from dual;
ROMAN_TO_DECIMAL('DXV') ROMAN_TO_DECIMAL('MCMLXXXIV')
----------------------- -----------------------------
515 1984
Вы можете просмотреть и проверить все преобразования (1-3999, поскольку это диапазон, поддерживаемый to_char()
) с помощью:
with t (orig, roman) as (
select level, to_char(level, 'RN') from dual connect by level < 4000
)
select orig, roman, roman_to_decimal(roman)
from t;
ORIG ROMAN ROMAN_TO_DECIMAL(ROMAN)
---------- --------------- -----------------------
1 I 1
2 II 2
3 III 3
4 IV 4
5 V 5
6 VI 6
7 VII 7
8 VIII 8
9 IX 9
10 X 10
11 XI 11
...
3994 MMMCMXCIV 3994
3995 MMMCMXCV 3995
3996 MMMCMXCVI 3996
3997 MMMCMXCVII 3997
3998 MMMCMXCVIII 3998
3999 MMMCMXCIX 3999
Или просто чтобы убедиться, что все они возвращаются к своим исходным значениям:
with t (original, roman) as (
select level, to_char(level, 'RN') from dual connect by level < 4000
)
select original, roman, roman_to_decimal(roman)
from t
where roman_to_decimal(roman) != original;
no rows selected
Это немного медленнее, чем рекурсивный эквивалент CTE для меня, но может отличаться на других версиях и платформах; и, как я уже сказал, просто для удовольствия ...