Exercism v3 launches on Sept 1st 2021. Learn more! ๐Ÿš€๐Ÿš€๐Ÿš€
Avatar of rootulp

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)

Community comments

Find this solution interesting? Ask the author a question to learn more.

What can you learn from this solution?

A huge amount can be learned from reading other peopleโ€™s code. This is why we wanted to give exercism users the option of making their solutions public.

Here are some questions to help you reflect on this solution and learn the most from it.

  • What compromises have been made?
  • Are there new concepts here that you could read more about to improve your understanding?