{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Euler Problem 4\n",
    "\n",
    "A palindromic number reads the same both ways. The largest palindrome made from the product of two 2-digit numbers is 9009 = 91 × 99.\n",
    "\n",
    "Find the largest palindrome made from the product of two 3-digit numbers."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "No big magic going on in this solution. We apply brute force. The only optimization we take is starting from the back so that the first palindrome should be the largest. However, we first implement a simple function to check whether an integer is a palindrome."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def is_palindrome(n):\n",
    "    assert(type(n) is int)\n",
    "    n = str(n)\n",
    "    a, b = 0, len(n) - 1\n",
    "    while a < b:\n",
    "        if n[a] != n[b]:\n",
    "            return False\n",
    "        a += 1\n",
    "        b -= 1\n",
    "    return True\n",
    "\n",
    "assert(is_palindrome(9009) is True)\n",
    "assert(is_palindrome(909) is True)\n",
    "assert(is_palindrome(1) is True)\n",
    "assert(is_palindrome(908) is False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Get potential solutions. Generating a list comprehension which sorts the products in decreasing order is actually not that easys. This is because if we use two ranges it is not straight foward to decide which product is smaller or bigger. In the following example. 9 times 7 is still bigger than 8 times 8, but 9 times 6 is not anymore. So let's go for brute force the hard way."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(9, 9), (9, 8), (9, 7), (9, 6), (9, 5), (9, 4), (9, 3), (9, 2), (9, 1), (8, 8), (8, 7), (8, 6), (8, 5), (8, 4), (8, 3), (8, 2), (8, 1), (7, 7), (7, 6), (7, 5), (7, 4), (7, 3), (7, 2), (7, 1), (6, 6), (6, 5), (6, 4), (6, 3), (6, 2), (6, 1), (5, 5), (5, 4), (5, 3), (5, 2), (5, 1), (4, 4), (4, 3), (4, 2), (4, 1), (3, 3), (3, 2), (3, 1), (2, 2), (2, 1), (1, 1)]\n"
     ]
    }
   ],
   "source": [
    "pairs = ((a, b) for a in range(9, 0, -1) for b in range(a, 0, -1))\n",
    "print(list(pairs))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We iterate over all 3-digit pairs. Two avoid duplicates the second range starts at the current value of the first range. If the new product is greater than the old one and a palindrome we have found a new result."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2.844880788875627\n",
      "906609\n"
     ]
    }
   ],
   "source": [
    "import timeit\n",
    "\n",
    "def brute_force():\n",
    "    r = 0\n",
    "    for a in range(999, 99, -1):\n",
    "        for b in range(a, 99, -1):\n",
    "            c = a * b\n",
    "            if c > r and is_palindrome(c):\n",
    "                r = c\n",
    "    return r\n",
    "\n",
    "print(timeit.timeit(brute_force, number=100))\n",
    "print(brute_force())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To do some more optimization we can simply break the loops if the square of the outer loop is smaller than the current result. If we find the solution quiet early this will prevent iterating through a lot of values which cannot yield a better result."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.562125718678324\n",
      "906609\n"
     ]
    }
   ],
   "source": [
    "def brute_force():\n",
    "    r = 0\n",
    "    for a in range(999, 99, -1):\n",
    "        if a * a < r:\n",
    "            break\n",
    "        for b in range(a, 99, -1):\n",
    "            c = a * b\n",
    "            if c > r and is_palindrome(c):\n",
    "                r = c\n",
    "    return r\n",
    "\n",
    "print(timeit.timeit(brute_force, number=100))\n",
    "print(brute_force())"
   ]
  }
 ],
 "metadata": {
  "completion_date": "Wed, 20 Aug 2014, 13:27",
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.4"
  },
  "tags": [
   "palindrome",
   "combinations",
   "brute force",
   "timeit"
  ]
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
