Převod římských číslic

Školení, která pořádám

Nedávno jsem četl zajímavý článek Make Everything The Same o poznatcích z převodu na římské číslice. Článek je v kostce o tom, že odečítací číslice (např. IX = 9) vyžadují jiné zacházení než normální číslice (např. X = 10). Článek hovoří o podmínce, která má tendence se do kódu vkrádat a popisuje způsob, jak se jí zbavit. Ten spočívá v tom, že místo jedné konverze do finálního tvaru uděláme dvě konverze – první do aditivní formy (nepoužívá odečítací číslice) a druhou z aditivní formy do finální podoby.

Oceňuji přístup se vším zacházet stejně a nepoužívat zbytečně podmínky, ale popsané řešení mi přijde zbytečně krkolomné. Tady je moje řešení:

<?php
class RomanNumerals {
    private static $romanToNumber = array(
        'M' => 1000, 'CM' => 900,
        'D' => 500, 'CD' => 400,
        'C' => 100, 'XC' => 90,
        'L' => 50, 'XL' => 40,
        'X' => 10, 'IX' => 9,
        'V' => 5, 'IV' => 4,
        'I' => 1,
    );

    /** Převod na římské číslice
    * @param int
    * @return string
    * @copyright Jakub Vrána, https://php.vrana.cz/
    */
    static function toRoman($n) {
        $return = '';
        foreach (self::$romanToNumber as $roman => $number) {
            $times = floor($n / $number);
            $return .= str_repeat($roman, $times);
            $n -= $number * $times;
        }
        return $return;
    }
}
?>

Vytvoříme si jen jednu mapu, ve které budou všechny převody a projdeme ji. Žádné podmínky v kódu nejsou. Mapa by se dala vytvořit i programově, ale to by bylo poměrně krkolomné.

Převod z římských číslic

Mnohem zajímavější je převod z římských číslic. Pro ten můžeme použít stejnou převodní mapu a následující kód:

<?php
class RomanNumerals {
    /** Převod z římských číslic
    * @param string
    * @return int
    * @copyright Jakub Vrána, https://php.vrana.cz/
    */
    static function fromRoman($s) {
        $return = 0;
        $pos = 0;
        foreach (self::$romanToNumber as $roman => $number) {
            while ($pos < strlen($s) && substr_compare($s, $roman, $pos, strlen($roman)) == 0) {
                $return += $number;
                $pos += strlen($roman);
            }
        }
        if ($pos != strlen($s)) {
            throw new Exception("Invalid roman number: $s");
        }
        return $return;
    }
}
?>

Kód kontroluje, jestli jsou číslice seřazené od nejvyšší po nejnižší a jestli řetězec neobsahuje nepovolený znak (což jsou nutné podmínky při zápisu římských číslic). Nekontroluje počet opakování jednotlivých číslic (např. IIIII = 5 nebo IXIX = 18 projde) ani kompaktnost zápisu (např. VIV = 9 projde, i když se dá zapsat jako IX = 9). Podle článku na Wikipedii jsou ale takové zápisy možné. Pokud bychom chtěli zkontrolovat, jestli je číslo v kanonické formě, tak by asi nejjednodušší bylo na výsledku zavolat toRoman a podívat se, jestli vrátilo náš vstup.

Viz též můj starší článek na stejné téma, který jsem objevil až po dopsání tohoto. A protože u minulého článku bylo nejvíc dotazů na to, jak se převede nějaké konkrétní číslo, tak ještě formulář:

Arabské nebo římské číslo:

Jakub Vrána, Řešení problému, 17.6.2016, diskuse: 3 (nové: 0)

Diskuse

honzík:

Omlouvám se za začátečnický dotaz, ale nemůžu přijít na to, jak převést aktuální rok <?php Date("Y") ?> na římské číslice. Poradíte? Díky!

Spectator:

Například skriptem viz výše.

ADELA MRKVICKOVA:

DEKUJI JSOU TO NEJLEPSI STRANKY NA PREVOD RIMSKYCH CISLIC.

Diskuse je zrušena z důvodu spamu.

avatar © 2005-2024 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.