| 1 | #========================================================================== |
|---|
| 2 | # Roman Numeral Utility Functions |
|---|
| 3 | #========================================================================== |
|---|
| 4 | # Description |
|---|
| 5 | # |
|---|
| 6 | # A set of utility routines for handling and manipulating |
|---|
| 7 | # roman numerals. |
|---|
| 8 | #------------------------------------------------------------------------- |
|---|
| 9 | # Copyright/License |
|---|
| 10 | # |
|---|
| 11 | # This code was originally harvested from the Tcler's |
|---|
| 12 | # wiki at http://wiki.tcl.tk/1823 and as such is free |
|---|
| 13 | # for any use for any purpose. |
|---|
| 14 | #------------------------------------------------------------------------- |
|---|
| 15 | # Modification history |
|---|
| 16 | # |
|---|
| 17 | # 27 Sep 2005 Kenneth Green |
|---|
| 18 | # Original version derived from wiki code |
|---|
| 19 | #------------------------------------------------------------------------- |
|---|
| 20 | |
|---|
| 21 | package provide math::roman 1.0 |
|---|
| 22 | |
|---|
| 23 | #========================================================================== |
|---|
| 24 | # Namespace |
|---|
| 25 | #========================================================================== |
|---|
| 26 | namespace eval ::math::roman { |
|---|
| 27 | namespace export tointeger toroman |
|---|
| 28 | |
|---|
| 29 | # We dont export 'sort' or 'expr' to prevent collision |
|---|
| 30 | # with existing commands. These functions are less likely to be |
|---|
| 31 | # commonly used and have to be accessed as fully-scoped names. |
|---|
| 32 | |
|---|
| 33 | # romanvalues - array that maps roman letters to integer values. |
|---|
| 34 | # |
|---|
| 35 | variable romanvalues |
|---|
| 36 | |
|---|
| 37 | # i2r - list of integer-roman tuples |
|---|
| 38 | variable i2r {1000 M 900 CM 500 D 400 CD 100 C 90 XC 50 L 40 XL 10 X 9 IX 5 V 4 IV 1 I} |
|---|
| 39 | |
|---|
| 40 | # sortkey - list of patterns to supporting sorting of roman numerals |
|---|
| 41 | variable sortkey {IX VIIII L Y XC YXXXX C Z D {\^} ZM {\^ZZZZ} M _} |
|---|
| 42 | variable rsortkey {_ M {\^ZZZZ} ZM {\^} D Z C YXXXX XC Y L VIIII IX} |
|---|
| 43 | |
|---|
| 44 | # Initialise array variables |
|---|
| 45 | array set romanvalues {M 1000 D 500 C 100 L 50 X 10 V 5 I 1} |
|---|
| 46 | } |
|---|
| 47 | |
|---|
| 48 | #========================================================================== |
|---|
| 49 | # Public Functions |
|---|
| 50 | #========================================================================== |
|---|
| 51 | |
|---|
| 52 | #---------------------------------------------------------- |
|---|
| 53 | # Roman numerals sorted |
|---|
| 54 | # |
|---|
| 55 | proc ::math::roman::sort list { |
|---|
| 56 | variable sortkey |
|---|
| 57 | variable rsortkey |
|---|
| 58 | |
|---|
| 59 | foreach {from to} $sortkey { |
|---|
| 60 | regsub -all $from $list $to list |
|---|
| 61 | } |
|---|
| 62 | set list [lsort $list] |
|---|
| 63 | foreach {from to} $rsortkey { |
|---|
| 64 | regsub -all $from $list $to list |
|---|
| 65 | } |
|---|
| 66 | return $list |
|---|
| 67 | } |
|---|
| 68 | |
|---|
| 69 | #---------------------------------------------------------- |
|---|
| 70 | # Roman numerals from integer |
|---|
| 71 | # |
|---|
| 72 | proc ::math::roman::toroman {i} { |
|---|
| 73 | variable i2r |
|---|
| 74 | |
|---|
| 75 | set res "" |
|---|
| 76 | foreach {value roman} $i2r { |
|---|
| 77 | while {$i>=$value} { |
|---|
| 78 | append res $roman |
|---|
| 79 | incr i -$value |
|---|
| 80 | } |
|---|
| 81 | } |
|---|
| 82 | return $res |
|---|
| 83 | } |
|---|
| 84 | |
|---|
| 85 | #---------------------------------------------------------- |
|---|
| 86 | # Roman numerals parsed into integer: |
|---|
| 87 | # |
|---|
| 88 | proc ::math::roman::tointeger {s} { |
|---|
| 89 | variable romanvalues |
|---|
| 90 | |
|---|
| 91 | set last 99999 |
|---|
| 92 | set res 0 |
|---|
| 93 | foreach i [split [string toupper $s] ""] { |
|---|
| 94 | if { [catch {set val $romanvalues($i)}] } { |
|---|
| 95 | return -code error "roman::tointeger - un-Roman digit $i in $s" |
|---|
| 96 | } |
|---|
| 97 | incr res $val |
|---|
| 98 | if { $val > $last } { |
|---|
| 99 | incr res [::expr -2*$last] |
|---|
| 100 | } |
|---|
| 101 | set last $val |
|---|
| 102 | } |
|---|
| 103 | return $res |
|---|
| 104 | } |
|---|
| 105 | |
|---|
| 106 | #---------------------------------------------------------- |
|---|
| 107 | # Roman numeral arithmetic |
|---|
| 108 | # |
|---|
| 109 | proc ::math::roman::expr args { |
|---|
| 110 | |
|---|
| 111 | if { [string first \$ $args] >= 0 } { |
|---|
| 112 | set args [uplevel subst $args] |
|---|
| 113 | } |
|---|
| 114 | |
|---|
| 115 | regsub -all {[^IVXLCDM]} $args { & } args |
|---|
| 116 | foreach i $args { |
|---|
| 117 | catch {set i [tointeger $i]} |
|---|
| 118 | lappend res $i |
|---|
| 119 | } |
|---|
| 120 | return [toroman [::expr $res]] |
|---|
| 121 | } |
|---|
| 122 | |
|---|
| 123 | #========================================================== |
|---|
| 124 | # Developer test code |
|---|
| 125 | # |
|---|
| 126 | if { 0 } { |
|---|
| 127 | |
|---|
| 128 | puts "Basic int-to-roman-to-int conversion test" |
|---|
| 129 | for { set i 0 } {$i < 50} {incr i} { |
|---|
| 130 | set r [::math::roman::toroman $i] |
|---|
| 131 | set j [::math::roman::tointeger $r] |
|---|
| 132 | puts [format "%5d %-15s %s" $i $r $j] |
|---|
| 133 | if { $i != $j } { |
|---|
| 134 | error "Invalid conversion: $i -> $r -> $j" |
|---|
| 135 | } |
|---|
| 136 | } |
|---|
| 137 | |
|---|
| 138 | puts "" |
|---|
| 139 | puts "roman arithmetic test" |
|---|
| 140 | set x 23 |
|---|
| 141 | set xr [::math::roman::toroman $x] |
|---|
| 142 | set y 77 |
|---|
| 143 | set yr [::math::roman::toroman $y] |
|---|
| 144 | set xr+yr [::math::roman::expr $xr + $yr] |
|---|
| 145 | set yr-xr [::math::roman::expr $yr - $xr] |
|---|
| 146 | set xr*yr [::math::roman::expr $xr * $yr] |
|---|
| 147 | set yr/xr [::math::roman::expr $yr / $xr] |
|---|
| 148 | set yr/xr2 [::math::roman::expr {$yr / $xr}] |
|---|
| 149 | puts "$x + $y\t\t= [expr $x + $y]" |
|---|
| 150 | puts "$x * $y\t\t= [expr $x * $y]" |
|---|
| 151 | puts "$y - $x\t\t= [expr $y - $x]" |
|---|
| 152 | puts "$y / $x\t\t= [expr $y / $x]" |
|---|
| 153 | puts "$xr + $yr\t= ${xr+yr} = [::math::roman::tointeger ${xr+yr}]" |
|---|
| 154 | puts "$xr * $yr\t= ${xr*yr} = [::math::roman::tointeger ${xr*yr}]" |
|---|
| 155 | puts "$yr - $xr\t= ${yr-xr} = [::math::roman::tointeger ${yr-xr}]" |
|---|
| 156 | puts "$yr / $xr\t= ${yr/xr} = [::math::roman::tointeger ${yr/xr}]" |
|---|
| 157 | puts "$yr / $xr\t= ${yr/xr2} = [::math::roman::tointeger ${yr/xr2}]" |
|---|
| 158 | |
|---|
| 159 | puts "" |
|---|
| 160 | puts "roman sorting test" |
|---|
| 161 | set l {X III IV I V} |
|---|
| 162 | puts "IN : $l" |
|---|
| 163 | puts "OUT: [::math::roman::sort $l]" |
|---|
| 164 | } |
|---|