I recently came across a email bot that would give you random problems to solve.
Here is the problem.
Please write a function in python which receives a natural decimal N-digit number (1 to 15 digits long) as an input and returns the minimal next N-digit number with the same sum of digits or -1 if there isn't one. Try not to use brute force.
Examples: Input: Output: *123 132 *0200 1001 *09999999999999 18999999999999 *90 -1 *9999 -1
I have written my solution and would like it to be reviewed. Could this be done better? Should testing be done in a different way?
test_generators.py
import pytest
from generators import NumberGenerator
@pytest.fixture
def max_number():
return "12345678901234567890"
@pytest.fixture
def trailing_zero_none():
return "1234"
@pytest.fixture
def trailing_zero_three():
return "1234000"
@pytest.fixture
def trailing_zero_trick():
return "12039000"
@pytest.fixture
def num_one():
return "468992000"
@pytest.fixture
def num_one_step_one():
return "468992"
@pytest.fixture
def num_one_step_two():
return "46899"
@pytest.fixture
def num_one_step_three():
return "468"
def test_next_number_construtor_str():
NumberGenerator("10")
def test_next_number_construtor_int():
with pytest.raises(TypeError):
NumberGenerator(10)
def test_max_number_length(max_number):
with pytest.raises(ValueError):
NumberGenerator(max_number)
def test_find_trailing_zeros(trailing_zero_none):
assert NumberGenerator(trailing_zero_none).get_trailing_zeros() == ""
def test_find_trailing_zeros_three(trailing_zero_three):
assert NumberGenerator(trailing_zero_three).get_trailing_zeros() == "000"
def test_find_trailing_zeros_trick(trailing_zero_trick):
assert NumberGenerator(trailing_zero_trick).get_trailing_zeros() == "000"
def test_get_lowest_non_zero_digit(trailing_zero_three):
num = NumberGenerator(trailing_zero_three)
num.get_trailing_zeros()
assert num.get_lowest_non_zero_digit() == 4
def test_get_lowest_non_zero_digit(trailing_zero_trick):
num = NumberGenerator(trailing_zero_trick)
num.get_trailing_zeros()
assert num.get_lowest_non_zero_digit() == 9
def test_get_consectutive_nines(trailing_zero_trick):
num = NumberGenerator(trailing_zero_trick)
num.get_trailing_zeros()
num.get_lowest_non_zero_digit()
assert num.get_consecutive_nines() == ""
def test_get_consectutive_nines():
num = NumberGenerator("468992000")
num.get_trailing_zeros()
num.get_lowest_non_zero_digit()
assert num.get_consecutive_nines() == "99"
def test_get_number_one(num_one, num_one_step_one):
num = NumberGenerator(num_one)
num.get_trailing_zeros()
assert num.get_number() == int(num_one_step_one)
def test_get_number_two(num_one, num_one_step_two):
num = NumberGenerator(num_one)
num.get_trailing_zeros()
num.get_lowest_non_zero_digit()
assert num.get_number() == int(num_one_step_two)
def test_get_number_three(num_one, num_one_step_three):
num = NumberGenerator(num_one)
num.get_trailing_zeros()
num.get_lowest_non_zero_digit()
num.get_consecutive_nines()
assert num.get_number() == int(num_one_step_three)
def test_next_number_one():
assert NumberGenerator("123").get_next_number() == 132
def test_next_number_two():
assert NumberGenerator("0200").get_next_number() == 1001
def test_next_number_three():
assert NumberGenerator("09999999999999").get_next_number() == 18999999999999
def test_next_number_four():
assert NumberGenerator("90").get_next_number() == -1
def test_next_number_five():
assert NumberGenerator("9999").get_next_number() == -1
generators.py
import re
class NumberGenerator(object):
def __rtrim_number__(self, index):
self.__number = self.__number[:index]
def __init__(self, number):
if len(number) > 15:
raise ValueError("Number cannot be larger than 15 digits")
self.__number = number
def get_trailing_zeros(self):
result = re.search("(0+)\\b", self.__number)
if result:
self.__rtrim_number__(result.start(1))
return result.group(1)
else:
return ""
def get_lowest_non_zero_digit(self):
digit = self.__number[-1]
self.__rtrim_number__(-1)
return int(digit)
def get_consecutive_nines(self):
result = re.search("(9+)\\b", self.__number)
if result:
self.__rtrim_number__(result.start(1))
return result.group(1)
else:
return ""
def get_number(self):
if len(self.__number) > 0:
return int(self.__number)
else:
return -1
def get_next_number(self):
zeros = self.get_trailing_zeros()
lowest_digit = self.get_lowest_non_zero_digit()
nines = self.get_consecutive_nines()
base_digit = self.get_number()
if base_digit >= 0:
digit_str = "{0}{1}{2}{3}".format(base_digit+1, zeros, lowest_digit-1, nines)
return int(digit_str)
else:
return base_digit
910
(or9991000
or ...) as a test-case \$\endgroup\$