# RomanSol.py # the 2016sp cs 1110 profs (cs-1110profs-l@cornell.edu) # Feb 25, 2016 """ A framework for checking Roman numeral strings and their value.""" def AllCharsOK(R): """Returns True if every character in R is in 'MDCLXVI' and returns False if not. PreC: R is a string of length > 0.""" NumGoodChars = (R.count('I') + R.count('V') + R.count('X') + R.count('L') + R.count('C') + R.count('D') + R.count('M')) return NumGoodChars==len(R) # Alternate implementation def AllCharsOK2(R): """Returns True if every character in R is in 'MDCLXVI' and returns False if not. PreC: R is a string of length > 0""" roman_chars = 'MDCLXVI' for c in R: if c not in roman_chars: return False return True def AllFreqsOK(R): """ Returns True if R is a frequency-legal string" PreC: R is a string of length > 0 """ B = ( R.count('M')<=3 and R.count('D')<=1 and R.count('C')<=3 and R.count('L')<=1 and R.count('X')<=3 and R.count('V')<=1 and R.count('I')<=3) return B # alternate implementation def AllFreqsOK2(R): """ Returns True if R is a frequency-legal string" PreC: R is a string of length > 0 """ at_most_three='MCXI' at_most_one='DLV' for c in at_most_three: if R.count(c) > 3: return False for c in at_most_one: if R.count(c) > 1: return False # all conditions above passed return True def SingleOK(c,s,R): """ Returns True if c is not in R or if c is in R but not preceded by any character in s. Otherwise, False is returned, PreC: c is a character, s is a nonempty string, R is a nonempty string, and c is not in s. """ for x in s: if (x in R) and (R.rfind(c) > R.find(x)): return False # if here, there isn't any x in R that occurs before the last c. return True # Note: rfind is just like find except it looks for the location # of the last occurrence. # alternate solution more like what's given in the hints def SingleOK2(c,s,R): """ Returns True if c is not in R or if c is in R but not preceded by any character in s. Otherwise, False is returned, PreC: c is a character, s is a nonempty string, R is a nonempty string, and c is not in s. """ if c not in R: return True else: for character in s: if character in R and R.rfind(c) > R.find(character): return False return True def AllSinglesOK(R): """Returns True if and only if R is an single-legal string. PreC: R is a non-null string. """ M_ok = SingleOK('M','DLXVI',R) D_ok = SingleOK('D','LXVI',R) C_ok = SingleOK('C','LVI',R) L_ok = SingleOK('L','VI',R) X_ok = SingleOK('X','V',R) return M_ok and D_ok and C_ok and L_ok and X_ok def DoubleOK(s,R): """ Returns True if s is not in R or if s occurs once in R and the first occurrence of s[0] is the first occurrence of s. PreC: s is a length-2 string and R is a nonempty string. """ if R.count(s)==0: return True else: return R.find(s)==R.find(s[0]) and R.count(s)==1 def AllDoublesOK(R): """Returns True if and only if R is a double-legal string. PreC: R is a non-null string. """ C_ok = DoubleOK('CD',R) and DoubleOK('CM',R) X_ok = DoubleOK('XL',R) and DoubleOK('XC',R) I_ok = DoubleOK('IV',R) and DoubleOK('IX',R) return C_ok and X_ok and I_ok def Value(R): """ Returns an int that is the value of R PreC: R is a Roman numeral string. """ # The preliminary value s = (R.count('M')*1000 + R.count('D')*500 + R.count('C')*100 + R.count('L')*50+ R.count('X')*10 + R.count('V')*5 + R.count('I')) # The adjustments... # in acceptable strings, there can't be both 'CM' and 'CD' if R.find('CM')>=0 or R.find('CD')>=0: s = s-200 # in acceptable strings, there can't be both 'XL' and 'XC' if R.find('XL')>=0 or R.find('XC')>=0: s = s - 20 # in acceptable strings, there can't be both 'IX' and 'IV' if R.find('IX')>=0 or R.find('IV')>=0: s = s-2 return s if __name__ == '__main__': # an alternate testing scheme using lists of lists and lists of functions list_debug=True if list_debug: print '(partially) testing AllChars functions' answers = [['IXIIIDDCM', True],['3X', False],['XXX', True]] for answer in answers: for fn in [AllCharsOK, AllCharsOK2]: response = fn(answer[0]) if response != answer[1]: print "function", fn, ", input was", answer[0], "but output was",str(response) print '(partially) testing AllFreqs functions' answers = [ ['IXIILDMVX', True], ['MM1984XVI', True], ['XXXX', False], ['D3D', False]] for answer in answers: for fn in [AllFreqsOK, AllFreqsOK2]: response = fn(answer[0]) if response != answer[1]: print "function", fn, ", input was", answer[0], "but output was",str(response) print '(partially) testing SingleOK functions' answers = [ ['C', 'LVI', 'MDV', True], ['C', 'LVI', 'MCCV', True], ['C', 'LVI', 'MCVCV', False], ['M', 'DLXVI', 'CIMVM', False], ['M', 'DLXVI', 'MCMXIV', True]] for answer in answers: for fn in [SingleOK, SingleOK2]: response = fn(answer[0], answer[1], answer[2]) if response != answer[3]: print "function", fn, ", input was", answer[0], answer[1], answer[2], "but output was",str(response) print '(partially) testing AllSinglesOK functions' answers = [ ['MCMXIV', True], ['CIMVM', False]] for answer in answers: for fn in [AllSinglesOK]: response = fn(answer[0]) if response != answer[1]: print "function", fn, ", input was", answer[0], "but output was",str(response) print '(partially) testing DoubleOK functions' answers = [ ['CM','MCMXC', True], ['CM','MCX', True], ['CM', 'MCXCM', False]] for answer in answers: for fn in [DoubleOK]: response = fn(answer[0], answer[1]) if response != answer[2]: print "function", fn, ", input was", answer[0],answer[1], "but output was",str(response) print 'all tests done' # the regular "test on the command-line" testing scheme R = raw_input('Enter a Roman Numeral String (do not surround with quotes): ') print 'AllCharsOK(R) is ',AllCharsOK(R) print 'AllFreqsOK(R) is ',AllFreqsOK(R) print 'AllSinglesOK(R) is ',AllSinglesOK(R) print 'AllDoublesOK(R) is ',AllDoublesOK(R) OK = AllCharsOK(R) and AllFreqsOK(R) and AllSinglesOK(R) and AllDoublesOK(R) if OK: print 'Value = %1d' % Value(R) else: print 'Not a valid Roman numeral string.'