oracle - 如何将罗马数字转换为十进制数字?
问题描述
这会将十进制数字转换为罗马数字:
select to_char(515, 'RN') from dual;
回报:DXV
怎么做反向?这抛出ORA-01722: Invalid number
:
select to_number('DXV', 'RN') from dual;
解决方案
只是为了好玩,一种将字符串分成单个和相邻数字组的替代方法,允许标准减法表示法(谢谢,维基百科;将每个单个或相邻对或数字转换为其十进制等效项;然后对它们求和:
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
请注意,正则表达式中搜索词的顺序与其等效的十进制顺序不同;由于减法符号,您需要在 M 之前匹配 CM。
如果这是您需要做的很多事情,那么可能值得创建一个确定性函数(当然,递归 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 等价物要慢得多,但在其他版本和平台上可能会有所不同;就像我说的,只是为了好玩...
推荐阅读
- node.js - 如何使用 express 框架通过 node.js 获取远程客户端的本地 IP 地址?
- python - 使用在线数据增强是 CNN 模型之间的公平比较吗
- scala - 斯卡拉要么与列表
- c# - 使用带有 Gmail OAuth 的 MailKit 发送邮件
- emacs - 在 Emacs 中编辑阿拉伯语-英语文件
- python-3.x - 如何计算熊猫数组结构中每列的字数
- windows - NASM 程序集 - 这个变量后面的“,0”是什么?
- typescript - kendo,typescript - 将 dataBound 事件移动到单独的函数以重用
- javascript - jQuery 地址:$.address.change 被调用两次
- python - Python 函数内存溢出