<jupyter_start><jupyter_text>Chapter 1 - Discussion Questions  [Link](https://runestone.academy/runestone/books/published/pythonds3/Introduction/DiscussionQuestions.html)   1. Construct a class hierarchy for people on a college campus. Include faculty, staff, and students. What do they have in common? What distinguishes them from one another?<jupyter_code>class Person:
    def __init__(self, name, date_of_birth):
        self.name = name 
        self.date_of_birth = date_of_birth

class Student(Person):
    def __init__(self, name, date_of_birth, course):
        super().__init__(name, date_of_birth)
        self.course = course

class FacultyPerson(Person):
    def __init__(self, name, date_of_birth, department):
        super().__init__(name, date_of_birth)
        self.department = department

class StaffPerson(Person):
    def __init__(self, name, date_of_birth):
        super().__init__(name, date_of_birth)

class Professor(FacultyPerson):
    def __init__(self, name, date_of_birth, department, teaching, office_hours):
        super().__init__(name, date_of_birth, department)
        self.teaching = teaching
        self.office_hours = office_hours

class TeachingAssistant(FacultyPerson):
    def __init__(self, name, date_of_birth, department, teaching):
        super().__init__(name, date_of_birth, department)
        self.teaching = teaching

class Janitor(StaffPerson):
    def __init__(self, name, date_of_birth, responsibilities):
        super().__init__(name, date_of_birth)
        self.responsibilities = responsibilities<jupyter_output><empty_output><jupyter_text>* All the classes have the `Person` class in common since it contains all the demographic data relevant for each one of the subclasses. Students have course of attendance as a distinguishing characteristics, faculty have the class they teach and the department they * They can be distinguished by additional data which is specific to both classes: the student attends a **major**, while the professor teaches a **course**.   NotesClasses are incomplete and non exhaustive, because the primary reason for this exercise is to show how we can get through Object Oriented Programming more or less complex hierarchies of classes. Therefore, what is peculiar in this exercise is the subclassing technique typical in Object Oriented Languages such as Python, and how we can use it to create object hierarchies.  2. Construct a class hierarchy for bank accounts  Creating a helper function to generate IBANs<jupyter_code>import random

def generate_iban(country):
    """
    Generates iban
    Note: IBANs are not realistic at all, and are not meant to be
    - input: country
    - output: 16-characters IBAN code generated randomly
    """
    allowed_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    iban = country[:2].upper()
    
    for _ in range(14):
        self.iban += allowed_chars[random.randint(0, len(allowed_chars) - 1)]
    
    return iban<jupyter_output><empty_output><jupyter_text>Defining the hierarchy of Bank Accounts<jupyter_code>class BankAccount:
    def __init__(self, owner, country, currency):
        self.owner = owner 
        self.country = country
        self.currency = currency
        self.iban = generate_iban(country)

class CheckingAccount(BankAccount):
    def __init__(self, owner, country, currency, spending_limit_monthly):
        super().__init__(owner, country, currency)
        self.spending_limit_monthly = spending_limit_monthly

class SavingsAccount(BankAccount):
   def __init__(self, owner, country, currency, interest_rate):
       super().__init__(owner, country, currency)
       self.interest_rate = interest_rate

class MoneyMarketDepositAccount(CheckingAccount, SavingsAccount):
    def __init__(self, owner, country, currency, interest_rate):
        super(CheckingAccount).__init__(owner, country, currency)
        super(SavingsAccount).__init__(owner, country, currency, interest_rate)

class CertificateOfDeposit(SavingsAccount):
    def __init__(self, owner, country, currency, fixed_term):
        super().__init__(owner, country, currency)
        self.fixed_term = fixed_term

class RetirementAccount(BankAccount):
    def __init__(self, owner, country, currency, taxes_now, taxes_reduction):
        super().__init__(owner, country, currency)
        self.taxes_now = taxes_now
        self.taxes_reduction = taxes_reduction<jupyter_output><empty_output><jupyter_text>NotesThis is not an exhaustive list, and it is taken from [here](https://www.thebalance.com/types-of-bank-accounts-315458). In no way it represent bank accounts, but it is helpful to see how we can, for example, use multiple inheritance.  3. Construct a class hierarchy for different types of computers.<jupyter_code>class Computer:
    def __init__(self, brand):
        pass

class PersonalComputer(Computer):
    def __init__(self, brand, os):
        super().__init__(brand)
        self.os = os

class Laptop(PersonalComputer):
    def __init__(self, brand, os, n_of_ports):
        super().__init__(brand, os)
        self.n_of_ports = n_of_ports

class MobileDevice(Computer):
    def __init__(self, brand, os, phone_capability=True):
        super().__init__(brand, os)
        self.n_of_ports = n_of_ports

class Tablet(MobileDevice):
    def __init__(self, brand, os, phone_capability):
        super().__init__(brand, os, phone_capability)

class PersonalComputer(MobileDevice):
    def __init__(self, brand, os):
        super().__init__(brand, os)<jupyter_output><empty_output><jupyter_text>NotesIn order to keep it simple, I made a hierarchy based on the most common everyday devices we use, since they can all be classified as computers. A computer has a brand (it has lot's of others variables like processor brand, gpu and so on), but the point here is to encapsulate what varies in a superclass, and delegating to the subclasses to implement specific features (for example, tablet can have phone capability or not, while phones can definitely make calls).  Using the classes provided in the chapter, interactively construct a circuit and test it.  Defining the classes already defined in the book<jupyter_code>class LogicGate:
    def __init__(self, lbl):
        self.name = lbl
        self.output = None

    def get_label(self):
        return self.name

    def get_output(self):
        self.output = self.perform_gate_logic()
        return self.output


class BinaryGate(LogicGate):
    def __init__(self, lbl):
        super(BinaryGate, self).__init__(lbl)

        self.pin_a = None
        self.pin_b = None

    def get_pin_a(self):
        if self.pin_a == None:
            return int(input("Enter pin A input for gate " + self.get_label() + ": "))
        else:
            return self.pin_a.get_from().get_output()

    def get_pin_b(self):
        if self.pin_b == None:
            return int(input("Enter pin B input for gate " + self.get_label() + ": "))
        else:
            return self.pin_b.get_from().get_output()

    def set_next_pin(self, source):
        if self.pin_a == None:
            self.pin_a = source
        else:
            if self.pin_b == None:
                self.pin_b = source
            else:
                print("Cannot Connect: NO EMPTY PINS on this gate")


class AndGate(BinaryGate):
    def __init__(self, lbl):
        BinaryGate.__init__(self, lbl)

    def perform_gate_logic(self):
        a = self.get_pin_a()
        b = self.get_pin_b()
        if a == 1 and b == 1:
            return 1
        else:
            return 0

class OrGate(BinaryGate):
    def __init__(self, lbl):
        BinaryGate.__init__(self, lbl)

    def perform_gate_logic(self):
        a = self.get_pin_a()
        b = self.get_pin_b()
        if a == 1 or b == 1:
            return 1
        else:
            return 0

class UnaryGate(LogicGate):
    def __init__(self, lbl):
        LogicGate.__init__(self, lbl)

        self.pin = None

    def get_pin(self):
        if self.pin == None:
            return int(input("Enter pin input for gate " + self.get_label() + ": "))
        else:
            return self.pin.get_from().get_output()

    def set_next_pin(self, source):
        if self.pin == None:
            self.pin = source
        else:
            print("Cannot Connect: NO EMPTY PINS on this gate")


class NotGate(UnaryGate):
    def __init__(self, nlbl):
        UnaryGate.__init__(self, lbl)

    def perform_gate_logic(self):
        if self.get_pin():
            return 0
        else:
            return 1


class Connector:
    def __init__(self, fgate, tgate):
        self.from_gate = fgate
        self.to_gate = tgate

        tgate.set_next_pin(self)

    def get_from(self):
        return self.from_gate

    def get_to(self):
        return self.to_gate

# These were left as an exercise
class NorGate(BinaryGate):
    def __init__(self, lbl):
        super().__init__(self, lbl)

    def perform_gate_logic(self):
        a = self.get_pin_a()
        b = self.get_pin_b()

        if a == 0 and b == 0:
            return 1
        else:
            return 0

class NandGate(BinaryGate):
    def __init__(self, lbl):
        super().__init__(self, lbl)

    def perform_gate_logic(self):
        a = self.get_pin_a()
        b = self.get_pin_b()

        if a == 1 and b == 1:
            return 0
        else:
            return 1<jupyter_output><empty_output><jupyter_text>Creation of some custom circuits<jupyter_code># Circuit number 1
and1 = AndGate("a1")
and2 = AndGate("a2")
or1 = OrGate("o1")
conn1 = Connector(and1, or1)
conn2 = Connector(and2, or1)

or1.get_output()
# Circuit number 2

or1 = AndGate("o1")
or2 = AndGate("o2")
and1 = OrGate("a1")
conn1 = Connector(or1, and1)
conn2 = Connector(or2, and1)

and1.get_output()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09052867932231068, 'fraction_numerical': 0.007552561747295366, 'mean_word_length': 3.1538787621873676, 'pattern_counts': {'":': 3, '<': 25, '<?xml version=': 0, '>': 25, 'https://': 2, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '6', 'dir': 'jupyter-structured-clean-dedup', 'id': '71620', 'license': 'MIT', 'n_tokens_mistral': 3018, 'n_tokens_neox': 2869, 'n_words': 878, 'path': 'chapter1/chapter1_questions.ipynb', 'repo_name': 'RiccardoMPesce/PythonDS3-Selected-Assignments', 'size': '9798'}