{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Euler Problem 38\n",
    "\n",
    "Take the number 192 and multiply it by each of 1, 2, and 3:\n",
    "\n",
    "$192 \\times 1 = 192$\n",
    "\n",
    "$192 \\times 2 = 384$\n",
    "\n",
    "$192 \\times 3 = 576$\n",
    "\n",
    "By concatenating each product we get the 1 to 9 pandigital, 192384576. We will call 192384576 the concatenated product of 192 and (1,2,3)\n",
    "\n",
    "The same can be achieved by starting with 9 and multiplying by 1, 2, 3, 4, and 5, giving the pandigital, 918273645, which is the concatenated product of 9 and (1,2,3,4,5).\n",
    "\n",
    "What is the largest 1 to 9 pandigital 9-digit number that can be formed as the concatenated product of an integer with (1,2, ... , n) where n > 1?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's start by implementing a function which can calculate the concatenated product of a number based on a given $n$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def get_concatenated_product_of(number, n):\n",
    "    number = int(number)\n",
    "    return \"\".join(str(number * (i + 1)) for i in range(n))\n",
    "\n",
    "assert(get_concatenated_product_of(9, 5) == \"918273645\")\n",
    "assert(get_concatenated_product_of(192, 3) == \"192384576\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next we need a function that can check if a number is pandigital for the numbers from 1 to 9. The idea is that we generate a validation array for the numbers 1 to 9. Each number maps to a field in the array and we initialize the array with zeros. We then iterate over the input number and increment the respective validation field by one. Afterwards we check if each field in the validation array is one in which case the number is n-pandigital. Otherwise, it is not."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def is_pandigital(number, n=9):\n",
    "    number_str = str(number)\n",
    "    validation_array = [0 for _ in range(n)]\n",
    "    if \"0\" in number_str:\n",
    "        return False\n",
    "    for digit in number_str:\n",
    "        validation_array[ord(digit) - 49] += 1\n",
    "    for n in validation_array:\n",
    "        if n != 1:\n",
    "            return False\n",
    "    return True\n",
    "\n",
    "assert(is_pandigital(\"012345678\") == False)\n",
    "assert(is_pandigital(\"123\") == False)\n",
    "assert(is_pandigital(\"9\") == False)\n",
    "assert(is_pandigital(\"123568\") == False)\n",
    "assert(is_pandigital(\"987\", 9) == False)\n",
    "assert(is_pandigital(\"918273645\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This example shows how important it is to add little tests even for simple programs like this. The first example is certainly not 1 to 9 pandigital, but because of negative array indexing in Python it qualifies as True. We solved this problem by adding a simple check for a \"0\" in the number_str."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The next step is to check whether a number has the potential to become a 1 to 9 pandigital. The algorithm is essentially the same except this time we return False only if a field in the validation array is greater than 1. This means at least one digit occurs multiple times. Otherwise, the array has still potential to become a pandigital number."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def could_be_pandigital(number, n=9):\n",
    "    number_str = str(number)\n",
    "    validation_array = [0 for _ in range(n)]\n",
    "    if \"0\" in number_str:\n",
    "        return False\n",
    "    for digit in number_str:\n",
    "        validation_array[ord(digit) - 49] += 1\n",
    "    for n in validation_array:\n",
    "        if n > 1:\n",
    "            return False\n",
    "    return True\n",
    "\n",
    "assert(could_be_pandigital(\"012345678\") == False)\n",
    "assert(could_be_pandigital(\"123\") == True)\n",
    "assert(could_be_pandigital(\"9\") == True)\n",
    "assert(could_be_pandigital(\"123568\") == True)\n",
    "assert(could_be_pandigital(\"987\", 9) == True)\n",
    "assert(could_be_pandigital(\"918273645\"))\n",
    "assert(could_be_pandigital(\"98233\") == False)\n",
    "assert(could_be_pandigital(\"1223\") == False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The next function we want is one which checks if a certain number can actually create a concatenated pandigital product of an integer with (1,2, ... , n) where n > 1. For this function we can use all function which we have implemented to this point. We take a number and do a simple brute force starting with n = 2. We return False once the concatenated product cannot be a pandigital anymore. We use the two given examples as tests."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def can_build_concatenated_pandigital_product(number):\n",
    "    number_str = str(number)\n",
    "    for n in range(2, 9):\n",
    "        concatenated_product = get_concatenated_product_of(number_str, n)\n",
    "        if is_pandigital(concatenated_product):\n",
    "            return concatenated_product\n",
    "        if could_be_pandigital(concatenated_product) == False:\n",
    "            return False\n",
    "    raise Exception(\"If we got here we have a bug.\")\n",
    "\n",
    "assert(can_build_concatenated_pandigital_product(9) == \"918273645\")\n",
    "assert(can_build_concatenated_pandigital_product(192) == \"192384576\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "From here on it is just a matter of bruteforcing. We know that the base number of a higher concatenated pandigital product has to start with a 9. Also we know that the base number cannot have more than 5 digits because $10000 \\times 1, 10000 \\times 2 = 1000020000\" has already more digits than a potential solution. This means we create a list of possible solutions first."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Number of possible solutions: 401\n"
     ]
    }
   ],
   "source": [
    "possible_solutions = [i for i in range(1, 10000)\n",
    "                      if str(i).startswith(\"9\")\n",
    "                      if not \"0\" in str(i)\n",
    "                      if could_be_pandigital(i)]\n",
    "\n",
    "print(\"Number of possible solutions: {}\".format(len(possible_solutions)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Since this number is actually small enough we filter all base numbers which can produce a concatenated pandigital product."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Number of possible solutions: 4\n"
     ]
    }
   ],
   "source": [
    "possible_solutions = [s for s in possible_solutions\n",
    "                      if can_build_concatenated_pandigital_product(s)]\n",
    "\n",
    "print(\"Number of possible solutions: {}\".format(len(possible_solutions)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, calculate the actual product and print the highest one."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "932718654\n"
     ]
    }
   ],
   "source": [
    "s = max(map(int, map(can_build_concatenated_pandigital_product, possible_solutions)))\n",
    "assert(s == 932718654)\n",
    "print(s)"
   ]
  }
 ],
 "metadata": {
  "completion_date": "Sat, 19 May 2018, 23:45",
  "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.5"
  },
  "tags": [
   "concatenated",
   "product",
   "pandigital"
  ]
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
