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é.
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ář:
Diskuse je zrušena z důvodu spamu.