{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "b098f5b8-9643-40eb-8dd5-cffd2d047c12",
   "metadata": {},
   "source": [
    "<center><H1>ACOL106: Data Structures</H1></center>\n",
    "<center><H2>(Recap: Python Module 5)</H2></center>\n",
    "(<I>IMPORTANT NOTE: This module is not copied entirely from the book. I have kept only relevant material within this notebook. So, students are advised to read the book to get the full picture.</I>)<br><br>\n",
    "\n",
    "We start this module with a discussion on <b>inheritance</b>, an essential concept in Object-oriented programming.\n",
    "\n",
    "<h3>1. Inheritance</h3>\n",
    "A natural way to organize various structural components of a software package is in a <b>hierarchical</b> fashion, with similar abstract definitions grouped together in a level-by-level manner that goes from specific to more general as one traverses up the hierarchy. An example of such a hierarchy is shown in the figure below. Using mathematical notations, the set of houses is a <b>subset</b> of the set of buildings, but a <b>superset</b> of the set of ranches. The correspondence between levels is often referred to as an <b>“is a” relationship</b>, as a house is a building, and a ranch is a house.\n",
    "\n",
    "<img src=./Module-5/buildings.png style=\"width:350px;\">\n",
    "\n",
    "A hierarchical design is useful in software development, as common functionality can be grouped at the most general level, thereby promoting the reuse of code, while differentiated behaviors can be viewed as extensions of the general case. In object-oriented programming, the mechanism for a modular and hierarchical organization is a technique known as <b>inheritance</b>. This allows a new class to be defined based upon an existing class as the starting point. In object-oriented terminology, the existing class is typically described as the <b>base class, parent class, or superclass</b>, while the newly defined class is known as the <b>subclass</b> or <b>child class</b>. There are two ways in which a subclass can differentiate itself from its superclass. A subclass may <b>specialize</b> an existing behavior by providing a new implementation that <b>overrides</b> an existing method. A subclass may also <b>extend</b> its superclass by providing brand new methods.\n",
    "\n",
    "<h3>An example: Python's Exception hierarchy</h3>\n",
    "Another example of a rich inheritance hierarchy is the organization of various exception types in Python. We introduced many of those classes in an earlier module, but did not discuss their relationship with each other. The figure below illustrates a (small) portion of that hierarchy. The <mark>BaseException</mark> class is the root of the entire hierarchy, while the more specific <mark>Exception</mark> class includes most of the error types that we have discussed. Programmers are welcome to define their own special exception classes to denote errors that may occur in the context of their application. Those user-defined exception types should be declared as subclasses of <mark>Exception</mark>.\n",
    "\n",
    "<img src=./Module-5/buildings.png style=\"width:350px;\">\n",
    "\n",
    "<h4>1.1 Extending the <mark>CreditCard</mark> class</h4>\n",
    "\n",
    "To demonstrate the mechanisms for inheritance in Python, we revisit the <mark>CreditCard</mark> class discussed in an earlier section. The class is given in the code segment below for experimentation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "e7211d10-aa5b-441f-98f5-5278aafb113f",
   "metadata": {},
   "outputs": [],
   "source": [
    "class CreditCard:\n",
    "    \"\"\"A consumer credit card\"\"\"\n",
    "\n",
    "    def __init__(self, customer, bank, actn, limit):\n",
    "        \"\"\"Create a new credit card instance.\n",
    "        The initial balance is zero.\n",
    "\n",
    "        customer the name of the customer (e.g., John Bowman )\n",
    "        bank the name of the bank (e.g., California Savings )\n",
    "        acnt the acount identifier (e.g., 5391 0375 9387 5309 )\n",
    "        limit credit limit (measured in dollars)\n",
    "        \"\"\"\n",
    "        self._customer = customer\n",
    "        self._bank = bank\n",
    "        self._account = actn\n",
    "        self._limit = limit\n",
    "        self._balance = 0\n",
    "\n",
    "    def get_customer(self):\n",
    "        \"\"\"Return the name of the customer\"\"\"\n",
    "        return(self._customer)\n",
    "\n",
    "    def get_bank(self):\n",
    "        \"\"\"Return the bank's name\"\"\"\n",
    "        return(self._bank)\n",
    "\n",
    "    def get_account(self):\n",
    "        \"\"\"Return the card identifying number (stored as a string)\"\"\"\n",
    "        return(self._account)\n",
    "\n",
    "    def get_limit(self):\n",
    "        \"\"\"Return the current credit limit\"\"\"\n",
    "        return(self._limit)\n",
    "\n",
    "    def get_balance(self):\n",
    "        \"\"\"Return the current balance\"\"\"\n",
    "        return self._balance\n",
    "\n",
    "    def charge(self, price):\n",
    "        \"\"\"Charge given price to the card, assuming suﬃcient credit limit.\n",
    "            Return True if charge was processed; False if charge was denied.\n",
    "        \"\"\"\n",
    "        if price+self._balance > self._limit:\n",
    "            return(False)\n",
    "        else:\n",
    "            self._balance += price\n",
    "            return(True)\n",
    "\n",
    "    def make_payment(self, amount):\n",
    "        \"\"\"Process customer payment that reduces the balance\"\"\"\n",
    "        self._balance -= amount\n",
    "        "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b7d44d23-a947-471f-9dea-84a18b763035",
   "metadata": {},
   "source": [
    "We shall implement a subclass that, for lack of a better name, we name <mark>PredatoryCreditCard</mark>. The new class will differ from the original in two ways: (1) if an attempted charge is rejected because it would have exceeded the credit limit, a $5 fee will be charged, and (2) there will be a mechanism for assessing a monthly interest charge on the outstanding balance, based upon an <I>Annual Percentage Rate (APR)</I> specified as a constructor parameter.<br>\n",
    "\n",
    "In accomplishing this goal, we demonstrate the techniques of specialization and extension. To charge a fee for an invalid charge attempt, we override the existing charge method, thereby specializing it to provide the new functionality (although the new version takes advantage of a call to the overridden version). To provide support for charging interest, we extend the class with a new method named <mark>process_month</mark>. The figure below provides an overview of our use of inheritance in designing the new <mark>PredatoryCreditCard</mark> class, and the code segment below gives a complete Python implementation of that class.\n",
    "\n",
    "<img src=./Module-5/CreditCard.png style=\"width:350px;\">"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "95a6fb21-6124-4ef2-9ccb-e7b88ef2cac2",
   "metadata": {},
   "outputs": [],
   "source": [
    "class PredatoryCreditCard(CreditCard):\n",
    "    \"\"\"An extension to CreditCard that compounds interest and fees.\"\"\"\n",
    "\n",
    "    def __init__(self, customer, bank, acnt, limit, apr):\n",
    "        \"\"\"Create a new predatory credit card instance.\n",
    "\n",
    "        The initial balance is zero.\n",
    "\n",
    "        customer the name of the customer (e.g., John Bowman )\n",
    "        bank the name of the bank (e.g., California Savings )\n",
    "        acnt the acount identifier (e.g., 5391 0375 9387 5309 )\n",
    "        limit credit limit (measured in dollars)\n",
    "        apr annual percentage rate (e.g., 0.0825 for 8.25% APR)\n",
    "        \"\"\"\n",
    "        super().__init__(customer, bank, acnt, limit) # call super constructor\n",
    "        self._apr = apr\n",
    "\n",
    "    def charge(self, price):\n",
    "        \"\"\"Charge given price to the card, assuming suﬃcient credit limit.\n",
    "\n",
    "        Return True if charge was processed.\n",
    "        Return False and assess 5 fee if charge is denied.\n",
    "        \"\"\"\n",
    "        success = super().charge(price) # call inherited method\n",
    "        if not success:\n",
    "            self._balance += 5 # assess penalty\n",
    "        return success # caller expects return value\n",
    "\n",
    "    def process_month(self):\n",
    "        \"\"\"Assess monthly interest on outstanding balance.\"\"\"\n",
    "        if self._balance > 0:\n",
    "        # if positive balance, convert APR to monthly multiplicative factor\n",
    "            monthly_factor = pow(1 + self._apr, 1/12)\n",
    "            self._balance *= monthly_factor"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6fd97792-e9a4-468e-911f-ed8fc9fdcaec",
   "metadata": {},
   "source": [
    "To indicate that the new class inherits from the existing <mark>CreditCard</mark> class, our definition begins with the syntax, class <mark>PredatoryCreditCard(CreditCard)</mark>. The body of the new class provides three member functions: <mark>\\_\\_init\\_\\_</mark>, <mark>charge</mark>, and <mark>process_month</mark>. The <mark>\\_\\_init\\_\\_</mark> constructor serves a very similar role to the original <mark>CreditCard</mark> constructor, except that for our new class, there is an extra parameter to specify the annual percentage rate. The body of our new constructor relies upon making a call to the inherited constructor to perform most of the initialization (in fact, everything other than the recording of the percentage rate). The mechanism for calling the inherited constructor relies on the syntax, <mark>super()</mark>. Specifically, the command:\n",
    "<pre>\n",
    "    super().__init__(customer, bank, acnt, limit)\n",
    "</pre> \n",
    "calls the <mark>\\_\\_init\\_\\_</mark> method that was inherited from the <mark>CreditCard</mark> superclass. Note well that this method only accepts four parameters. We record the APR value in a new field named <mark>_apr</mark>.<br>\n",
    "\n",
    "In a similar fashion, our <mark>PredatoryCreditCard</mark> class provides a new implementation of the <mark>charge</mark> method that overrides the inherited method. Yet, our implementation of the new method relies on a call to the inherited method, with syntax <mark>super().charge(price)</mark>. The return value of that call designates whether the charge was successful. We examine that return value to decide whether to assess a fee, and in turn, we return that value to the caller of the method, so that the new version of charge has a similar outward interface as the original.<br>\n",
    "\n",
    "The <mark>process_month</mark> method is a new behavior, so there is no inherited version upon which to rely. In our model, this method should be invoked by the bank once each month to add new interest charges to the customer’s balance. The most challenging aspect in implementing this method is making sure we have a working knowledge of how an annual percentage rate translates to a monthly rate. We do not simply divide the annual rate by twelve to get a monthly rate (that would be too predatory, as it would result in a higher APR than advertised). The correct computation is to take the twelfth root of <mark>1 + self._apr</mark>, and use that as a multiplicative factor. For example, if the APR is 0.0825 (representing 8.25%), we compute (1.0825)^(1/12) ≈ 1.006628, and therefore charge 0.6628% interest per month. In this way, each 100 units of debt will amass 8.25 units of compounded interest in a year.\n",
    "\n",
    "<h4>Protected Members</h4>\n",
    "Our <mark>PredatoryCreditCard</mark> subclass directly accesses the data member <mark>self._balance</mark>, which was established by the parent <mark>CreditCard</mark> class. The underscored name, by convention, suggests that this is a <i>nonpublic</i> member, so we might ask if it is okay that we access it in this fashion. While general users of the class should not be doing so, our subclass has a somewhat privileged relationship with the superclass. Several object-oriented languages (e.g., Java, C++) draw a distinction between nonpublic members, allowing declarations of <b>protected</b> or <b>private access</b> modes. Members that are declared as protected are accessible to subclasses, but not to the general public, while members that are declared as private are not accessible to either. In this respect, we are using <mark>_balance</mark> as if it were protected (but not private).<br>\n",
    "\n",
    "Python does not support formal access control, but names beginning with a single underscore are conventionally akin to protected, while names beginning with a double underscore (other than special methods) are akin to private. In choosing to use protected data, we have created a dependency in that our <mark>PredatoryCreditCard</mark> class might be compromised if the author of the <mark>CreditCard</mark> class were to change the internal design. Note that we could have relied upon the public <mark>get_balance()</mark> method to retrieve the current balance within the <mark>process_month</mark> method. But the current design of the <mark>CreditCard</mark> class does not afford an effective way for a subclass to change the balance, other than by direct manipulation of the data member. It may be tempting to use <mark>charge</mark> to add fees or interest to the balance. However, that method does not allow the balance to go above the customer’s credit limit, even though a bank would presumably let interest compound beyond the credit limit, if warranted. If we were to redesign the original <mark>CreditCard</mark> class, we might add a nonpublic method, <mark>set_balance</mark>, that could be used by subclasses to affect a change without directly accessing the data member <mark>_balance</mark>.\n",
    "\n",
    "<h4>1.2 Another example: Hierarchy of numeric progressions</h4>\n",
    "As a second example of the use of inheritance, we develop a hierarchy of classes for iterating numeric progressions. A numeric progression is a sequence of numbers, where each number depends on one or more of the previous numbers. For example, an <b>arithmetic progression</b> determines the next number by adding a fixed constant to the previous value, and a <b>geometric progression</b> determines the next number by multiplying the previous value by a fixed constant. In general, a progression requires a first value and a way of identifying a new value based on one or more previous values.<br>\n",
    "\n",
    "To maximize the reusability of code, we develop a hierarchy of classes stemming from a general base class that we name <mark>Progression</mark> (see Figure below). Technically, the <mark>Progression</mark> class produces the progression of whole numbers: <mark>0, 1, 2, ...</mark>. However, this class is designed to serve as the base class for other progression types, providing as much common functionality as possible, and thereby minimizing the burden on the subclasses.\n",
    "\n",
    "<img src=./Module-5/Progressions.png style=\"width:350px;\">\n",
    "\n",
    "Our implementation of the basic <mark>Progression</mark> class is provided in the code segment below. The constructor for this class accepts a starting value for the progression (0 by default) and initializes a data member, <mark>self._current</mark>, to that value."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "67ac4aa2-7b4a-4c6c-a299-3cf1f38372e0",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Progression:\n",
    "    \"\"\"Iterator producing a generic progression.\n",
    "\n",
    "    Default iterator produces the whole numbers 0, 1, 2, ...\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, start=0):\n",
    "        \"\"\"Initialize current to the first value of the progression.\"\"\"\n",
    "        self._current = start\n",
    "\n",
    "    def _advance(self):\n",
    "        \"\"\"Update self. current to a new value.\n",
    "\n",
    "        This should be overridden by a subclass to customize progression.\n",
    "\n",
    "        By convention, if current is set to None, this designates the\n",
    "        end of a finite progression.\n",
    "        \"\"\"\n",
    "        self._current += 1\n",
    "\n",
    "    def __next__(self):\n",
    "        \"\"\"Return the next element, or else raise StopIteration error.\"\"\"\n",
    "        if self._current is None: # our convention to end a progression\n",
    "            raise StopIteration()\n",
    "        else:\n",
    "            answer = self._current # record current value to return\n",
    "            self._advance() # advance to prepare for next time\n",
    "            return answer # return the answer   \n",
    "\n",
    "    def __iter__(self):\n",
    "        \"\"\"By convention, an iterator must return itself as an iterator.\"\"\"\n",
    "        return self\n",
    "\n",
    "    def print_progression(self, n):\n",
    "        \"\"\"Print next n values of the progression.\"\"\"\n",
    "        print(' '.join(str(next(self)) for j in range(n)))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b13cb238-cbfa-4aa6-8dba-4fa3280557d0",
   "metadata": {},
   "source": [
    "The <mark>Progression</mark> class implements the conventions of a Python <b>iterator</b>, namely the special <mark>\\_\\_next\\_\\_</mark> and <mark>\\_\\_iter\\_\\_</mark> methods. If a user of the class creates a progression as <mark>seq = Progression()</mark>, each call to <mark>next(seq)</mark> will return a subsequent element of the progression sequence. It would also be possible to use a for-loop syntax, <mark>for value in seq:</mark>, although we note that our default progression is defined as an infinite sequence.<br>\n",
    "\n",
    "To better separate the mechanics of the iterator convention from the core logic of advancing the progression, our framework relies on a nonpublic method named <mark>_advance</mark> to update the value of the <mark>self._current</mark> field. In the default implementation, <mark>_advance</mark> adds one to the current value, but our intent is that subclasses will override <mark>_advance</mark> to provide a different rule for computing the next entry. For convenience, the <mark>Progression</mark> class also provides a utility method, named <mark>print_progression</mark>, that displays the next <mark>n</mark> values of the progression.\n",
    "\n",
    "<h4>An arithmetic progression class</h4>\n",
    "Our first example of a specialized progression is an arithmetic progression. While the default progression increases its value by one in each step, an arithmetic progression adds a fixed constant to one term of the progression to produce the next. For example, using an increment of 4 for an arithmetic progression that starts at 0 results in the sequence 0,4,8,12,....<br>\n",
    "\n",
    "The code segment below presents our implementation of an <mark>ArithmeticProgression</mark> class, which relies on <mark>Progression</mark> as its base class. The constructor for this new class accepts both an increment value and a starting value as parameters, although default values for each are provided. By our convention, <mark>ArithmeticProgression(4)</mark> produces the sequence <mark>0,4,8,12,...</mark> , and <mark>ArithmeticProgression(4, 1)</mark> produces the sequence <mark>1,5,9,13,....</mark><br>\n",
    "\n",
    "The body of the <mark>ArithmeticProgression</mark> constructor calls the super constructor to initialize the <mark>_current</mark> data member to the desired start value. Then it directly establishes the new <mark>_increment</mark> data member for the arithmetic progression. The only remaining detail in our implementation is to override the <mark>_advance</mark> method so as to add the increment to the current value."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "f321783b-ecfa-4492-af3f-96f724567354",
   "metadata": {},
   "outputs": [],
   "source": [
    "class ArithmeticProgression(Progression): \n",
    "    \"\"\"Iterator producing an arithmetic progression.\"\"\"\n",
    "    # inherit from Progression\n",
    "\n",
    "    def __init__(self, increment=1, start=0):\n",
    "        \"\"\"Create a new arithmetic progression.\n",
    "\n",
    "        increment 8 start the fixed constant to add to each term (default 1)\n",
    "        the first term of the progression (default 0)\n",
    "        \"\"\"\n",
    "        super().__init__(start) # initialize base class\n",
    "        self._increment = increment\n",
    "\n",
    "    def _advance(self): \n",
    "        \"\"\"Update current value by adding the fixed increment.\"\"\"\n",
    "        self._current += self._increment"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4fdbe783-db4b-4d50-97b4-492a9af6c946",
   "metadata": {},
   "source": [
    "<h4>A geometric progression class</h4>\n",
    "Our second example of a specialized progression is a geometric progression, in which each value is produced by multiplying the preceding value by a fixed constant, known as the base of the geometric progression. The starting point of a geometric progression is traditionally 1, rather than 0, because multiplying 0 by any factor results in 0. As an example, a geometric progression with base 2 proceeds as 1,2,4,8,16,....<br>\n",
    "\n",
    "The code segment below presents our implementation of a <mark>GeometricProgression</mark> class. The constructor uses 2 as a default base and 1 as a default starting value, but either of those can be varied using optional parameters."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "cf31a0d8-73c9-493f-89e1-146afbe7197e",
   "metadata": {},
   "outputs": [],
   "source": [
    "class GeometricProgression(Progression):\n",
    "    \"\"\"Iterator producing a geometric progression.\"\"\"\n",
    "    # inherit from Progression\n",
    "\n",
    "    def __init__(self, base=2, start=1):\n",
    "        \"\"\"Create a new geometric progression.\n",
    "\n",
    "        base the fixed constant multiplied to each term (default 2)\n",
    "        start the first term of the progression (default 1)\n",
    "        \"\"\"\n",
    "        super().__init__(start)\n",
    "        self._base = base\n",
    "\n",
    "    def _advance(self): # override inherited version\n",
    "        \"\"\"Update current value by multiplying it by the base value.\"\"\"\n",
    "        self._current *= self._base"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "deb26df9-a4bc-46ee-aa90-ea9d06c9f54d",
   "metadata": {},
   "source": [
    "<h4>A Fibonacci progression class</h4>\n",
    "As our final example, we demonstrate how to use our progression framework to produce a Fibonacci progression. Each value of a Fibonacci series is the sum of the two most recent values. To begin the series, the first two values are conventionally 0 and 1, leading to the Fibonacci series <mark>0,1,1,2,3,5,8,...</mark>. More generally, such a series can be generated from any two starting values. For example, if we start with values 4 and 6, the series proceeds as 4,6,10,16,26,42,....<br>\n",
    "\n",
    "We use our progression framework to define a new <mark>FibonacciProgression</mark> class, as shown in the code segment below. This class is markedly different from those for the arithmetic and geometric progressions because we cannot determine the next value of a Fibonacci series solely from the current one. We must maintain knowledge of the two most recent values. The base <mark>Progression</mark> class already provides storage of the most recent value as the <mark>_current</mark> data member. Our <mark>FibonacciProgression</mark> class introduces a new member, named <mark>_prev</mark>, to store the value that preceded the current one.<br>\n",
    "\n",
    "With both previous values stored, the implementation of advance is relatively straightforward. (We use a simultaneous assignment) However, the question arises as to how to initialize the previous value in the constructor. The desired first and second values are provided as parameters to the constructor. The first should be stored as <mark>_current</mark> so that it becomes the first one that is reported. Looking ahead, once the first value is reported, we will do an assignment to set the new current value (which will be the second value reported), equal to the first value plus the “previous.” By initializing the previous value to <mark>(second− first)</mark>, the initial advancement will set the new current value to <mark>first + (second− first) = second</mark>, as desired."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "cc9ea849-ebf1-485c-9f62-3f541a658357",
   "metadata": {},
   "outputs": [],
   "source": [
    "class FibonacciProgression(Progression):\n",
    "    \"\"\"Iterator producing a generalized Fibonacci progression.\"\"\"\n",
    "\n",
    "    def __init__(self, first=0, second=1):\n",
    "        \"\"\"Create a new fibonacci progression.\n",
    "\n",
    "        first the first term of the progression (default 0)\n",
    "        second the second term of the progression (default 1)\n",
    "        \"\"\"\n",
    "        super().__init__(first) # start progression at first\n",
    "        self._prev = second - first # fictitious value preceding the first\n",
    "\n",
    "    def _advance(self):\n",
    "        \"\"\"Update current value by taking sum of previous two.\"\"\"\n",
    "        self._prev, self._current = self._current, self._prev + self._current"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d10919f2-6a8c-4c29-9816-8cd24a00215b",
   "metadata": {},
   "source": [
    "Let's experiment with our progression classes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "5fbf541e-a5b4-44da-a572-7f2626dd4ddc",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Default progression:\n",
      "0 1 2 3 4 5 6 7 8 9\n",
      "Arithmetic progression with increment 5:\n",
      "0 5 10 15 20 25 30 35 40 45\n",
      "Arithmetic progression with increment 5 and start 2:\n",
      "2 7 12 17 22 27 32 37 42 47\n",
      "Geometric progression with default base:\n",
      "1 2 4 8 16 32 64 128 256 512\n",
      "Geometric progression with base 3:\n",
      "1 3 9 27 81 243 729 2187 6561 19683\n",
      "Fibonacci progression with default start values:\n",
      "0 1 1 2 3 5 8 13 21 34\n",
      "Fibonacci progression with start values 4 and 6:\n",
      "4 6 10 16 26 42 68 110 178 288\n"
     ]
    }
   ],
   "source": [
    "if __name__ == '__main__':\n",
    "    print('Default progression:')\n",
    "    Progression().print_progression(10)\n",
    "    \n",
    "    print('Arithmetic progression with increment 5:')\n",
    "    ArithmeticProgression(5).print_progression(10)\n",
    "    \n",
    "    print('Arithmetic progression with increment 5 and start 2:')\n",
    "    ArithmeticProgression(5, 2).print_progression(10)\n",
    "    \n",
    "    print('Geometric progression with default base:')\n",
    "    GeometricProgression().print_progression(10)\n",
    "    \n",
    "    print('Geometric progression with base 3:')\n",
    "    GeometricProgression(3).print_progression(10)\n",
    "    \n",
    "    print('Fibonacci progression with default start values:')\n",
    "    FibonacciProgression().print_progression(10)\n",
    "    \n",
    "    print('Fibonacci progression with start values 4 and 6:')\n",
    "    FibonacciProgression(4, 6).print_progression(10)\n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "05efa3e1-b463-499d-a9ec-602baca88468",
   "metadata": {},
   "source": [
    "<h3>1.3 Abstract Base Class</h3>\n",
    "When defining a group of classes as part of an inheritance hierarchy, one technique for avoiding repetition of code is to design a base class with common functionality that can be inherited by other classes that need it. As an example, the progression hierarchy from includes a <mark>Progression</mark> class, which serves as a base class for three distinct subclasses: <mark>ArithmeticProgression</mark>, <mark>GeometricProgression</mark>, and <mark>FibonacciProgression</mark>. Although it is possible to create an instance of the <mark>Progression</mark> base class, there is little value in doing so because its behavior is simply a special case of an <mark>ArithmeticProgression</mark> with increment 1. The real purpose of the <mark>Progression</mark> class was to centralize the implementations of behaviors that other progressions needed, thereby streamlining the code that is relegated to those subclasses.<br>\n",
    "\n",
    "In classic object-oriented terminology, we say a class is an <b>abstract base class</b> if its only purpose is to serve as a base class through inheritance. More formally, an abstract base class is one that cannot be directly instantiated, while a <b>concrete</b> class is one that can be instantiated. By this definition, our <mark>Progression</mark> class is technically concrete, although we essentially designed it as an abstract base class.<br>\n",
    "\n",
    "In statically typed languages such as Java and C++, an abstract base class serves as a formal type that may guarantee one or more <b>abstract methods</b>. This provides support for polymorphism, as a variable may have an abstract base class as its declared type, even though it refers to an instance of a concrete subclass. Because there are no declared types in Python, this kind of polymorphism can be accomplished without the need for a unifying abstract base class. For this reason, there is not as strong a tradition of defining abstract base classes in Python, although Python’s abc module provides support for defining a formal abstract base class.<br>\n",
    "\n",
    "Our reason for focusing on abstract base classes in our study of data structures is that Python’s collections module provides several abstract base classes that assist when defining custom data structures that share a common interface with some of Python’s built-in data structures. These rely on an object-oriented software design pattern known as the <b>template method pattern</b>. The template method pattern is when an abstract base class provides concrete behaviors that rely upon calls to other abstract behaviors. In that way, as soon as a subclass provides definitions for the missing abstract behaviors, the inherited concrete behaviors are well defined.<br>\n",
    "\n",
    "As a tangible example, the <mark>collections.Sequence</mark> abstract base class defines behaviors common to Python’s <mark>list, str, and tuple</mark> classes, as sequences that support element access via an integer index. More so, the <mark>collections.Sequence</mark> class provides concrete implementations of methods, <mark>count</mark>, <mark>index</mark>, and <mark>\\_\\_contains\\_\\_</mark> that can be inherited by any class that provides concrete implementations of both <mark>\\_\\_len\\_\\_</mark> and <mark>\\_\\_getitem\\_\\_</mark>. For the purpose of illustration, we provide a sample implementation of such a Sequence abstract base class in the code segment below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "8f4d6a65-e1b7-49e8-b58e-6c923472b0d3",
   "metadata": {},
   "outputs": [],
   "source": [
    "from abc import ABCMeta, abstractmethod # need these definitions\n",
    "\n",
    "class Sequence(metaclass=ABCMeta):\n",
    "    \"\"\"Our own version of collections.Sequence abstract base class.\"\"\"\n",
    "\n",
    "    @abstractmethod\n",
    "    def __len__(self):\n",
    "        \"\"\"Return the length of the sequence.\"\"\"\n",
    "\n",
    "    @abstractmethod\n",
    "    def __getitem__(self, j):\n",
    "        \"\"\"Return the element at index j of the sequence.\"\"\"\n",
    "\n",
    "    def __contains__(self, val):\n",
    "        \"\"\"Return True if val found in the sequence; False otherwise.\"\"\"\n",
    "        for j in range(len(self)):\n",
    "            if self[j] == val: # found match\n",
    "                return True\n",
    "        return False\n",
    "\n",
    "    def index(self, val):\n",
    "        \"\"\"Return leftmost index at which val is found (or raise ValueError).\"\"\"\n",
    "        for j in range(len(self)):\n",
    "            if self[j] == val: # leftmost match\n",
    "                return j\n",
    "        raise ValueError('value not in sequence') # never found a match\n",
    "\n",
    "    def count(self, val):\n",
    "        \"\"\"Return the number of elements equal to given value.\"\"\"\n",
    "        k = 0\n",
    "        for j in range(len(self)):\n",
    "            if self[j] == val: # found a match\n",
    "                k += 1\n",
    "        return k"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "394cbe2a-ba6b-4180-b1c2-dbbb3c63e80d",
   "metadata": {},
   "source": [
    "This implementation relies on two advanced Python techniques. The first is that we declare the <mark>ABCMeta</mark> class of the <mark>abc</mark> module as a <b>metaclass</b> of our <mark>Sequence</mark> class. A metaclass is different from a superclass, in that it provides a template for the class definition itself. Specifically, the <mark>ABCMeta</mark> declaration assures that the constructor for the class raises an error.<br>\n",
    "\n",
    "The second advanced technique is the use of the <mark>@abstractmethod</mark> <b>decorator</b> immediately before the <mark>\\_\\_len\\_\\_</mark> and <mark>\\_\\_getitem\\_\\_</mark> methods are declared. That declares these two particular methods to be abstract, meaning that we do not provide an implementation within our <mark>Sequence</mark> base class, but that we expect any concrete subclasses to support those two methods. Python enforces this expectation by disallowing instantiation for any subclass that does not override the abstract methods with concrete implementations.<br>\n",
    "\n",
    "The rest of the <mark>Sequence</mark> class definition provides tangible implementations for other behaviors, under the assumption that the abstract <mark>\\_\\_len\\_\\_</mark> and <mark>\\_\\_getitem\\_\\_</mark> methods will exist in a concrete subclass. If you carefully examine the source code, the implementations of methods <mark>\\_\\_contains\\_\\_</mark>, <mark>index</mark>, and <mark>count</mark> do not rely on any assumption about the <mark>self</mark> instances, other than that syntax <mark>len(self)</mark> and <mark>self[j]</mark> are supported (by special methods <mark>\\_\\_len\\_\\_</mark> and <mark>\\_\\_getitem\\_\\_</mark>, respectively). Support for iteration is automatic as well, as described earlier.<br>\n",
    "\n",
    "In the remainder of this book, we omit the formality of using the <mark>abc</mark> module. If we need an “abstract” base class, we simply document the expectation that subclasses provide assumed functionality, without a technical declaration of the methods as abstract. But we will make use of the wonderful abstract base classes that are defined within the <mark>collections</mark> module (such as <mark>Sequence</mark>). To use such a class, we need only rely on standard inheritance techniques.<br>\n",
    "\n",
    "For example, our <mark>Range</mark> class, from the previous module, is an example of a class that supports the <mark>\\_\\_len\\_\\_</mark> and <mark>\\_\\_getitem\\_\\_</mark> methods. But that class does not support the methods <mark>count</mark> or <mark>index</mark>. Had we originally declared it with <mark>Sequence</mark> as a superclass, then it would also inherit the <mark>count</mark> and <mark>index</mark> methods. The syntax for such a declaration would begin as:\n",
    "<pre>\n",
    "    class Range(collections.Sequence):\n",
    "</pre>\n",
    "Finally, we emphasize that if a subclass provides its own implementation of an inherited behavior from a base class, the new definition overrides the inherited one. This technique can be used when we have the ability to provide a more efficient implementation of a behavior than is achieved by the generic approach. As an example, the general implementation of contains for a sequence is based on a loop used to search for the desired value. For our <mark>Range</mark> class, there is an opportunity for a more efficient determination of containment. For example, it is evident that the expression, <mark>100000 in Range(0, 2000000, 100)</mark>, should evaluate to <mark>True</mark>, even without examining the individual elements of the range, because the range starts with zero, has an increment of 100, and goes until 2 million; it must include 100000, as that is a multiple of 100 that is between the start and stop values.\n",
    "\n",
    "<h3>1.4 Namespace and Object-orientation</h3>\n",
    "A <b>namespace</b> is an abstraction that manages all of the identifiers that are defined in a particular scope, mapping each name to its associated value. In Python, functions, classes, and modules are all first-class objects, and so the “value” associated with an identifier in a namespace may in fact be a function, class, or module.<br>\n",
    "\n",
    "In an earlier module, we explored Python’s use of namespaces to manage identifiers that are defined with global scope, versus those defined within the local scope of a function call. In this section, we discuss the important role of namespaces in Python’s management of object-orientation.\n",
    "\n",
    "<h4>1.4.1 Instance and class namespaces</h4>\n",
    "We begin by exploring what is known as the <b>instance namespace</b>, which manages attributes specific to an individual object. For example, each instance of our <mark>CreditCard</mark> class maintains a distinct balance, a distinct account number, a distinct credit limit, and so on (even though some instances may coincidentally have equivalent balances or equivalent credit limits). Each credit card will have a dedicated instance namespace to manage such values.\n",
    "\n",
    "There is a separate <b>class namespace</b> for each class that has been defined. This namespace is used to manage members that are to be shared by all instances of a class, or used without reference to any particular instance. For example, the <mark>make_payment</mark> method of the <mark>CreditCard</mark> class is not stored independently by each instance of that class. That member function is stored within the namespace of the <mark>CreditCard</mark> class. The <mark>CreditCard</mark> class namespace includes the functions: <mark>\\_\\_init\\_\\_</mark>, <mark>get_customer</mark>, <mark>get_bank</mark>, <mark>get_account</mark>, <mark>get_balance</mark>, <mark>get_limit</mark>, <mark>charge</mark>, and <mark>make_payment</mark>. Our <mark>PredatoryCreditCard</mark> class has its own namespace, containing the three methods we defined for that subclass: <mark>\\_\\_init\\_\\_</mark>, <mark>charge</mark>, and <mark>process_month</mark>.<br>\n",
    "\n",
    "The figure below provides a portrayal of three such namespaces: a class namespace containing methods of the <mark>CreditCard</mark> class, another class namespace with methods of the <mark>PredatoryCreditCard</mark> class, and finally a single instance namespace for a sample instance of the <mark>PredatoryCreditCard</mark> class. We note that there are two different definitions of a function named <mark>charge</mark>, one in the <mark>CreditCard</mark> class, and then the overriding method in the <mark>PredatoryCreditCard</mark> class. In a similar fashion, there are two distinct <mark>\\_\\_init\\_\\_</mark> implementations. However, <mark>process_month</mark> is a name that is only defined within the scope of the <mark>PredatoryCreditCard</mark> class. The instance namespace includes all data members for the instance (including the <mark>_apr</mark> member that is established by the <mark>PredatoryCreditCard</mark> constructor).\n",
    "\n",
    "<img src=./Module-5/namespaces.png style=\"width:350px;\">\n",
    "\n",
    "<h5>How Entries Are Established in a Namespace</h5>\n",
    "It is important to understand why a member such as <mark>_balance</mark> resides in a credit card’s instance namespace, while a member such as <mark>make_payment</mark> resides in the class namespace. The <mark>_balance</mark> is established within the <mark>\\_\\_init\\_\\_</mark> method when a new credit card instance is constructed. The original assignment uses the syntax <mark>self._balance = 0</mark>, where <mark>self</mark> is an identifier for the newly constructed instance. The use of <mark>self</mark> as a qualifier for <mark>self._balance</mark> in such an assignment causes the <mark>_balance</mark> identifier to be added directly to the instance namespace.<br>\n",
    "\n",
    "When inheritance is used, there is still a single instance namespace per object. For example, when an instance of the <mark>PredatoryCreditCard</mark> class is constructed, the <mark>_apr</mark> attribute, as well as attributes such as <mark>_balance</mark> and <mark>_limit</mark> all reside in that instance’s namespace, because all are assigned using a qualified syntax, such as <mark>self._apr</mark>.<br>\n",
    "\n",
    "A class namespace includes all declarations that are made directly within the body of the class definition. For example, our <mark>CreditCard</mark> class definition included the following structure:\n",
    "<pre>\n",
    "    class CreditCard:\n",
    "        def make payment(self, amount):\n",
    "        ...\n",
    "</pre>\n",
    "Because the make payment function is declared within the scope of the <mark>CreditCard</mark> class, that function becomes associated with the name make payment within the <mark>CreditCard</mark> class namespace. Although member functions are the most typical types of entries that are declared in a class namespace, we next discuss how other types of data values, or even other classes, can be declared within a class namespace.\n",
    "\n",
    "<h5>Class Data Members</h5>\n",
    "A class-level data member is often used when there is some value, such as a constant, that is to be shared by all instances of a class. In such a case, it would be unnecessarily wasteful to have each instance store that value in its instance namespace. As an example, we revisit the <mark>PredatoryCreditCard</mark> introduced earlier. That class assesses a 5 dollar fee if an attempted charge is denied because of the credit limit. Our choice of 5 dollars for the fee was somewhat arbitrary, and our coding style would be better if we used a named variable rather than embedding the literal value in our code. Often, the amount of such a fee is determined by the bank’s policy and does not vary for each customer. In that case, we could define and use a class data member as follows:\n",
    "<pre>\n",
    "    class PredatoryCreditCard(CreditCard):\n",
    "        OVERLIMIT_FEE = 5 # this is a class-level member\n",
    "        \n",
    "        def charge(self, price):\n",
    "            success = super().charge(price)\n",
    "            if not success:\n",
    "                self._balance += PredatoryCreditCard.OVERLIMIT_FEE\n",
    "            return success\n",
    "</pre>\n",
    "The data member, <mark>OVERLIMIT_FEE</mark>, is entered into the <mark>PredatoryCreditCard</mark> class namespace because that assignment takes place within the immediate scope of the class definition, and without any qualifying identifier.\n",
    "\n",
    "<h5>Nested classes</h5>\n",
    "It is also possible to nest one class definition within the scope of another class. This is a useful construct, which we will exploit several times in this book in the implementation of data structures. This can be done by using a syntax such as\n",
    "<pre>\n",
    "    class A: # the outer class\n",
    "        class B: # the nested class        \n",
    "            ...\n",
    "</pre>\n",
    "In this case, <mark>class B</mark> is the nested class. The identifier <mark>B</mark> is entered into the namespace of <mark>class A</mark> associated with the newly defined class. We note that this technique is unrelated to the concept of inheritance, as <mark>class B</mark> does not inherit from <mark>class A</mark>.<br>\n",
    "\n",
    "Nesting one class in the scope of another makes clear that the nested class exists for the support of the outer class. Furthermore, it can help reduce potential name conflicts because it allows for a similarly named class to exist in another context. For example, we will later introduce a data structure known as a <b>linked list</b> and will define a nested node class to store the individual components of the list. We will also introduce a data structure known as a <b>tree</b> that depends upon its own nested node class. These two structures rely on different node definitions, and by nesting those within the respective container classes, we avoid ambiguity.<br>\n",
    "\n",
    "Another advantage of one class being nested as a member of another is that it allows for a more advanced form of inheritance in which a subclass of the outer class overrides the definition of its nested class. We will make use of that technique when specializing the nodes of a tree structure.\n",
    "\n",
    "<h4>1.4.2 Name Resolution and Dynamic Dispatch</h4>\n",
    "In the previous section, we discussed various namespaces and the mechanism for establishing entries in those namespaces. In this section, we examine the process that is used when retrieving a name in Python’s object-oriented framework. When the dot operator syntax is used to access an existing member, such as <mark>obj.foo</mark>, the Python interpreter begins a name resolution process, described as follows: \n",
    "<OL>\n",
    "    <li>The instance namespace is searched; if the desired name is found, its associated value is used.</li>\n",
    "    <li> Otherwise, the class namespace, for the class to which the instance belongs, is searched; if the name is found, its associated value is used.</li>\n",
    "    <li> If the name was not found in the immediate class namespace, the search continues upward through the inheritance hierarchy, checking the class namespace for each ancestor (commonly by checking the superclass class, then its superclass class, and so on). The first time the name is found, its associated value is used.</li>\n",
    "    <li> If the name has still not been found, an <mark>AttributeError</mark> is raised.</li>\n",
    "</OL>\n",
    "As a tangible example, let us assume that <mark>mycard</mark> identifies an instance of the <mark>PredatoryCreditCard</mark> class. Consider the following possible usage patterns.\n",
    "<UL>\n",
    "    <LI> <mark>mycard._balance</mark> (or equivalently, <mark>self._balance</mark> from within a method body): the <mark>_balance</mark> method is found within the instance namespace for <mark>mycard</mark>.</LI>\n",
    "    <LI> <mark>mycard.process_month()</mark>: The search begins in the instance namespace, but\n",
    "the name <mark>process_month</mark> is not found in that namespace. As a result, the\n",
    "<mark>PredatoryCreditCard</mark> class namespace is searched; in this case, the name is\n",
    "found and that method is called.</LI>\n",
    "    <LI> <mark>mycard.make payment(200)</mark>: The search for the name, <mark>make_payment</mark>, fails in the instance namespace and in the <mark>PredatoryCreditCard</mark> namespace. The name is resolved in the namespace for superclass <mark>CreditCard</mark> and thus the inherited method is called.</LI>\n",
    "    <LI> <mark>mycard.charge(50)</mark>: the search for name charge fails in the instance namespace. The next namespace checked is for the <mark>PredatoryCreditCard</mark> class,\n",
    "because that is the true type of the instance. There is a definition for a charge\n",
    "function in that class, and so that is the one that is called.</LI>\n",
    "</UL>\n",
    "In the last case shown, notice that the existence of a charge function in the <mark>PredatoryCreditCard</mark> class has the effect of <b>overriding</b> the version of that function that exists in the <mark>CreditCard</mark> namespace. In traditional object-oriented terminology, Python uses what is known as <b>dynamic dispatch</b> (or <b>dynamic binding</b>) to determine, at run-time, which implementation of a function to call based upon the type of the object upon which it is invoked. This is in contrast to some languages that use <b>static dispatching</b>, making a compile-time decision as to which version of a function to call, based upon the declared type of a variable.\n",
    "\n",
    "<h3>1.5 Shallow and Deep Copying</h3>\n",
    "In the first module, we emphasized that an assignment statement <mark>foo = bar</mark> makes the name <mark>foo</mark> an alias for the object identified as <mark>bar</mark>. In this section, we consider the task of making a copy of an object, rather than an alias. This is necessary in applications when we want to subsequently modify either the original or the copy in an independent manner.<br>\n",
    "\n",
    "Consider a scenario in which we manage various lists of colors, with each color represented by an instance of a presumed <mark>color</mark> class. We let identifier <mark>warmtones</mark> denote an existing list of such colors (e.g., oranges, browns). In this application, we wish to create a new list named <mark>palette</mark>, which is a copy of the <mark>warmtones</mark> list. However, we want to subsequently be able to add additional colors to <mark>palette</mark>, or to modify or remove some of the existing colors, without affecting the contents of <mark>warmtones</mark>. If we were to execute the command:\n",
    "<pre>\n",
    "    palette = warmtones\n",
    "</pre>\n",
    "This creates an alias, as shown in the figure below. No new list is created; instead, the new identifier <mark>palette</mark> references the original list.\n",
    "\n",
    "<img src=./Module-5/copying-1.png style=\"width:250px;\">\n",
    "\n",
    "Unfortunately, this does not meet our desired criteria, because if we subsequently add or remove colors from <mark>palette</mark>, we modify the list identified as <mark>warmtones</mark>. We can instead create a new instance of the list class by using the syntax:\n",
    "<pre>\n",
    "    palette = list(warmtones)\n",
    "</pre>\n",
    "In this case, we explicitly call the <mark>list</mark> constructor, sending the first list as a parameter. This causes a new list to be created, as shown in the figure below; however, it is what is known as a <b>shallow copy</b>. The new list is initialized so that its contents are precisely the same as the original sequence. However, Python’s lists are <b>referential</b>, and so the new list represents a sequence of references to the same elements as in the first.\n",
    "\n",
    "<img src=./Module-5/copying-2.png style=\"width:300px;\">\n",
    "\n",
    "This is a better situation than our first attempt, as we can legitimately add or remove elements from <mark>palette</mark> without affecting <mark>warmtones</mark>. However, if we edit a color instance from the <mark>palette</mark> list, we effectively change the contents of <mark>warmtones</mark>. Although <mark>palette</mark> and <mark>warmtones</mark> are distinct lists, there remains indirect aliasing, for example, with <mark>palette[0]</mark> and <mark>warmtones[0]</mark> as aliases for the same color instance.<br>\n",
    "\n",
    "We prefer that palette be what is known as a deep copy of <mark>warmtones</mark>. In a <b>deep copy</b>, the new copy references its own copies of those objects referenced by the original version. See figure below.\n",
    "\n",
    "\n",
    "\n",
    "<h4>Python's copy module</h4>\n",
    "To create a deep copy, we could populate our list by explicitly making copies of the original color instances, but this requires that we know how to make copies of colors (rather than aliasing). Python provides a very convenient module, named <mark>copy</mark>, that can produce both shallow copies and deep copies of arbitrary objects.\n",
    "\n",
    "This module supports two functions: the <mark>copy</mark> function creates a shallow copy of its argument, and the <mark>deepcopy</mark> function creates a deep copy of its argument. After importing the module, we may create a deep copy for our example, as shown in the figure below, using the command:\n",
    "<pre>\n",
    "    palette = copy.deepcopy(warmtones)\n",
    "</pre>\n",
    "\n",
    "<img src=./Module-5/copying-3.png style=\"width:350px;\">\n",
    "\n",
    "\n",
    "<h3> Module endnote</h3>\n",
    "We have covered most of the Python programming concepts. There are certain concepts that we will pick up as and when required during the course. You can use the code segment below to experiment with some of the concepts discussed above.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "f5d3393c-c769-45f6-8ffe-263f2fa211d6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[249, 124, 43], [169, 163, 52]]\n",
      "[[250, 124, 43], [169, 163, 52]]\n",
      "[[250, 124, 43], [169, 163, 52]]\n",
      "[[251, 124, 43], [169, 163, 52]]\n"
     ]
    }
   ],
   "source": [
    "C1 = [249, 124, 43]\n",
    "C2 = [169, 163, 52]\n",
    "warmtones = [C1, C2]\n",
    "print(warmtones)\n",
    "palette = warmtones\n",
    "palette[0][0] = 250\n",
    "print(warmtones)\n",
    "import copy\n",
    "palette = copy.deepcopy(warmtones)\n",
    "palette[0][0] = 251\n",
    "print(warmtones)\n",
    "print(palette)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f17fba42-8141-4d79-a686-504a2b202a2f",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "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.9.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
