# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# ![nerw_zad6_stopka.jpg](attachment:nerw_zad6_stopka.jpg)
#
# # Programming Language With Numerical Methods
# <heads>
# <NAME>, Msc
#     
# ## Class 14. Exceptions. Object-oriented programming

# **Whenever you learn a new feature, you should try it out in interactive mode and make errors on purpose to see what goes wrong and what types of errors you run into.**

# - Exceptions, Python tutorial: https://docs.python.org/3/tutorial/errors.html
# - Object-oriented programming: https://docs.python.org/3/tutorial/classes.html

# ## Exercises<a name="exercises"></a>

# #### Exercise 1
#
# Define a class `Rectangle`. Create an `__init__` to assign sides of the rectangle to attributes `a` and `b`. Define two methods of the class: `area` returning an area of the rectangle and `perimeter` returning the perimeter of the rectangle.

# +
class Rectangle:
    def __init__(self, a, b):
        self.a = a
        self.b = b
    
    def area(self):
        return self.a * self.b
    
    def perimeter(self):
        return 2*self.a + 2*self.b
    
rect = Rectangle(1, 2)
print(f"Area: {rect.area()}, perimeter: {rect.perimeter()}")
# -

# #### Exercise 2
#
# Create a class `Point` representing a point on a plane. A point should have two coordinates, `x` and `y`. Define a class called `Line` representing a line segment. `Line` has a start point and an end-point, both should be objects of the type `Point`. In class `Line` define methods to compute the distance between the points, azimuth of the line and a reversed azimuth (azimuth of the reversed line).
#
# Create an object of the class and invoke defined methods.

# +
import math as m

class Point:
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

        
# we can use class Point to define the beginning and ending points of the line

class Line:
    def __init__(self, A, B):
        """
        A and B given as iterables (list or tuple)
        """
        self.start = Point(*A) # star operator unpacks the list/tuple to individual arguments
        self.end = Point(*B)
    
    def dist(self):
        """
        Compute the length of the line based on the coordinates of the starting and ending point
        """
        d = m.sqrt((self.end.x-self.start.x)**2 + (self.end.y - self.start.y)**2)
        return d
    
    def azimuth(self):
        # azimuth in degrees
        # compute azimuth here
        return None
        
    def rev_azimuth(self):
        """
        Compute the reverse azimuth
        """
        # returns the result in degrees
        return (self.azimuth + 180) % 360
    
        
l = Line((3, 4), (0, 0))
print(l.dist())
print(l.azimuth())
# -

# #### Exercise 3
#
# Create a class `Dice` with one attribute called `sides`, which has a default value of 6. Write a method called `roll_dice()` that prints a random number
# between 1 and the number of sides the dice has. 
#
# Create a 6-sided dice and roll it 5 times. Create a 10-sided dice and a 20-sided dice. Roll each dice 10 times.

# choosing a random integer
import random
a = random.randint(1, 100)
print(a)


# +
class Dice:
    def __init__(self, sides=6):
        self.sides = sides
        
    def roll_dice(self):
        val = random.randint(1, self.sides)
        return val
        
d = Dice()

for i in range(5):
    print(f"Rolling the {d.sides}-sided dice...{d.roll_dice()}")

    
d10 = Dice(20)    
for i in range(10):
    print(f"Rolling the {d10.sides}-sided dice...{d10.roll_dice()}")


# -

# #### Exercise 4
#
# Create a class called `Person`. Each object of this class should have a `name`, `last_name` and `age`. Define a method `get_full_name` that returns `name` and `last_name` as one string. Next, define a class `Teacher` that inherits from a class `Person`. Every object of the class should have a list of classes taught as the attribute. 
#
# Create an object of the class `Teacher`. Provide name, last name, age and a list of classes. Print teacher name (full name) using already defined methods.

# +
class Person:
    def __init__(self, name, last_name, age=10):
        self.name = name
        self.last_name = last_name
        self.age = age
    
    def get_full_name(self):
        return self.name + " " +  self.last_name
    
    
class Teacher(Person):
    
    def __init__(self, name, last_name, age, classes):
        #Person.__init__(self, name, last_name, age)
        # works the same as:
        super().__init__(name, last_name, age)
        self.classes = classes
        
person = Person("John", "Doe", 43)
teacher = Teacher("Anna", "Jones", 35, ["Calculus", "Linear Algebra", "Computer programming 101"])

print(teacher.get_full_name())
print(teacher.age)
print(teacher.classes)


# -

# #### Exercise 5
#
# Create a class `Angle` for storing angles in degrees minutes and seconds. Redefine addition and subtraction of angles (operator overloading) to enable adding and subtracting two angles.

# +
class Angle:
    def __init__(self, deg=0, minutes=0, seconds=0.0):
        # check if seconds and minutes are in range (0-60)
        seconds_overflow = 0
        minutes_overflow = 0
        if seconds >= 60:
            seconds_overflow = seconds % 60
            self.s = seconds // 60
        elif seconds < 0:
            self.s = 0
        else:
            self.s = seconds
        if minutes + seconds_overflow >= 60:
            minutes_overflow = (minutes + seconds_overflow) % 60
            self.m = (minutes + seconds_overflow) // 60
        elif minutes + seconds_overflow < 0:
            self.m = 0
        else:
            self.m = minutes + seconds_overflow
        self.d = deg + minutes_overflow
        
        
    def __add__(self, angle2): #redefining the built in functions -> __add__ is a function for a '+' operator
        result = Angle()
        # div mod returns the integer division (//) result and modulo operator result
        s_overload, result.s = divmod(self.s + angle2.s, 60)
        m_overload, result.m = divmod(self.m + angle2.m + s_overload, 60)
        result.d = (self.d + angle2.d + m_overload) % 360
        return result
    
    def __sub__(self, angle2):
        result = Angle()
        s_overload, m_overload = 0, 0
        result.s = self.s - angle2.s
        if result.s < 0:
            s_overload = -1
            result.s = self.s + 60 - angle2.s
        result.m = self.m - angle2.m  + s_overload
        if result.m < 0 :
            m_overload = -1
            result.m = self.m - angle2.m + s_overload + 60
        result.d = (self.d - angle2.d + m_overload + 360) % 360
        return result
    
    def __str__(self):
        # redefining look for printing the object
        deg = "\u00B0" # degree sign
        return f"{self.d}{deg} {self.m:02d}' {self.s:07.4f}\" " # fill the minutes and seconds with 0 in the beginning
    
    def __repr__(self):
        # redefining the look of the object while calling the object to the console
        return self.__str__()

  
an1 = Angle(20, 20, 30)
an2 = Angle(30, 10, 9)
r = an1 + an2
print(r)
print(an1 - an2)
an1 + an2 + an1
# -

# #### Exercise 6
#
# Check out the list of exceptions in Python (https://www.tutorialspoint.com/python3/standard_exceptions.htm). Try to write a few pieces of code that raises three of those exceptions. Handle the exceptions using `try-except`. 

# +
# example Exception handling

# +
# index error
l = range(10)

try:
    a = l[10]
except IndexError:
    print("No index with this value in l")
# -

# key error
d = {"a": 8, "b": 10, "c":6}
try:
    print(d["d"])
except KeyError:
    print("No key 'd' in dictionary d")

# zero division error
x = 10
try:
    z = x / 0
except ZeroDivisionError:
    print("do not divide by zero!")

# Name error
y = 10
try:
    z = c / y
except NameError:
    print("One of the variables in z = x/y not defined")


# #### Exercise 7
#
# Take a look at the code listed below. Add `try-except` statement where necessary to avoid crashing the program.

# +
def example1():
    for i in range(3):
        x = int(input("enter a number: "))
        y = int( input("enter another number: "))
        print( x, '/', y, '=', x/y )

def example2(L):
    product = 0
    product_of_pairs = []
    for i in range(len(L)):
        product_of_pairs.append(L[i]+L[i+1])

    print( "product_of_pairs = ", product_of_pairs )


def printUpperFile(fileName ):
   file = open(fileName, "r")
   for line in file:
       print(line.upper())
   file.close()
    
def main():
    example1()
    L = [10, 3, 5, 6, 9, 3]
    example2(L)
    example2([10, 3, 5, 6, "NA", 3])
    printUpperFile("notexisting.txt" )

main()


# +
def example1():
    for i in range(3):
        try:
            x = int(input("enter a number: "))
            y = int( input("enter another number: "))
        except ValueError:
            print("One of the values x, y is not given as integer")
        else:
            try:
                print( x, '/', y, '=', x/y )
            except ZeroDivisionError:
                print("ZeroDivision by y")

def example2(L):
    product = 0
    product_of_pairs = []
    for i in range(len(L)):
        try:
            product_of_pairs.append(L[i]+L[i+1])
        except IndexError:
            print(f"index out of bounds for {i=}")
        except TypeError:
            print(f"Unsupported operation in example2 between {L[i]} and {L[i+1]}")
    print( "product_of_pairs = ", product_of_pairs )


def printUpperFile(fileName ):
    try:
        file = open(fileName, "r")
    except OSError as err:
        print("OS error: {0}".format(err))
    else:
        for line in file:
            print(line.upper())   
        file.close()
    
def main():
    example1()
    L = [10, 3, 5, 6, 9, 3]
    example2(L)
    example2([10, 3, 5, 6, "NA", 3])
    printUpperFile("notexisting.txt" )

main()


StarcoderdataJupyterScriptsDedupFiltered