From 3970e824340d7be3a3f4c19d4136ddf8f28dcee2 Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Sun, 9 Oct 2022 20:41:41 -0600 Subject: [PATCH 1/6] add str2int --- evennia/utils/utils.py | 58 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index 1fed76df21..d4ae968c7d 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -2755,3 +2755,61 @@ def int2str(number, adjective=False): if adjective: return _INT2STR_MAP_ADJ.get(number, f"{number}th") return _INT2STR_MAP_NOUN.get(number, str(number)) + +_STR2INT_MAP = { + "one": 1, "two": 2, "three": 3, + "four": 4, "five": 5, "six": 6, + "seven": 7, "eight": 8, "nine": 9, + "ten": 10, "eleven": 11, "twelve": 12, + "thirteen": 13, "fourteen": 14, "fifteen": 15, + "sixteen": 16, "seventeen": 17, "eighteen": 18, + "nineteen": 19, "twenty": 20, "thirty": 30, + "forty": 40, "fifty": 50, "sixty": 60, + "seventy": 70, "eighty": 80, "ninety": 90, + "hundred": 100, "thousand": 1000, +} +def str2int(number): + """ + Converts a string to an integer. + + Args: + number (str): The string to convert. It can be a digit such as "1", or a number word such as "one". + + Returns: + int: The string represented as an integer. + """ + number = str(number) + try: + # it's a digit already + return int(number) + except: + pass + + if i := _STR2INT_MAP.get(number): + # it's a single number, return it + return i + + number = number.replace(" and "," ") + # split number words by spaces, hyphens and commas, to accommodate multiple styles + numbers = [ word.lower() for word in re.split(r'[-\s\,]',number) if word ] + sums = [] + for word in numbers: + # check if it's a known number-word + if i := _STR2INT_MAP.get(word): + if not len(sums): + # initialize the list with the current value + sums = [i] + else: + # if the previous number was smaller, it's a multiplier + # e.g. the "two" in "two hundred" + if sums[-1] < i: + sums[-1] = sums[-1]*i + # otherwise, it's added on, like the "five" in "twenty five" + else: + sums.append(i) + else: + else: + # invalid number-word, return None to error + return None + return sum(sums) + From 00fe0a2d9d40b1e9e4d57024cde85d12aa347a11 Mon Sep 17 00:00:00 2001 From: InspectorCaracal Date: Sun, 9 Oct 2022 21:01:54 -0600 Subject: [PATCH 2/6] add ordinal case --- evennia/utils/utils.py | 52 ++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index d4ae968c7d..92a5daf18a 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -2757,16 +2757,19 @@ def int2str(number, adjective=False): return _INT2STR_MAP_NOUN.get(number, str(number)) _STR2INT_MAP = { - "one": 1, "two": 2, "three": 3, - "four": 4, "five": 5, "six": 6, - "seven": 7, "eight": 8, "nine": 9, - "ten": 10, "eleven": 11, "twelve": 12, - "thirteen": 13, "fourteen": 14, "fifteen": 15, - "sixteen": 16, "seventeen": 17, "eighteen": 18, - "nineteen": 19, "twenty": 20, "thirty": 30, - "forty": 40, "fifty": 50, "sixty": 60, - "seventy": 70, "eighty": 80, "ninety": 90, - "hundred": 100, "thousand": 1000, + "one": 1, "two": 2, "three": 3, + "four": 4, "five": 5, "six": 6, + "seven": 7, "eight": 8, "nine": 9, + "ten": 10, "eleven": 11, "twelve": 12, + "thirteen": 13, "fourteen": 14, "fifteen": 15, + "sixteen": 16, "seventeen": 17, "eighteen": 18, + "nineteen": 19, "twenty": 20, "thirty": 30, + "forty": 40, "fifty": 50, "sixty": 60, + "seventy": 70, "eighty": 80, "ninety": 90, + "hundred": 100, "thousand": 1000, +} +_STR2INT_ADJS = { + "first": 1, "second": 2, "third": 3, } def str2int(number): """ @@ -2779,17 +2782,35 @@ def str2int(number): int: The string represented as an integer. """ number = str(number) + original_input = number try: # it's a digit already return int(number) except: - pass + # if it's an ordinal number such as "1st", it'll convert to int with the last two characters chopped off + try: + return int(number[:-2]) + except: + pass if i := _STR2INT_MAP.get(number): # it's a single number, return it return i - number = number.replace(" and "," ") + # convert sound changes for generic ordinal numbers + if number[-2:] == "th": + # remove "th" + number = number[:-2] + if number[-1] == "f": + # e.g. twelfth, fifth + number = number[:-1] + "ve" + elif number[-2:] == "ie": + # e.g. twentieth, fortieth + number = number[:-2] + "y" + # custom case for ninth + elif number[-3:] == "nin" + number += "e" + # split number words by spaces, hyphens and commas, to accommodate multiple styles numbers = [ word.lower() for word in re.split(r'[-\s\,]',number) if word ] sums = [] @@ -2808,8 +2829,11 @@ def str2int(number): else: sums.append(i) else: + elif i := STR2INT_ADJS.get(word): + # it's a special adj word; ordinal case will never be a multiplier + sums.append(i) else: - # invalid number-word, return None to error - return None + # invalid number-word, raise ValueError + raise ValueError(f"String {original_input} cannot be converted to int.") return sum(sums) From d4e11abd1dd7cf19652d3321edfe9fe73771c298 Mon Sep 17 00:00:00 2001 From: InspectorCaracal Date: Sun, 9 Oct 2022 21:14:16 -0600 Subject: [PATCH 3/6] add tests --- evennia/utils/tests/test_utils.py | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/evennia/utils/tests/test_utils.py b/evennia/utils/tests/test_utils.py index 8104b3117f..a93ff91f12 100644 --- a/evennia/utils/tests/test_utils.py +++ b/evennia/utils/tests/test_utils.py @@ -696,3 +696,36 @@ class TestDelay(BaseEvenniaTest): timedelay ) # Clock must advance to trigger, even if past timedelay self.assertEqual(self.char1.ndb.dummy_var, "dummy_func ran") + + +class TestIntConversions(TestCase): + def test_int2str(self): + self.assertEqual("three", utils.int2str(3)) + # special adjective conversion + self.assertEqual("3rd", utils.int2str(3, adjective=True)) + # generic adjective conversion + self.assertEqual("5th", utils.int2str(5, adjective=True)) + # No mapping return int as str + self.assertEqual("15", utils.int2str(15)) + + def test_str2int(self): + # simple conversions + self.assertEqual(5, utils.str2int("5")) + + # basic mapped numbers + self.assertEqual(3, utils.str2int("three")) + self.assertEqual(20, utils.str2int("twenty")) + + # multi-place numbers + self.assertEqual(2345, utils.str2int("two thousand, three hundred and thirty-five")) + + # ordinal numbers + self.assertEqual(1, utils.str2int("1st")) + self.assertEqual(1, utils.str2int("first")) + self.assertEqual(4, utils.str2int("fourth")) + # ordinal sound-change conversions + self.assertEqual(5, utils.str2int("fifth")) + self.assertEqual(20, utils.str2int("twentieth")) + + with self.assertRaises(ValueError): + utils.str2int("not a number") \ No newline at end of file From 0ff4097a47119da940c0040f5ba23c7d2a0c275b Mon Sep 17 00:00:00 2001 From: InspectorCaracal Date: Sun, 9 Oct 2022 21:19:05 -0600 Subject: [PATCH 4/6] typos --- evennia/utils/utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index 92a5daf18a..1bf11f73e6 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -2808,7 +2808,7 @@ def str2int(number): # e.g. twentieth, fortieth number = number[:-2] + "y" # custom case for ninth - elif number[-3:] == "nin" + elif number[-3:] == "nin": number += "e" # split number words by spaces, hyphens and commas, to accommodate multiple styles @@ -2828,8 +2828,7 @@ def str2int(number): # otherwise, it's added on, like the "five" in "twenty five" else: sums.append(i) - else: - elif i := STR2INT_ADJS.get(word): + elif i := _STR2INT_ADJS.get(word): # it's a special adj word; ordinal case will never be a multiplier sums.append(i) else: From 18c399602095116e391f7daebacbe86a03305cc6 Mon Sep 17 00:00:00 2001 From: InspectorCaracal Date: Sun, 9 Oct 2022 21:33:11 -0600 Subject: [PATCH 5/6] do single-number check AFTER conversion from ordinal --- evennia/utils/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index 1bf11f73e6..1830ca9946 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -2793,10 +2793,6 @@ def str2int(number): except: pass - if i := _STR2INT_MAP.get(number): - # it's a single number, return it - return i - # convert sound changes for generic ordinal numbers if number[-2:] == "th": # remove "th" @@ -2810,6 +2806,10 @@ def str2int(number): # custom case for ninth elif number[-3:] == "nin": number += "e" + + if i := _STR2INT_MAP.get(number): + # it's a single number, return it + return i # split number words by spaces, hyphens and commas, to accommodate multiple styles numbers = [ word.lower() for word in re.split(r'[-\s\,]',number) if word ] From 734f0dd20eceec88a41f75e6da5c918a19019c5c Mon Sep 17 00:00:00 2001 From: InspectorCaracal Date: Sun, 9 Oct 2022 22:11:20 -0600 Subject: [PATCH 6/6] strip ands, correct test --- evennia/utils/tests/test_utils.py | 2 +- evennia/utils/utils.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/evennia/utils/tests/test_utils.py b/evennia/utils/tests/test_utils.py index a93ff91f12..b8d23d4829 100644 --- a/evennia/utils/tests/test_utils.py +++ b/evennia/utils/tests/test_utils.py @@ -717,7 +717,7 @@ class TestIntConversions(TestCase): self.assertEqual(20, utils.str2int("twenty")) # multi-place numbers - self.assertEqual(2345, utils.str2int("two thousand, three hundred and thirty-five")) + self.assertEqual(2345, utils.str2int("two thousand, three hundred and forty-five")) # ordinal numbers self.assertEqual(1, utils.str2int("1st")) diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index 1830ca9946..6c4b1a6df6 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -2810,6 +2810,9 @@ def str2int(number): if i := _STR2INT_MAP.get(number): # it's a single number, return it return i + + # remove optional "and"s + number = number.replace(" and "," ") # split number words by spaces, hyphens and commas, to accommodate multiple styles numbers = [ word.lower() for word in re.split(r'[-\s\,]',number) if word ]