Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Palindrome products: sync expected test results and input data with problem-specifications. #1814

Merged
merged 8 commits into from
Jun 17, 2019
71 changes: 35 additions & 36 deletions exercises/palindrome-products/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
from math import log10, floor, ceil


def largest_palindrome(max_factor, min_factor):
def largest(min_factor, max_factor):
return get_extreme_palindrome_with_factors(max_factor, min_factor,
"largest")


def smallest_palindrome(max_factor, min_factor):
def smallest(max_factor, min_factor):
return get_extreme_palindrome_with_factors(max_factor, min_factor,
"smallest")

Expand All @@ -17,37 +17,36 @@ def get_extreme_palindrome_with_factors(max_factor, min_factor, extreme):
palindromes_found = palindromes(max_factor, min_factor,
reverse=(extreme == "largest"))
factor_pairs = None
for palin in palindromes_found:
factor_pairs = ((fact, palin // fact)
for fact in range(min_factor, max_factor + 1)
if palin % fact == 0)
for palindrome in palindromes_found:
factor_pairs = ((factor, palindrome // factor)
for factor in range(min_factor, max_factor + 1)
if palindrome % factor == 0)
factor_pairs = list(pair for pair in factor_pairs
if min_factor <= pair[1] <= max_factor)
if len(factor_pairs) > 0:
break

if factor_pairs is None or len(factor_pairs) == 0:
if not factor_pairs:
return (None, [])

return (palin, factor_pairs)
return (palindrome, factor_pairs)


def reverse_num(n):
rev = 0
while n > 0:
rev *= 10
rev += (n % 10)
n //= 10
return rev
def reverse_num(number):
reversed = 0
while number > 0:
reversed *= 10
reversed += (number % 10)
number //= 10
return reversed


def num_digits(n):
return int(floor(log10(n) + 1))
def num_digits(number):
return int(floor(log10(number) + 1))


def palindromes(max_factor, min_factor, reverse=False):
"""Generates all palindromes between `min_factor`**2 and max_factor`**2

If `reverse` is True, will produce the palindromes in decreasing order,
from `max_factor`**2 down to `min_factor`**2. This is needed for
`largest_palindrome`, since it won't have to iterate through a
Expand All @@ -62,55 +61,55 @@ def palindromes(max_factor, min_factor, reverse=False):
minimum = min_factor ** 2
maximum = max_factor ** 2

def gen_palins_of_length(nd, reverse=reverse):
def gen_palindromes_of_length(num_digits, reverse=reverse):
"""Generates all palindromes with `nd` number of digits that are
within the desired range.

Again, if `reverse` is True, the palindromes are generated in
reverse order.
"""
even_nd = (nd % 2 == 0)
even_nd = (num_digits % 2 == 0)

min_left_half = max(10 ** (int(ceil(nd / 2)) - 1),
minimum // (10 ** (nd // 2)))
max_left_half = min((10 ** int(ceil(nd / 2))) - 1,
maximum // (10 ** (nd // 2)))
min_left_half = max(10 ** (int(ceil(num_digits / 2)) - 1),
minimum // (10 ** (num_digits // 2)))
max_left_half = min((10 ** int(ceil(num_digits / 2))) - 1,
maximum // (10 ** (num_digits // 2)))

current_left_half = min_left_half if not reverse else max_left_half

def make_palindrome(left_half, even_nd=False):
right_half = (reverse_num(left_half)
if even_nd
else reverse_num(left_half // 10))
return (left_half * (10 ** (nd // 2))) + right_half
return (left_half * (10 ** (num_digits // 2))) + right_half

if not reverse:
while current_left_half <= max_left_half:
palin = make_palindrome(current_left_half, even_nd)
if minimum <= palin <= maximum:
yield palin
elif palin > maximum:
palindrome = make_palindrome(current_left_half, even_nd)
if minimum <= palindrome <= maximum:
yield palindrome
elif palindrome > maximum:
# since palindromes are generated in increasing order,
# we break out of the loop once we've exceeded the
# maximum value
break
current_left_half += 1
else:
while current_left_half >= min_left_half:
palin = make_palindrome(current_left_half, even_nd)
if minimum <= palin <= maximum:
yield palin
elif palin < minimum:
palindrome = make_palindrome(current_left_half, even_nd)
if minimum <= palindrome <= maximum:
yield palindrome
elif palindrome < minimum:
# since palindromes are generated in decreasing order,
# we break out of the loop once we've gone below the
# minimum value
break
current_left_half -= 1

min_nd, max_nd = num_digits(minimum), num_digits(maximum)
min_nd = num_digits(minimum)
max_nd = num_digits(maximum)

lengths = (range(min_nd, max_nd + 1)
if not reverse
else range(max_nd, min_nd - 1, -1))

return chain(*map(gen_palins_of_length, lengths))
return chain(*map(gen_palindromes_of_length, lengths))
4 changes: 2 additions & 2 deletions exercises/palindrome-products/palindrome_products.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
def largest_palindrome(max_factor, min_factor):
def largest(min_factor, max_factor):
pass


def smallest_palindrome(max_factor, min_factor):
def smallest(min_factor, max_factor):
pass
51 changes: 25 additions & 26 deletions exercises/palindrome-products/palindrome_products_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""
Notes regarding the implementation of smallest_palindrome and
largest_palindrome:
Notes regarding the implementation of smallest and
largest:

Both functions must take two keyword arguments:
max_factor -- int
Expand All @@ -13,70 +13,69 @@

import unittest

from palindrome_products import smallest_palindrome, largest_palindrome
from palindrome_products import smallest, largest


# Tests adapted from `problem-specifications//canonical-data.json` @ v1.2.0

class PalindromeProductsTest(unittest.TestCase):
def test_smallest_palindrome_from_single_digit_factors(self):
value, factors = smallest_palindrome(min_factor=1, max_factor=9)
value, factors = smallest(min_factor=1, max_factor=9)
self.assertEqual(value, 1)
self.assertFactorsEqual(factors, {(1, 1)})
self.assertFactorsEqual(factors, [[1, 1]])

def test_largest_palindrome_from_single_digit_factors(self):
value, factors = largest_palindrome(min_factor=1, max_factor=9)
value, factors = largest(min_factor=1, max_factor=9)
self.assertEqual(value, 9)
self.assertFactorsEqual(factors, {(1, 9), (3, 3)})
self.assertFactorsEqual(factors, [[1, 9], [3, 3]])

def test_smallest_palindrome_from_double_digit_factors(self):
value, factors = smallest_palindrome(min_factor=10, max_factor=99)
value, factors = smallest(min_factor=10, max_factor=99)
self.assertEqual(value, 121)
self.assertFactorsEqual(factors, {(11, 11)})
self.assertFactorsEqual(factors, [[11, 11]])

def test_largest_palindrome_from_double_digit_factors(self):
value, factors = largest_palindrome(min_factor=10, max_factor=99)
value, factors = largest(min_factor=10, max_factor=99)
self.assertEqual(value, 9009)
self.assertFactorsEqual(factors, {(91, 99)})
self.assertFactorsEqual(factors, [[91, 99]])

def test_smallest_palindrome_from_triple_digit_factors(self):
value, factors = smallest_palindrome(min_factor=100, max_factor=999)
value, factors = smallest(min_factor=100, max_factor=999)
self.assertEqual(value, 10201)
self.assertFactorsEqual(factors, {(101, 101)})
self.assertFactorsEqual(factors, [[101, 101]])

def test_largest_palindrome_from_triple_digit_factors(self):
value, factors = largest_palindrome(min_factor=100, max_factor=999)
value, factors = largest(min_factor=100, max_factor=999)
self.assertEqual(value, 906609)
self.assertFactorsEqual(factors, {(913, 993)})
self.assertFactorsEqual(factors, [[913, 993]])

def test_smallest_palindrome_from_four_digit_factors(self):
value, factors = smallest_palindrome(min_factor=1000, max_factor=9999)
value, factors = smallest(min_factor=1000, max_factor=9999)
self.assertEqual(value, 1002001)
self.assertFactorsEqual(factors, {(1001, 1001)})
self.assertFactorsEqual(factors, [[1001, 1001]])

def test_largest_palindrome_from_four_digit_factors(self):
value, factors = largest_palindrome(min_factor=1000, max_factor=9999)
value, factors = largest(min_factor=1000, max_factor=9999)
self.assertEqual(value, 99000099)
self.assertFactorsEqual(factors, {(9901, 9999)})
self.assertFactorsEqual(factors, [[9901, 9999]])

def test_empty_for_smallest_palindrome_if_none_in_range(self):
value, factors = smallest_palindrome(min_factor=1002, max_factor=1003)
value, factors = smallest(min_factor=1002, max_factor=1003)
self.assertIsNone(value)
self.assertFactorsEqual(factors, [])

def test_empty_for_largest_palindrome_if_none_in_range(self):
value, factors = largest_palindrome(min_factor=15, max_factor=15)
value, factors = largest(min_factor=15, max_factor=15)
self.assertIsNone(value)
self.assertFactorsEqual(factors, [])

def test_error_for_smallest_if_min_is_more_than_max(self):
def test_error_for_smallest_palindrome_if_min_is_more_than_max(self):
with self.assertRaisesWithMessage(ValueError):
value, factors = smallest_palindrome(min_factor=10000,
max_factor=1)
value, factors = smallest(min_factor=10000, max_factor=1)

def test_error_for_largest_if_min_is_more_than_max(self):
def test_error_for_largest_palindrome_if_min_is_more_than_max(self):
with self.assertRaisesWithMessage(ValueError):
value, factors = largest_palindrome(min_factor=2, max_factor=1)
value, factors = largest(min_factor=2, max_factor=1)

# Utility functions
def setUp(self):
Expand Down