LoginSignup
4
0

More than 3 years have passed since last update.

整数をローマ数字に変換する(の返答)

Posted at

はじめに

Delphi Advent Calenar 2019 の 12/2 の DEKO さんの記事「整数をローマ数字 (文字列) に変換するコードを書いてみる」に

私はこの問題を Delphi 10.3 Rio で解くことにしました。この辺で一旦読むのをやめて、各自お好きな言語でこの課題にチャレンジしてみるのもいいかと思います。

とあったので、僕も作ってみました。
同じ Delphi 10.3.3 で作ったのでちょっと趣旨が違いますが解き方が違うので大目に見て…

考え方

剰余

4 と 9 がネックになる事は判っていました。
この2つだけ次の桁の数字から1つ引くという特殊な書き方になるからです。
ただ、どちらの数も 5 との剰余は 4 になります。

N mod 5 = 4 // 剰余が 4 の場合は N は 4 or 9

文字列化

ローマ数字はその書き方の制約上、3999 より大きい数を表せません。
つまり、最大の大きさでもわずか4桁の数に過ぎません。
ですので、これを数として計算せずに文字列化してもたった4文字。処理上大きな負担にはならないはずです。
今回は上の剰余を使いたいと考えたので、文字列化しました。

まとめると

  • 剰余が 4 の場合は次の数から今の数を引く書き方にする
  • 数を4文字の文字列化して先頭から処理する

として作りました。

アラビア数字→ローマ数字

では実際のソースです。

ローマ数字化
function ToRoman(const iArabic: Integer): String;
const // S5 は 5 * 10^n を表すシンボル, S1 は 1 * 10^n を表すシンボル
  ROMAN_SYMBOLS: array [-1.. 3] of record S5, S1: Char end = (
    (S5: ' '; S1: ' '), // 範囲チェックを横着するためのダミー
    (S5: ' '; S1: 'M'),
    (S5: 'D'; S1: 'C'),
    (S5: 'L'; S1: 'X'),
    (S5: 'V'; S1: 'I')
  );
begin
  if (iArabic > 3999) or (iArabic < 1) then
    Exit('');

  var V := Int32(iArabic).ToString;
  V := (StringOfChar('0', 4) + V).SubString(V.Length, 4); // 必ず4文字の文字列にする

  var SB := TStringBuilder.Create;
  try
    for var i := 0 to 3 do // 4文字しかないので 0~3 で OK
    begin
      var C := ROMAN_SYMBOLS[i];
      var P := ROMAN_SYMBOLS[i - 1]; // 現在の桁数より大きい桁のシンボル

      var N := StrToIntDef(V.Chars[i], 0);
      var F := N > 4;
      var R := N mod 5;

      if (R = 4) then // 剰余が 4 か?
      begin
        SB.Append(C.S1);
        SB.Append(IfThen(F, P.S1, C.S5))
      end
      else
      begin
        if F then
          SB.Append(C.S5);
        SB.Append(C.S1, R);
      end;
    end;

    Result := SB.ToString;
  finally
    SB.DisposeOf;
  end;
end;
出力結果
   0:
  12: XII
  24: XXIV
  42: XLII
  49: XLIX
  89: LXXXIX
 299: CCXCIX
 493: CDXCIII
1960: MCMLX
3999: MMMCMXCIX

ローマ数字→アラビア数字(おまけ)

ローマ数字化とこのアラビア数字化で、同じシンボルを別々の構造体に持っているのがカッコ悪いですね…

アラビア数字化
function ToAlabic(const iRomanNum: String): Integer;
const
  ROMANS: array [-1.. 6] of record Number: Integer; Symbol: Char; end = (
    (Number:    0; Symbol: ' '),
    (Number: 1000; Symbol: 'M'),
    (Number:  500; Symbol: 'D'),
    (Number:  100; Symbol: 'C'),
    (Number:   50; Symbol: 'L'),
    (Number:   10; Symbol: 'X'),
    (Number:    5; Symbol: 'V'),
    (Number:    1; Symbol: 'I')
  );
begin
  Result := 0;

  var i := 0;
  var j := 0;
  var Len := iRomanNum.Length;
  while i < Len do
  begin
    var C := iRomanNum.Chars[i];
    var N := iRomanNum.Chars[i + 1];

    var CR := ROMANS[j];
    var NR := ROMANS[j - 1];

    if N = NR.Symbol then
    begin
      var D := 1;
      while D < NR.Number do
        D := D * 10;

      Inc(Result, NR.Number - D div 10);
      Inc(i, 2);
    end
    else
    begin
      if C = CR.Symbol then
      begin
        Inc(Result, CR.Number);
        Inc(i);
      end
      else
        Inc(j);
    end
  end;
end;
出力結果
         :    0
      XII:   12
     XXIV:   24
     XLII:   42
     XLIX:   49
   LXXXIX:   89
   CCXCIX:  299
  CDXCIII:  493
    MCMLX: 1960
MMMCMXCIX: 3999

上記の出力結果を出すルーチン

標準手続き Write で使える桁数指定を使っているので、珍しいかもしれないなと思ってこちらも掲載。

program RomanNum;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  uRomanNumHelper in 'uRomanNumHelper.pas';

const
  NUMS: array[0.. 9] of Integer = (
    0, 12, 24, 42, 49, 89, 299, 493, 1960, 3999
  );
begin
  // アラビア数字→ローマ数字
  Writeln('Alabic to Roman');
  for var N in NUMS do
    Writeln(N:4, ': ', ToRoman(N));

  Writeln;

  // ローマ数字→アラビア数字
  Writeln('Roman to Alabic');
  for var N in NUMS do
  begin
    var R := ToRoman(N);
    Writeln(R:9, ': ', ToAlabic(R):4);
  end;

  Readln;
end.

最後に

DEKO さんの解き方の方がエレガント!!
そして、この課題、大学の情報の実習で解かされた気がする…

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0