{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Data\n",
    "amounts = [12.35, 12.35*2, 12.35*3, 12.35*4, 12.35*5, 12.35*6, 12.35*7, 12.35*8, 12.35*9, 12.35*10]\n",
    "\n",
    "coin_list = [\n",
    "    [0.2, 0.1, 0.05],\n",
    "    [1, 0.2, 0.1, 0.05],\n",
    "    [2, 1, 0.2, 0.1, 0.05],\n",
    "    [5, 2, 1, 0.2, 0.1, 0.05],\n",
    "    [5, 2, 1, 0.5, 0.2, 0.1, 0.05],\n",
    "    [5, 2, 1, 0.5, 0.2, 0.1, 0.05, 0.02],\n",
    "    [5, 2, 1, 0.5, 0.2, 0.1, 0.05, 0.02, 0.01]\n",
    "]\n",
    "\n",
    "import time\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Greedy\n",
    "def greedy_make_change(amount, coins):\n",
    "    i = 0\n",
    "    change = []\n",
    "\n",
    "    while amount > 0 and len(coins) > i:\n",
    "        # print(str(round(amount//coins[i])) + \" Coins of \" + str(coins[i]) + \"€\")\n",
    "        for j in range(round(amount//coins[i])):\n",
    "            change.append(coins[i])\n",
    "        amount = round(amount%coins[i], 2)\n",
    "        i = i+1\n",
    "    if amount > 0:\n",
    "        print(f\"Cannot make exact change for {amount:.2f}€\")\n",
    "    return change\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Iterative\n",
    "\n",
    "# Function to count occurrences of items in a list\n",
    "def count_occurrences(array):\n",
    "    counts = {}\n",
    "    for item in array:\n",
    "        if item in counts:\n",
    "            counts[item] += 1  # If the item is already in the dictionary, increment its count\n",
    "        else:\n",
    "            counts[item] = 1  # If the item is not in the dictionary, add it with a count of 1\n",
    "    return counts\n",
    "\n",
    "# Function to calculate all combinations of coins to make a specific amount\n",
    "def calculate_change_combinations(amount, coins):\n",
    "    # Convert euro amounts to cents for calculations\n",
    "    amount_cents = int(amount * 100)\n",
    "    coin_values_cents = [int(coin * 100) for coin in coins]\n",
    "\n",
    "    # Initialize a list to store combinations and their counts\n",
    "    combinations = []\n",
    "    stack = [(0, [], 0)]  # (current amount in cents, current combination, current coin index)\n",
    "\n",
    "    while stack:\n",
    "        current_amount, current_combination, current_coin_index = stack.pop()\n",
    "\n",
    "        # If the current combination sums up to the target amount, add it to the list\n",
    "        if current_amount == amount_cents:\n",
    "            combinations.append(current_combination)\n",
    "        # If the current amount is less than the target amount and there are more coins to consider\n",
    "        elif current_amount < amount_cents and current_coin_index < len(coin_values_cents):\n",
    "            coin = coin_values_cents[current_coin_index]\n",
    "            max_count = (amount_cents - current_amount) // coin  # Maximum count of the current coin\n",
    "\n",
    "            # Try adding different counts of the current coin to explore possibilities\n",
    "            for count in range(max_count + 1):\n",
    "                new_amount = current_amount + count * coin\n",
    "                new_combination = current_combination + [coins[current_coin_index]] * count\n",
    "                # Push the new state onto the stack for further exploration\n",
    "                stack.append((new_amount, new_combination, current_coin_index + 1))\n",
    "\n",
    "    # Print the total number of combinations\n",
    "    # print(f\"Total number of combinations: {len(combinations)}\")\n",
    "    return combinations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Recursive\n",
    "def make_change_recursive(amount, coins, start, current_change, result):\n",
    "    if amount == 0:\n",
    "        result.append(current_change[:])\n",
    "        return\n",
    "\n",
    "    for i in range(start, len(coins)):\n",
    "        coin_cents = round(coins[i] * 100)\n",
    "        if amount >= coin_cents:\n",
    "            current_change.append(coins[i])\n",
    "            make_change_recursive(amount - coin_cents, coins, i, current_change, result)\n",
    "            current_change.pop()\n",
    "\n",
    "    return result\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 40528 characters]",
      "text/plain": [
       "<Figure size 1000x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 58204 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "greedy_times_coin_list = []\n",
    "greedy_labels_coin_list = []\n",
    "\n",
    "coins_list_copy = coin_list.copy()\n",
    "\n",
    "for coins in coins_list_copy:\n",
    "    start = time.perf_counter()\n",
    "    greedy_make_change(amounts[0], coins)\n",
    "    end = time.perf_counter()\n",
    "    greedy_times_coin_list.append(end - start)\n",
    "    greedy_labels_coin_list.append(str(len(coins)))\n",
    "\n",
    "greedy_times_amount_list = []\n",
    "greedy_labels_amount_list = []\n",
    "\n",
    "# Plot a graph\n",
    "plt.figure(figsize=(10, 6))\n",
    "\n",
    "# NO BAR\n",
    "plt.plot(greedy_labels_coin_list[1:], greedy_times_coin_list[1:], marker='o', linestyle='solid')\n",
    "plt.xlabel('Coins')\n",
    "plt.ylabel('Time (s)')\n",
    "plt.title('Greedy Algorithm using different coins to make change for 12.35€')\n",
    "\n",
    "plt.show()\n",
    "\n",
    "for amount in amounts:\n",
    "    start = time.perf_counter()\n",
    "    greedy_make_change(amount, coin_list[-1])\n",
    "    end = time.perf_counter()\n",
    "    greedy_times_amount_list.append(end - start)\n",
    "    greedy_labels_amount_list.append(str(amount))\n",
    "\n",
    "# NO BAR\n",
    "plt.plot(greedy_labels_amount_list[1:], greedy_times_amount_list[1:], marker='o', linestyle='solid')\n",
    "plt.xlabel('Amount')\n",
    "plt.ylabel('Time (s)')\n",
    "plt.title('Greedy Algorithm using 9 coins to make change for different amounts')\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 39400 characters]",
      "text/plain": [
       "<Figure size 1000x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "iterative_times_coin_list = []\n",
    "iterative_labels_coin_list = []\n",
    "\n",
    "coins_list_copy = coin_list.copy()\n",
    "\n",
    "for coins in coins_list_copy[:5]:\n",
    "    start = time.perf_counter()\n",
    "    calculate_change_combinations(amounts[0], coins)\n",
    "    end = time.perf_counter()\n",
    "    iterative_times_coin_list.append(end - start)\n",
    "    iterative_labels_coin_list.append(str(len(coins)))\n",
    "\n",
    "# Plot a graph\n",
    "plt.figure(figsize=(10, 6))\n",
    "\n",
    "# NO BAR\n",
    "plt.plot(iterative_labels_coin_list[1:], iterative_times_coin_list[1:], marker='o', linestyle='solid')\n",
    "plt.xlabel('Coins')\n",
    "plt.ylabel('Time (s)')\n",
    "plt.title('Iterative Algorithm generating all the possibilities using different coins to make change for 12.35€')\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The complexity increase more when we add coins that are smaller. We can see that adding 0.02 and 0.01 have more impact than adding 2, and 5."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.2, 0.1, 0.05]\n",
      "[1, 0.2, 0.1, 0.05]\n",
      "[2, 1, 0.2, 0.1, 0.05]\n",
      "[5, 2, 1, 0.2, 0.1, 0.05]\n",
      "[5, 2, 1, 0.5, 0.2, 0.1, 0.05]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 40284 characters]",
      "text/plain": [
       "<Figure size 1000x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "recursive_times_coin_list = []\n",
    "recursive_labels_coin_list = []\n",
    "\n",
    "coins_list_copy = coin_list.copy()\n",
    "\n",
    "for coins in coins_list_copy[:5]:\n",
    "    # print(coins)\n",
    "    start = time.perf_counter()\n",
    "    make_change_recursive(amounts[0]*100, coins, 0, [], [])\n",
    "    end = time.perf_counter()\n",
    "    recursive_times_coin_list.append(end - start)\n",
    "    recursive_labels_coin_list.append(str(len(coins)))\n",
    "\n",
    "# Plot a graph\n",
    "plt.figure(figsize=(10, 6))\n",
    "\n",
    "# NO BAR\n",
    "plt.plot(recursive_labels_coin_list[1:], recursive_times_coin_list[1:], marker='o', linestyle='solid')\n",
    "plt.xlabel('Coins')\n",
    "plt.ylabel('Time (s)')\n",
    "plt.title('Recursive Algorithm generating all the possibilities using different coins to make change for 12.35€')\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Complexity seems polynomial or exponential, but it is not possible to test using this problem as it takes to much time to run on all the list of coins."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Export all results to json\n",
    "\n",
    "import json\n",
    "\n",
    "# Greedy\n",
    "greedy_results = {\n",
    "    \"greedy_times_coin_list\": greedy_times_coin_list,\n",
    "    \"greedy_labels_coin_list\": greedy_labels_coin_list,\n",
    "    \"greedy_times_amount_list\": greedy_times_amount_list,\n",
    "    \"greedy_labels_amount_list\": greedy_labels_amount_list\n",
    "}\n",
    "\n",
    "with open('greedy_results.json', 'w') as fp:\n",
    "    json.dump(greedy_results, fp)\n",
    "\n",
    "# Iterative\n",
    "iterative_results = {\n",
    "    \"iterative_times_coin_list\": iterative_times_coin_list,\n",
    "    \"iterative_labels_coin_list\": iterative_labels_coin_list\n",
    "}\n",
    "\n",
    "with open('iterative_results.json', 'w') as fp:\n",
    "    json.dump(iterative_results, fp)\n",
    "\n",
    "# Recursive\n",
    "recursive_results = {\n",
    "    \"recursive_times_coin_list\": recursive_times_coin_list,\n",
    "    \"recursive_labels_coin_list\": recursive_labels_coin_list\n",
    "}\n",
    "\n",
    "with open('recursive_results.json', 'w') as fp:\n",
    "    json.dump(recursive_results, fp)\n",
    "    "
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.10.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
