diff --git a/circle.py b/circle.py new file mode 100644 index 0000000..32bfe8d --- /dev/null +++ b/circle.py @@ -0,0 +1,31 @@ +# Data Structures & Algorithms Spring 2024 +# Problem Set 2 +# Lonny Chen, 216697 +# circle.py + +import math + +class Circle: + rounding_int_default = 2 + + def __init__(self, radius): + '''Constructs Circle with instance attributes.''' + self.radius = radius + + def area(self, rounding_int=rounding_int_default): + '''Returns area of this Circle with input rounding.''' + result = math.pi * (self.radius ** 2) + if rounding_int == 0: + rounded_result = round(result) + else: + rounded_result = round(result, rounding_int) + return rounded_result + + def perimeter(self, rounding_int=rounding_int_default): + '''Returns perimeter of this Circle with input rounding.''' + result = 2 * math.pi * self.radius + if rounding_int == 0: + rounded_result = round(result) + else: + rounded_result = round(result, rounding_int) + return rounded_result diff --git a/flask_app.py b/flask_app.py index 8897fa2..892e20b 100644 --- a/flask_app.py +++ b/flask_app.py @@ -1,23 +1,32 @@ -from flask import Flask, render_template, request +# Data Structures & Algorithms Spring 2024 +# Problem Set 2 +# Lonny Chen, 216697 +# flask_app.py +from flask import Flask, render_template, request from helper import perform_calculation, convert_to_float +from circle import Circle app = Flask(__name__) # create the instance of the flask class - +# route() decorator tells Flask: base URL triggers index "home page" @app.route('/') @app.route('/home') def home(): + '''Home page template''' return render_template('home.html') - +# route() decorator tells Flask: '/calculate' URL triggers calculation @app.route('/calculate', methods=['GET', 'POST']) # associating the GET and POST method with this route def calculate(): + '''Calculation of mathematical operation from HTML form values, includes input error checking.''' + if request.method == 'POST': # using the request method from flask to request the values that were sent to the server through the POST method value1 = request.form['value1'] value2 = request.form['value2'] operation = str(request.form['operation']) + rounding = request.form['rounding'] # make sure the input is one of the allowed inputs (not absolutely necessary in the drop-down case) if operation not in ['add', 'subtract', 'divide', 'multiply']: @@ -27,14 +36,62 @@ def calculate(): try: value1 = convert_to_float(value=value1) value2 = convert_to_float(value=value2) + rounding_int = int(float(rounding)) + assert rounding_int >= 0 except ValueError: - return render_template('calculator.html', printed_result="Cannot perform operation with this input") + return render_template('calculator.html', printed_result="Entered values should be numbers!") + except AssertionError: + return render_template('calculator.html', printed_result="Rounding should not be negative!") try: - result = perform_calculation(value1=value1, value2=value2, operation=operation) - return render_template('calculator.html', printed_result=str(result)) + result = perform_calculation(value1=value1, value2=value2, operation=operation, rounding_int=rounding_int) + printed_result = f'The result of {value1} {operation} {value2} is: {result} (rounded to {rounding_int} decimals)' + return render_template('calculator.html', printed_result=printed_result) except ZeroDivisionError: return render_template('calculator.html', printed_result="You cannot divide by zero") return render_template('calculator.html') + +# route() decorator tells Flask: '/circle' URL triggers circle calculation +@app.route('/circle', methods=['GET', 'POST']) # associating the GET and POST method with this route +def circle(): + '''Calculation of circle calculation from HTML form values, includes input error checking.''' + + if request.method == 'POST': + # using the request method from flask to request the values that were sent to the server through the POST method + radius = request.form['radius'] + operation = str(request.form['operation']) + rounding = request.form['rounding'] + + # make sure the input is one of the allowed inputs (not absolutely necessary in the drop-down case) + if operation not in ['perimeter', 'area']: + return render_template('circle.html', + printed_result='Operation must be one of "perimeter" or "area".') + + try: + radius = convert_to_float(value=radius) + rounding = convert_to_float(value=rounding) + assert radius >= 0 and rounding >= 0 + except ValueError: + return render_template('circle.html', printed_result="Entered values should be numbers!") + except AssertionError: + return render_template('circle.html', printed_result="Radius or rounding should not be negative!") + + # Circle class calculations + rounding_int = int(rounding) + circle = Circle(float(radius)) + if operation == 'perimeter': + result = circle.perimeter(rounding_int) + else: + result = circle.area(rounding_int) + printed_result = f'For a circle with radius of {radius}, the {operation} is: {result} (rounded to {rounding_int} decimals)' + + return render_template('circle.html', printed_result=printed_result) + + return render_template('circle.html') + +# So web app can be refreshed in browser after changes +# Can just enter on command line: "python flask_app.py" +if __name__ == '__main__': + app.run(debug=True) \ No newline at end of file diff --git a/helper.py b/helper.py index 31e82d6..1404484 100644 --- a/helper.py +++ b/helper.py @@ -1,7 +1,11 @@ -# create helper functions for calculations +# Data Structures & Algorithms Spring 2024 +# Problem Set 2 +# Lonny Chen, 216697 +# helper.py +# create helper functions for calculations -def perform_calculation(value1: float, value2: float, operation: str) -> float: +def perform_calculation(value1: float, value2: float, operation: str, rounding_int: int) -> float: """ Perform a mathematical operation on two values. @@ -9,9 +13,10 @@ def perform_calculation(value1: float, value2: float, operation: str) -> float: value1 (float): The first value. value2 (float): The second value. operation (str): The operation to perform. Can be 'add', 'subtract', 'divide', or 'multiply'. + rounding_int (int): Number of decimal places to round result to. Returns: - float: The result of the operation. + float or int: The result of the operation. Raises: ZeroDivisionError: If attempting to divide by zero. @@ -25,7 +30,12 @@ def perform_calculation(value1: float, value2: float, operation: str) -> float: else: result = value1 * value2 - return result + if rounding_int == 0: + rounded_result = round(result) + else: + rounded_result = round(result, rounding_int) + + return rounded_result def convert_to_float(value: str) -> float: diff --git a/templates/calculator.html b/templates/calculator.html index 3dd1105..e7a6379 100644 --- a/templates/calculator.html +++ b/templates/calculator.html @@ -1,6 +1,6 @@ {% extends 'layout.html' %} {% block content %} -

Calculator

+

General Calculator

@@ -9,10 +9,13 @@

Calculator

+ + +
diff --git a/templates/circle.html b/templates/circle.html new file mode 100644 index 0000000..82d8294 --- /dev/null +++ b/templates/circle.html @@ -0,0 +1,23 @@ +{% extends 'layout.html' %} +{% block content %} +

Circle Calculator

+
+ + + + + + + + + +
+ +
+ + {{ printed_result }} + +{% endblock %} \ No newline at end of file diff --git a/templates/layout.html b/templates/layout.html index 8de2e62..b29886d 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -12,6 +12,7 @@

Home

diff --git a/test_circle.py b/test_circle.py new file mode 100644 index 0000000..35bca9a --- /dev/null +++ b/test_circle.py @@ -0,0 +1,67 @@ +# Data Structures & Algorithms Spring 2024 +# Problem Set 2 +# Lonny Chen, 216697 +# test_circle.py +# Usage: pytest test_circle.py or Run in PyCharm IDE + +from circle import Circle +import math +import random + +def check_circle_area(radius, rounding): + '''A Circle with input radius returns the expected area with input rounding.''' + circle = Circle(radius) + expected = round(math.pi * (radius ** 2), rounding) + result = circle.area(rounding) + print(f'Testing area of circle with radius {radius}, rounding {rounding}') + print(f'Expected: {expected}') + print(f'Result: {result}') + assert expected == result + +def check_circle_perimeter(radius, rounding): + '''A Circle with input radius returns the expected perimeter with input rounding.''' + circle = Circle(radius) + expected = round(2 * math.pi * radius, rounding) + result = circle.perimeter(rounding) + print(f'Testing perimeter of circle with radius {radius}, rounding {rounding}') + print(f'Expected: {expected}') + print(f'Result: {result}') + assert expected == result + +def generate_random_seed(min, max): + '''Generate a random seed between input min and max.''' + seed = random.randint(min, max) + print(f'\nRandom seed is: {seed}') #note for reproducability + random.seed(seed) + +def test_directed_circle_area(radius_list = [0, 1, 2, math.pi, 10, 100, 1e6]): + '''Test Circle::area() with interesting radius values.''' + generate_random_seed(1, 100) + for r in radius_list: + rounding = random.randint(0, 10) + check_circle_area(r, rounding) + +def test_directed_circle_perimeter(radius_list = [0, 1, 2, math.pi, 10, 100, 1e6]): + '''Test Circle::perimeter() with interesting radius values.''' + generate_random_seed(1, 100) + for r in radius_list: + rounding = random.randint(0, 10) + check_circle_perimeter(r, rounding) + +def test_random_circle_area(iterations=10): + ''' Test Circle::area() with random radius and rounding for input number of iterations.''' + generate_random_seed(1, 100) + for i in range(0, iterations): + print(f'\nIteration #{i}') + radius = random.uniform(0,1000) + rounding = random.randint(0, 10) + check_circle_area(radius, rounding) + +def test_random_circle_perimeter(iterations=10): + ''' Test Circle::perimeter() with random radius and rounding for input number of iterations.''' + generate_random_seed(1, 100) + for i in range(0, iterations): + print(f'\nIteration #{i}') + radius = random.uniform(0,1000) + rounding = random.randint(0, 10) + check_circle_perimeter(radius, rounding)