Inventory Class
Write an Inventory class, as defined below, that handles the management of inventory for a company. All instances of this class should be initialized by passing an integer value named max_capacity that indicates the maximum number of items that can be stored in inventory. Your Inventory class will need to store items that are represented by a name, price and quantity.
Your class should implement the following methods.
- add_item(name, price, quantity): This method should add an item to inventory and return True if it was successfully added. If adding an item results in the inventory being over capacity your method should return False and omit adding this item to the inventory. Additionally, if an item with the passed name already exists in inventory this method should return False to indicate the item could not be added.
- delete_item(name): This method should delete an item from inventory and return True if the item was successfully deleted. If there is no item with the passed name this method should return False.
- get_most_stocked_item(): This method should return the name of the item that has the highest quantity in the inventory, and return None if there are no items in the inventory. You may assume there will always be exactly one item with the largest quantity, except for the case where the inventory is empty.
- get_items_in_price_range(min_price, max_price): This method should return a list of the names of items that have a price within the specified range (inclusively).
Note: you may assume all input/arguments to your class will be valid and the correct types. For example, the max_capacity will always be greater than or equal to 0 and a valid integer.
See below for an example of how the Inventory class should behave.
>>> i = Inventory(4)
>>> i.add_item('Chocolate', 4.99, 1)
True
>>> i.add_item('Cereal', 6.99, 1)
True
>>> i.add_item('Milk', 3.99, 2)
True
>>> i.add_item('Butter', 2.99, 1)
False
>>> i.delete_item('Bread')
False
>>> i.delete_item('Cereal')
True
>>> i.get_items_in_price_range(4.50, 6.50)
['Chocolate']
Solution
class Inventory:
def __init__(self, max_capacity):
self.max_capacity = max_capacity
self.items = {}
self.item_count = 0
def add_item(self, name, price, quantity):
if name in self.items:
return False
if self.item_count + quantity > self.max_capacity:
return False
self.items[name] = {"name": name, "price": price, "quantity": quantity}
self.item_count += quantity
return True
def delete_item(self, name):
if name not in self.items:
return False
self.item_count -= self.items[name]["quantity"]
del self.items[name]
return True
def get_items_in_price_range(self, min_price, max_price):
item_names = []
for item in self.items.values():
name = item["name"]
price = item["price"]
if min_price <= price <= max_price:
item_names.append(name)
return item_names
def get_most_stocked_item(self):
most_stocked_item_name = None
largest_quantity = 0
for item in self.items.values():
name = item["name"]
quantity = item["quantity"]
if quantity > largest_quantity:
most_stocked_item_name = name
largest_quantity = quantity
return most_stocked_item_name
Student Class
Write a Student class, as defined below, that keeps track of all created students.
Your class should implement the following methods, class variables and properties:
- An instance attribute called name.
- A property called grade that returns the grade of that student. Trying to set the grade should raise a ValueError if the new grade is not a number between 0 and 100.
- A static method called calculate_average_grade(students) that accepts a list of Student objects and returns the average grade for those students. If there are no students in the list, it should return -1.
- A class variable called all_students that stores all of the student objects that have been created in a list.
- A class method named get_average_grade() which returns the average grade of all created students.
- A class method named get_best_student() which returns the student object with the best grade out of all the currently created students. If there are no students created this method should return None. You may assume there will always be one student with the best grade, except in the case where there are no students created.
See below for an example of the behavior of the Student class.
>>> Student.get_average_grade()
-1
>>> student1 = Student("Antoine", 75)
>>> student1.name
"Antoine"
>>> student1.grade
75
>>> student1.grade = 150
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: New grade not in the accepted range of [0-100].
>>> student1 == Student.get_best_student()
True
Solution
class Student:
all_students = []
def __init__(self, name, grade):
self.name = name
self._grade = grade
Student.all_students.append(self)
@property
def grade(self):
return self._grade
@grade.setter
def grade(self, new_grade):
if new_grade < 0 or new_grade > 100:
raise ValueError("New grade not in the accepted range of [0-100].")
self._grade = new_grade
@classmethod
def get_best_student(cls):
best_student = None
for student in cls.all_students:
if best_student == None or best_student.grade < student.grade:
best_student = student
return best_student
@classmethod
def get_average_grade(cls):
return cls.calculate_average_grade(cls.all_students)
@staticmethod
def calculate_average_grade(students):
if len(students) == 0:
return -1
total = 0
for student in students:
total += student.grade
return total / len(students)
Geometry Inheritance
Create 4 classes: Polygon, Triangle, Rectangle and Square. The Triangle and Rectangle class should be subclasses of Polygon, and Square should be a subclass of Rectangle.
Your Polygon class should raise a NotImplementedError when the get_area() and get_sides() methods are called. However, it should correctly return the perimeter of the polygon when get_perimeter() is called. Treat the Polygon class as an abstract class.
Your Triangle class should have a constructor that takes in 3 arguments, which will be the lengths of the 3 sides of the triangle. You may assume the sides passed to the constructor will always form a valid triangle.
Your Rectangle class should have a constructor that takes in 2 arguments, which will be the width and height of the Rectangle.
Your Square class should have a constructor that takes in 1 argument, which will be the length of each side of the Square.
Your Triangle and Rectangle classes should both implement the following methods:
- get_sides(): This method returns a list containing the lengths of the sides of the shape.
- get_area(): This method returns the area of the polygon.
Your Square class should only have an implementation for its constructor, and rely on the Rectangle superclass for implementations of get_sides() and get_area().
Note: To calculate the area of a triangle given three side lengths (x, y and z) you can use the following formula. First calculate the semi perimeter s using: s = (x + y + z) / 2. Then calculate the area A using: A = math.sqrt(s * (s - x) * (s - y) * (s - z)).
See below for an example of how these classes should behave.
>>> triangle = Triangle(2, 5, 6)
>>> triangle.get_area()
4.68
>>> Square(4).get_perimeter()
16
>>> Rectangle(3, 5).get_sides()
[3, 5, 3, 5]
Solution
import math
class Polygon:
def get_sides(self):
raise NotImplementedError
def get_area(self):
raise NotImplementedError
def get_perimeter(self):
return sum(self.get_sides())
class Triangle(Polygon):
def __init__(self, side1, side2, side3):
self.sides = [side1, side2, side3]
def get_sides(self):
return self.sides
def get_area(self):
side1, side2, side3 = self.sides
return get_triangle_area(side1, side2, side3)
class Rectangle(Polygon):
def __init__(self, width, height):
self.width = width
self.height = height
def get_sides(self):
return [self.width, self.height, self.width, self.height]
def get_area(self):
return get_rectangle_area(self.width, self.height)
class Square(Rectangle):
def __init__(self, side_length):
super().__init__(side_length, side_length)
def get_triangle_area(side1, side2, side3):
semi_perimeter = (side1 + side2 + side3) / 2
return math.sqrt(
semi_perimeter *
(semi_perimeter - side1) *
(semi_perimeter - side2) *
(semi_perimeter - side3)
)
def get_rectangle_area(width, height):
return width * height
Deck Class
Create a Deck class that represents a deck of 52 playing cards. The Deck should maintain which cards are currently in the deck and never contain duplicated cards. Cards should be represented by a string containing their value (2 - 10, J, Q, K, A) followed by their suit (D, H, C, S). For example, the jack of clubs would be represented by “JC” and the three of hearts would be represented by “3H”.
Your Deck class should implement the following methods:
- shuffle(): This method shuffles the cards randomly, in place. You may use the random.shuffle() method to help you do this.
- deal(n): This method removes and returns the last n cards from the deck in a list. If the deck does not contain enough cards it returns all the cards in the deck.
- sort_by_suit(): This method sorts the cards by suit, placing all the hearts first, diamonds second, clubs third and spades last. The order within each suit (i.e. the card values) does not matter. This method should sort the cards in place, it does not return anything.
- contains(card): This method returns True if the given card exists in the deck and False otherwise.
- copy(): This method returns a new Deck object that is a copy of the current deck. Any modifications made to the new Deck object should not affect the Deck object that was copied.
- get_cards(): This method returns all the cards in the deck in a list. Any modifications to the returned list should not change the Deck object.
- len(): This method returns the number of the cards in the Deck.
Your deck should always start with exactly 52 cards that are distributed across 4 suits and 13 values where there are no duplicate cards.
See below for an example of how the Deck class should behave.
>>> d = Deck()
>>> d.shuffle()
>>> d.deal(3)
["AS", "2H", "4D"]
>>> d.contains("4D")
False
>>> d.sort_by_suit()
>>> d.deal(3)
['2S', '5S', 'JS']
>>> len(d)
46
Solution
import random
class Deck:
suits = ["H", "D", "C", "S"]
values = [str(i) for i in range(2, 11)] + ["J", "Q", "K", "A"]
def __init__(self):
self.cards = []
self.fill_deck()
def fill_deck(self):
for suit in Deck.suits:
for value in Deck.values:
card = value + suit
self.cards.append(card)
def shuffle(self):
random.shuffle(self.cards)
def deal(self, n):
dealt_cards = []
for i in range(n):
if len(self.cards) == 0:
break
card = self.cards.pop()
dealt_cards.append(card)
return dealt_cards
def sort_by_suit(self):
cards_by_suit = {"H": [], "D": [], "C": [], "S": []}
for card in self.cards:
suit = card[-1]
cards_by_suit[suit].append(card)
self.cards = (
cards_by_suit["H"] +
cards_by_suit["D"] +
cards_by_suit["C"] +
cards_by_suit["S"]
)
def contains(self, card):
return card in self.cards
def copy(self):
new_deck = Deck()
new_deck.cards = self.cards[:]
return new_deck
def get_cards(self):
return self.cards[:]
def __len__(self):
return len(self.cards)
FileSystem Implementation
In this question, you need to implement a very simplistic FileSystem class that mimics the way that your own computer’s FileSystem works. A FileSystem starts empty with only a root node which will always be a directory.
A FileSystem is a tree-like structure composed of nodes, each of which is either a File or Directory.
Files are simplest and only have name and contents as attributes; which correspond to the name of the file and its contents, respectively. Files also have a write method, which sets the contents of that file to the argument passed in. Additionally, files override the len dunder method which returns the number of characters in the contents of that file.
Directories have a name and a children attribute. children is a dictionary that stores the name of its children nodes as keys, and the nodes themselves as the values of that dictionary. Directories also have the add and delete methods which are used to add or delete nodes from its children dictionary.
For your convenience, the str methods of each class have been overridden so that you may debug your FileSystem more easily.
Your task is to implement the following methods on the FileSystem class:
- create_directory(path): This method should create a Directory inside the FileSystem at the location specified. For instance, create_directory(“/dir1”) should create a directory as a child of the root of the filesystem called “dir1”. Running create_directory(“/dir1/dir2”) should create another directory, dir2, inside the one that was just created. If the path is malformed or the operation is impossible, it should raise a ValueError.
- create_file(path, contents): This method should create a new file at the desired path, with the contents passed in. If the operation is impossible, it should raise a ValueError.
- read_file(path): This method should return the contents of the file at the path parameter. If no such file exists, it should raise a ValueError.
- delete_directory_or_file(path): This method should delete the node located at path. It should work on files and directories alike, and should raise a ValueError if that file does not exist.
- size(): This method should return the number of characters across all files in your filesystem.
- _find_bottom_node(node_names): This is a private helper method of the FileSystem class that takes in a list of node names and should traverse the filesystem downwards until the last node in the list. For instance, calling this with [“a”, “b”, “c”] should first look for a node a inside the root node of the filesystem, then for a node b inside node a, and then return the node c which should be a child of node b.
Note: for all methods that accept a path parameter you will need to first validate that path and then parse it. The path will be a string, and from that string you’ll need to do one of the following:
- Obtain the directory object used to create a new directory or file inside of.
- Obtain the directory object used to delete a directory or file.
- Obtain the file object to read the contents of.
This is non-trivial because you may need to distinguish between the name of the new node create and the path where this node should be created.
See below for an example of how these classes should behave.
>>> fs = FileSystem()
>>> fs.create_directory("/dir1")
>>> fs.create_file("/file1.txt", "Hello World!")
>>> print(fs)
*** FileSystem ***
/ (Directory)
dir1 (Directory)
file1.txt (File) | 12 characters
***
>>> fs.delete("/file2.txt")
ValueError: file2.txt does not exist.
Solution
class FileSystem:
def __init__(self):
self.root = Directory("/")
def create_directory(self, path):
FileSystem._validate_path(path)
path_node_names = path[1:].split("/")
middle_node_names = path_node_names[:-1]
new_directory_name = path_node_names[-1]
before_last_node = self._find_bottom_node(middle_node_names)
if not isinstance(before_last_node, Directory):
raise ValueError(f"{before_last_node.name} isn't a directory.")
new_directory = Directory(new_directory_name)
before_last_node.add_node(new_directory)
def create_file(self, path, contents):
FileSystem._validate_path(path)
path_node_names = path[1:].split("/")
middle_node_names = path_node_names[:-1]
new_file_name = path_node_names[-1]
before_last_node = self._find_bottom_node(middle_node_names)
if not isinstance(before_last_node, Directory):
raise ValueError(f"{before_last_node.name} isn't a directory.")
new_file = File(new_file_name)
new_file.write_contents(contents)
before_last_node.add_node(new_file)
def read_file(self, path):
FileSystem._validate_path(path)
path_node_names = path[1:].split("/")
middle_node_names = path_node_names[:-1]
file_name = path_node_names[-1]
before_last_node = self._find_bottom_node(middle_node_names)
if not isinstance(before_last_node, Directory):
raise ValueError(f"{before_last_node.name} isn't a directory.")
if file_name not in before_last_node.children:
raise ValueError(f"File not found: {file_name}.")
return before_last_node.children[file_name].contents
def delete_directory_or_file(self, path):
FileSystem._validate_path(path)
path_node_names = path[1:].split("/")
middle_node_names = path_node_names[:-1]
node_to_delete_name = path_node_names[-1]
before_last_node = self._find_bottom_node(middle_node_names)
if not isinstance(before_last_node, Directory):
raise ValueError(f"{before_last_node.name} isn't a directory.")
if node_to_delete_name not in before_last_node.children:
raise ValueError(f"Node not found: {node_to_delete_name}.")
before_last_node.delete_node(node_to_delete_name)
def size(self):
size = 0
nodes = [self.root]
while len(nodes) > 0:
current_node = nodes.pop()
if isinstance(current_node, Directory):
children = list(current_node.children.values())
nodes.extend(children)
continue
if isinstance(current_node, File):
size += len(current_node)
return size
def __str__(self):
return f"*** FileSystem ***\n" + self.root.__str__() + "\n***"
@staticmethod
def _validate_path(path):
if not path.startswith("/"):
raise ValueError("Path should start with `/`.")
def _find_bottom_node(self, node_names):
current_node = self.root
for node_name in node_names:
if not isinstance(current_node, Directory):
raise ValueError(f"{current_node.name} isn't a directory.")
if node_name not in current_node.children:
raise ValueError(f"Node not found: {node_name}.")
current_node = current_node.children[node_name]
return current_node
class Node:
def __init__(self, name):
self.name = name
def __str__(self):
return f"{self.name} ({type(self).__name__})"
class Directory(Node):
def __init__(self, name):
super().__init__(name)
self.children = {}
def add_node(self, node):
self.children[node.name] = node
def delete_node(self, name):
del self.children[name]
def __str__(self):
string = super().__str__()
children_strings = []
for child in list(self.children.values()):
child_string = child.__str__().rstrip()
children_strings.append(child_string)
children_combined_string = indent("\n".join(children_strings), 2)
string += "\n" + children_combined_string.rstrip()
return string
class File(Node):
def __init__(self, name):
super().__init__(name)
self.contents = ""
def write_contents(self, contents):
self.contents = contents
def __len__(self):
return len(self.contents)
def __str__(self):
return super().__str__() + f" | {len(self)} characters"
def indent(string, number_of_spaces):
spaces = " " * number_of_spaces
lines = string.split("\n")
indented_lines = [spaces + line for line in lines]
return "\n".join(indented_lines)