Exercism v3 launches on Sept 1st 2021. Learn more! ๐๐๐

# rootulp's solution

## to Palindrome Products in the Python Track

Published at Jul 13 2018 · 0 comments
Instructions
Test suite
Solution

#### Note:

This solution was written on an old version of Exercism. The tests below might not correspond to the solution code, and the exercise may have changed since this code was written.

Detect palindrome products in a given range.

A palindromic number is a number that remains the same when its digits are reversed. For example, `121` is a palindromic number but `112` is not.

Given a range of numbers, find the largest and smallest palindromes which are products of numbers within that range.

Your solution should return the largest and smallest palindromes, along with the factors of each within the range. If the largest or smallest palindrome has more than one pair of factors within the range, then return all the pairs.

## Example 1

Given the range `[1, 9]` (both inclusive)...

And given the list of all possible products within this range: `[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 15, 21, 24, 27, 20, 28, 32, 36, 25, 30, 35, 40, 45, 42, 48, 54, 49, 56, 63, 64, 72, 81]`

The palindrome products are all single digit numbers (in this case): `[1, 2, 3, 4, 5, 6, 7, 8, 9]`

The smallest palindrome product is `1`. Its factors are `(1, 1)`. The largest palindrome product is `9`. Its factors are `(1, 9)` and `(3, 3)`.

## Example 2

Given the range `[10, 99]` (both inclusive)...

The smallest palindrome product is `121`. Its factors are `(11, 11)`. The largest palindrome product is `9009`. Its factors are `(91, 99)`.

## Exception messages

Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. Not every exercise will require you to raise an exception, but for those that do, the tests will only pass if you include a message.

To raise a message with an exception, just write it as an argument to the exception type. For example, instead of `raise Exception`, you should write:

``````raise Exception("Meaningful message indicating the source of the error")
``````

## Running the tests

To run the tests, run the appropriate command below (why they are different):

• Python 2.7: `py.test palindrome_products_test.py`
• Python 3.4+: `pytest palindrome_products_test.py`

Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version): `python -m pytest palindrome_products_test.py`

### Common `pytest` options

• `-v` : enable verbose output
• `-x` : stop running tests on first failure
• `--ff` : run failures from previous test before running other test cases

For other options, see `python -m pytest -h`

## Submitting Exercises

Note that, when trying to submit an exercise, make sure the solution is in the `\$EXERCISM_WORKSPACE/python/palindrome-products` directory.

You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.

For more detailed information about running tests, code style and linting, please see the help page.

## Source

Problem 4 at Project Euler http://projecteuler.net/problem=4

## Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

### palindrome_products_test.py

``````"""
Notes regarding the implementation of smallest_palindrome and
largest_palindrome:

Both functions must take two keyword arguments:
max_factor -- int
min_factor -- int, default 0

Their return value must be a tuple (value, factors) where value is the
palindrome itself, and factors is an iterable containing both factors of the
palindrome in arbitrary order.
"""

import unittest

from palindrome_products import smallest_palindrome, largest_palindrome

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

class PalindromeProductsTest(unittest.TestCase):
def test_smallest_palindrome_from_single_digit_factors(self):
value, factors = smallest_palindrome(min_factor=1, max_factor=9)
self.assertEqual(value, 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)
self.assertEqual(value, 9)
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)
self.assertEqual(value, 121)
self.assertFactorsEqual(factors, {(11, 11)})

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

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

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

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

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

def test_empty_for_smallest_palindrome_if_none_in_range(self):
with self.assertRaisesWithMessage(ValueError):
value, factors = smallest_palindrome(min_factor=1002,
max_factor=1003)

def test_empty_for_largest_palindrome_if_none_in_range(self):
with self.assertRaisesWithMessage(ValueError):
value, factors = largest_palindrome(min_factor=15, max_factor=15)

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

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

# Utility functions
def setUp(self):
try:
self.assertRaisesRegex
except AttributeError:
self.assertRaisesRegex = self.assertRaisesRegexp

def assertRaisesWithMessage(self, exception):
return self.assertRaisesRegex(exception, r".+")

def assertFactorsEqual(self, actual, expected):
self.assertEqual(set(map(frozenset, actual)),
set(map(frozenset, expected)))

if __name__ == '__main__':
unittest.main()``````
``````from operator import mul

class Palindromes:
@classmethod
def smallest_palindrome(cls, max_factor, min_factor=0):
return min(cls.palindromes(max_factor, min_factor), key=lambda
item: item[0])

@classmethod
def largest_palindrome(cls, max_factor, min_factor=0):
return max(cls.palindromes(max_factor, min_factor), key=lambda
item: item[0])

@classmethod
def palindromes(cls, max_factor, min_factor):
return [(cls.product(candidate), candidate) for candidate in
cls.candidates(max_factor, min_factor) if
cls.is_palindrome(cls.product(candidate))]

@staticmethod
def candidates(max_factor, min_factor):
return [(i, j) for i in range(min_factor, max_factor + 1)
for j in range(i, max_factor + 1)]

@staticmethod
def product(s):
return reduce(mul, s, 1)

@staticmethod
def is_palindrome(num):
return str(num) == ''.join(reversed(str(num)))

def smallest_palindrome(max_factor, min_factor=0):
return Palindromes.smallest_palindrome(max_factor, min_factor)

def largest_palindrome(max_factor, min_factor=0):
return Palindromes.largest_palindrome(max_factor, min_factor)``````