Как преобразовать римские цифры в десятичные числа? - PullRequest
0 голосов
/ 13 ноября 2018

Преобразует десятичное число в римские цифры:

select  to_char(515, 'RN') from dual;

Возвращает: DXV

Как сделать обратное?Это бросает ORA-01722: Invalid number:

select to_number('DXV', 'RN') from dual;

Ответы [ 4 ]

0 голосов
/ 13 ноября 2018

Просто для удовольствия, альтернатива, которая разбивает строку на группы одиночных и смежных цифр, позволяя использовать стандартную вычитающую нотацию (спасибо, Википедия ; преобразует каждую одиночную или смежные пары или цифры в их десятичные числа эквиваленты, а затем суммирует их:

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 для меня, но может отличаться на других версиях и платформах; и, как я уже сказал, просто для удовольствия ...

0 голосов
/ 13 ноября 2018

Попробуйте выполнить приведенный ниже запрос для поиска римских цифр в десятичных числах:

ссылка - https://livesql.oracle.com/apex/livesql/file/content_CESOH7H2D4O88XLW60330Q3L9.html

WITH Roman (Numeral) AS
(select 'DXV' as Numeral from dual),
RomToDec (ThisVal, ThisOne, ThisDec, LastDec, Remaining, Pos) AS
(SELECT 0 as ThisVal, cast(null as varchar2(4000)) as ThisOne, 
0 as ThisDec, 0 as LastDec,
Roman.Numeral as Remaining , length(Roman.Numeral) as Pos 
FROM Roman
UNION ALL
SELECT 
case 
  when RomToDec.ThisDec >= RomToDec.LastDec then RomToDec.ThisVal + ThisDec 
  else RomToDec.ThisVal-ThisDec
END as ThisVal, 
substr(RomToDec.Remaining,length(RomToDec.Remaining),1)  as ThisOne, 
case substr(RomToDec.Remaining,length(RomToDec.Remaining),1)  
  when 'M' then 1000
  when 'D' then 500
  when 'C' then 100
  when 'L' then 50
  when 'X' then 10
  when 'V' then 5
  when 'I' then 1
else 0 END as ThisDec,
RomToDec.ThisDec as LastDec,
substr(RomToDec.Remaining,1,length(RomToDec.Remaining)-1) as Remaining,
length(RomToDec.Remaining)-1 as Pos
from RomToDec                           
where Pos >= 0
)
select thisVal
from RomToDec 
where pos is null
0 голосов
/ 13 ноября 2018

Этот код скопирован с сайта LiveSQL (https://livesql.oracle.com/apex/livesql/file/content_CESOH7H2D4O88XLW60330Q3L9.html). Автор - г-жа Наталка Рошак.

Преобразование римской цифры в десятичный формат с использованием рекурсивного подзапроса. Передайте римское число для преобразования, поместив его во встроенную таблицу ROMAN в предложении With.

SQL> WITH
  2    roman ( numeral ) AS
  3    ( SELECT 'MCMLXXXVII' AS numeral FROM dual),
  4  romtodec (
  5    thisval,
  6    thisone,
  7    thisdec,
  8    lastdec,
  9    remaining,
 10    pos
 11  ) AS ( SELECT 0 AS thisval,
 12                CAST(NULL AS VARCHAR2(4000) ) AS thisone,
 13                0 AS thisdec,
 14                0 AS lastdec,
 15                roman.numeral AS remaining,
 16                length(roman.numeral) AS pos
 17         FROM roman
 18  UNION ALL
 19  SELECT
 20    CASE
 21      WHEN romtodec.thisdec >= romtodec.lastdec THEN romtodec.thisval + thisdec
 22      ELSE romtodec.thisval - thisdec
 23    END
 24  AS thisval,
 25    substr(romtodec.remaining,length(romtodec.remaining),1) AS thisone,
 26    CASE substr(romtodec.remaining,length(romtodec.remaining),1)
 27      WHEN 'M'   THEN 1000
 28      WHEN 'D'   THEN 500
 29      WHEN 'C'   THEN 100
 30      WHEN 'L'   THEN 50
 31      WHEN 'X'   THEN 10
 32      WHEN 'V'   THEN 5
 33      WHEN 'I'   THEN 1
 34      ELSE 0
 35    END
 36  AS thisdec,
 37    romtodec.thisdec AS lastdec,
 38    substr(romtodec.remaining,1,length(romtodec.remaining) - 1) AS remaining,
 39    length(romtodec.remaining) - 1 AS pos
 40  FROM romtodec
 41  WHERE pos >= 0
 42  ) SELECT thisval
 43    FROM romtodec
 44    WHERE pos IS NULL;

   THISVAL
----------
      1987

SQL>
0 голосов
/ 13 ноября 2018

Я думаю, вам нужна функция TO_NUMBER.

что-то вроде: ... to_number('XIV', 'RN') ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...