forked from UWPCE-PythonCert/ProgrammingInPython
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathroman12.py
More file actions
232 lines (183 loc) · 6.19 KB
/
Copy pathroman12.py
File metadata and controls
232 lines (183 loc) · 6.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
"""
roman.py
A Roman numeral to arabic numeral (and back!) converter
complete with tests
tests are expected to be able to be run with the pytest system
"""
import pytest
roman_numeral_map = (('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))
def to_roman(n):
"""convert integer to Roman numeral"""
if not (0 < n < 4000):
raise ValueError("number out of range (must be 1..3999)")
if int(n) != n:
raise ValueError("Only integers can be converted to Roman numerals")
result = ''
for numeral, integer in roman_numeral_map:
while n >= integer:
result += numeral
n -= integer
return result
def is_valid_roman_numeral(s):
"""
check if the input is a valid roman numeral
returns True if it is, False other wise
"""
# does it use only valid characters?
for c in s:
if c not in "MDCLXVI":
return False
return True
def from_roman(s):
"""convert Roman numeral to integer"""
if not is_valid_roman_numeral(s):
raise ValueError(f"{s} is not a valid Roman numeral")
result = 0
index = 0
for numeral, integer in roman_numeral_map:
while s[index:index + len(numeral)] == numeral:
result += integer
index += len(numeral)
# print(f'found, {numeral} of length, {len(numeral)} adding {integer}')
return result
#####################################
# Tests for roman numeral conversion
#####################################
KNOWN_VALUES = ( (1, 'I'),
(2, 'II'),
(3, 'III'),
(4, 'IV'),
(5, 'V'),
(6, 'VI'),
(7, 'VII'),
(8, 'VIII'),
(9, 'IX'),
(10, 'X'),
(50, 'L'),
(100, 'C'),
(500, 'D'),
(1000, 'M'),
(31, 'XXXI'),
(148, 'CXLVIII'),
(294, 'CCXCIV'),
(312, 'CCCXII'),
(421, 'CDXXI'),
(528, 'DXXVIII'),
(621, 'DCXXI'),
(782, 'DCCLXXXII'),
(870, 'DCCCLXX'),
(941, 'CMXLI'),
(1043, 'MXLIII'),
(1110, 'MCX'),
(1226, 'MCCXXVI'),
(1301, 'MCCCI'),
(1485, 'MCDLXXXV'),
(1509, 'MDIX'),
(1607, 'MDCVII'),
(1754, 'MDCCLIV'),
(1832, 'MDCCCXXXII'),
(1993, 'MCMXCIII'),
(2074, 'MMLXXIV'),
(2152, 'MMCLII'),
(2212, 'MMCCXII'),
(2343, 'MMCCCXLIII'),
(2499, 'MMCDXCIX'),
(2574, 'MMDLXXIV'),
(2646, 'MMDCXLVI'),
(2723, 'MMDCCXXIII'),
(2892, 'MMDCCCXCII'),
(2975, 'MMCMLXXV'),
(3051, 'MMMLI'),
(3185, 'MMMCLXXXV'),
(3250, 'MMMCCL'),
(3313, 'MMMCCCXIII'),
(3408, 'MMMCDVIII'),
(3501, 'MMMDI'),
(3610, 'MMMDCX'),
(3743, 'MMMDCCXLIII'),
(3844, 'MMMDCCCXLIV'),
(3888, 'MMMDCCCLXXXVIII'),
(3940, 'MMMCMXL'),
(3999, 'MMMCMXCIX'),
)
def test_to_roman_known_values():
"""
to_roman should give known result with known input
"""
for integer, numeral in KNOWN_VALUES:
result = to_roman(integer)
assert numeral == result
def test_too_large():
"""
to_roman should raise an ValueError when passed
values over 3999
"""
with pytest.raises(ValueError):
to_roman(4000)
def test_zero():
"""to_roman should raise an ValueError with 0 input"""
with pytest.raises(ValueError):
to_roman(0)
def test_negative():
"""to_roman should raise an ValueError with negative input"""
with pytest.raises(ValueError):
to_roman(-1)
def test_non_integer():
"""to_roman should raise an ValueError with non-integer input"""
with pytest.raises(ValueError):
to_roman(0.5)
def test_float_with_integer_value():
"""to_roman should work for floats with integer values"""
assert to_roman(3.0) == "III"
# ####################
# Tests for from_roman
def test_from_roman_known_values():
"""from_roman should give known result with known input"""
for integer, numeral in KNOWN_VALUES:
result = from_roman(numeral)
assert integer == result
def test_roundtrip():
"""from_roman(to_roman(n))==n for all n"""
for integer in range(1, 4000):
numeral = to_roman(integer)
result = from_roman(numeral)
assert integer == result
# #####################
# The following to test for valid roman numerals
def test_invalid_character():
"""
Roman numerals can only use these characters:
M, D, C, L, X, V, I
This tests that other characters will cause a failure
"""
for s in ['Z', 'XXIIIQ', 'QXXIII', 'XXYIII']:
with pytest.raises(ValueError):
from_roman(s)
def test_too_many_repeated_numerals():
'''from_roman should fail with too many repeated numerals'''
for s in ('MMMM', 'DD', 'CCCC', 'LL', 'XXXX', 'VV', 'IIII'):
with pytest.raises(ValueError):
from_roman(s)
def test_repeated_pairs():
'''from_roman should fail with repeated pairs of numerals'''
for s in ('CMCM', 'CDCD', 'XCXC', 'XLXL', 'IXIX', 'IVIV'):
with pytest.raises(ValueError):
from_roman(s)
def test_malformed_antecedents():
'''from_roman should fail with malformed antecedents'''
for s in ('IIMXCC', 'VX', 'DCM', 'CMM', 'IXIV',
'MCMC', 'XCX', 'IVI', 'LM', 'LD', 'LC'):
with pytest.raises(ValueError):
from_roman(s)