Dynamic Array Implementation in Python, Exams of Computer Science

A detailed implementation of a dynamic array class in python, which uses a staticarray object as its underlying data storage container. The dynamic array class provides various methods similar to those found in python lists, such as resize, append, insert_at_index, remove_at_index, slice, merge, map, filter, and reduce. The document also includes examples and explanations for each of these methods, demonstrating their usage and behavior. This implementation is part of the cs261 data structures course and is designed to help students understand the fundamental concepts of dynamic arrays and their practical applications in computer science.

Typology: Exams

2023/2024

Available from 08/02/2024

solution-master
solution-master 🇺🇸

3.3

(28)

11K documents

1 / 31

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
OSU CS261 Data Structures Assignment 2
v 1.09 Project 2 Your Very Own Dynamic
Array Oregon State University
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f

Partial preview of the text

Download Dynamic Array Implementation in Python and more Exams Computer Science in PDF only on Docsity!

OSU CS261 Data Structures Assignment 2

v 1.09 Project 2 Your Very Own Dynamic

Array Oregon State University

Your Very Own Dynamic

Array (plus Bag, Stack and

Queue)

General Instructions

  1. Programs in this assignment must be written in Python v3 and submitted to Gradescope before the due date specified in the syllabus. You may resubmit your code as many times as necessary. Gradescope allows you to choose which submission will be graded.
  2. In Gradescope, your code will be through several tests. Any failed tests will provide a brief explanation of testing conditions to help you with troubleshooting. Your goal is to pass all tests.
  3. We encourage you to create your own test programs and cases even though this work won’t have to be submitted and won’t be graded. Gradescope tests are limited in scope and may not cover all edge cases. Your submission must work on all valid inputs. We reserve the right to test your submission with more tests than Gradescope.
  4. Your code must have an appropriate level of comments. At a minimum, each method should have a descriptive docstring. Additionally, put comments throughout the code to make it easy to follow and understand.
  5. You will be provided with a starter “skeleton” code, on which you will build your implementation. Methods defined in the skeleton code must retain their names and input / output parameters. Variables defined in the skeleton code must also retain their names. We will only test your solution by making calls to methods defined in the skeleton code and by checking values of variables defined in the skeleton code. You can add more methods and variables, as needed. However, certain classes and methods cannot be changed in any way. Please see the comments in the skeleton code for guidance. In particular, the content of any methods pre-written for you as part of the skeleton code must not be changed.
  6. Both the skeleton code and the code examples provided in this document are part of the assignment requirements. They have been carefully selected to demonstrate requirements for each method. Refer to them for a detailed description of expected method behavior, input / output parameters, and the handling of edge cases. Code examples may include assignment requirements not explicitly stated elsewhere.
  7. For each method, you can choose to implement a recursive or iterative solution. When using a recursive solution, be aware of maximum recursion depths on large inputs. We will specify the maximum input size that your solution must handle.

Part 1 - Summary and Specific Instructions

  1. Implement a Dynamic Array class by completing the skeleton code provided in the file dynamic_array.py. The DynamicArray class will use a StaticArray object as its underlying data storage container and will provide many methods similar to those we are used to when working with Python lists. Once completed, your implementation will include the following methods: resize() append() insert_at_index() remove_at_index() slice() merge() map() filter() reduce() * Several class methods, like is_empty(), length(), get_at_index() and set_at_index() have been pre-written for you.
  2. We will test your implementations with different types of objects, not just integers. We guarantee that all such objects will have correct implementation of methods eq , lt , gt , ge , le , and str.
  3. The number of objects stored in the array at any given time will be between 0 and 1,000,000 inclusive. An array must allow for the storage of duplicate objects.
  4. Variables in the DynamicArray class are not marked as private. For this portion of the assignment, you are allowed to access and change their values directly. You are not required to write getter or setter methods for them.
  5. RESTRICTIONS: You are not allowed to use ANY built-in Python data structures and/or their methods (lists, dictionaries, etc.). You must solve this portion of the assignment by importing and using objects of the StaticArray class (prewritten for you) and using class methods to write your solution. You are also not allowed to directly access any variables of the StaticArray class (like self.data._data[]). All work must be done by using only StaticArray class methods.

append (self, value: object) -> None:

This method adds a new value at the end of the dynamic array. If the internal storage associated with the dynamic array is already full, you need to DOUBLE its capacity before adding a new value. Example #1: da = DynamicArray() print(da.size, da.capacity, da.data) da.append(1) print(da.size, da.capacity, da.data) print(da) Output: 0 4 STAT_ARR Size: 4 [None, None, None, None] 1 4 STAT_ARR Size: 4 [1, None, None, None] DYN_ARR Size/Cap: 1/4 [1] Example #2: da = DynamicArray() for i in range(9): da.append(i +

  1. print(da) Output: DYN_ARR Size/Cap: 1/4 [101] DYN_ARR Size/Cap: 2/4 [101, 102] DYN_ARR Size/Cap: 3/4 [101, 102, 103] DYN_ARR Size/Cap: 4/4 [101, 102, 103, 104] DYN_ARR Size/Cap: 5/8 [101, 102, 103, 104, 105] DYN_ARR Size/Cap: 6/8 [101, 102, 103, 104, 105, 106] DYN_ARR Size/Cap: 7/8 [101, 102, 103, 104, 105, 106, 107] DYN_ARR Size/Cap: 8/8 [101, 102, 103, 104, 105, 106, 107, 108] DYN_ARR Size/Cap: 9/16 [101, 102, 103, 104, 105, 106, 107, 108, 109] Example #3: da = DynamicArray() for i in range(600): da.append(i) print(da.size) print(da.capacity) Output: 600 1024

insert_at_index (self, index: int, value: object) -> None:

This method adds a new value at the specified index position in the dynamic array. Index 0 refers to the beginning of the array. If the provided index is invalid, the method raises a custom “DynamicArrayException”. Code for the exception is provided in the skeleton file. If the array contains N elements, valid indices for this method are [0, N] inclusive. If the internal storage associated with the dynamic array is already full, you need to DOUBLE its capacity before adding a new value. Example #1: da = DynamicArray([100]) print(da) da.insert_at_index(0, 200) da.insert_at_index(0, 300) da.insert_at_index(0,

  1. print(da) da.insert_at_index(3,
  2. print(da) da.insert_at_index(1,
  3. print(da) Output: DYN_ARR Size/Cap: 1/4 [100] DYN_ARR Size/Cap: 4/4 [400, 300, 200, 100] DYN_ARR Size/Cap: 5/8 [400, 300, 200, 500, 100] DYN_ARR Size/Cap: 6/8 [400, 600, 300, 200, 500, 100] Example #2: da = DynamicArray() try: da.insert_at_index(-1,
  4. except Exception as e: print("Exception raised:", type(e)) da.insert_at_index(0, 200) try: da.insert_at_index(2,
  5. except Exception as e: print("Exception raised:", type(e)) print(da) Output: Exception raised: <class ' main .DynamicArrayException'> Exception raised: <class ' main .DynamicArrayException'> DYN_ARR Size/Cap: 1/4 [200]

remove_at_index (self, index: int) -> None:

This method removes the element at the specified index from the dynamic array. Index 0 refers to the beginning of the array. If the provided index is invalid, the method raises a custom “DynamicArrayException”. Code for the exception is provided in the skeleton file. If the array contains N elements, valid indices for this method are [0, N - 1] inclusive. When the number of elements stored in the array (before removal) is STRICTLY LESS than ¼ of its current capacity, the capacity must be reduced to TWICE the number of current elements. This check / capacity adjustment must happen BEFORE removal of the element. If the current capacity (before reduction) is 10 elements or less, reduction should not happen at all. If the current capacity (before reduction) is greater than 10 elements, the reduced capacity cannot become less than 10 elements. Please see the examples below, especially example #3, for clarifications. Example #1: da = DynamicArray([10, 20, 30, 40, 50, 60, 70, 80]) print(da) da.remove_at_index(0) print(da) da.remove_at_index(6) print(da) da.remove_at_index(2) print(da) Output: DYN_ARR Size/Cap: 8/8 [10, 20, 30, 40, 50, 60, 70, 80] DYN_ARR Size/Cap: 7/8 [20, 30, 40, 50, 60, 70, 80] DYN_ARR Size/Cap: 6/8 [20, 30, 40, 50, 60, 70] DYN_ARR Size/Cap: 5/8 [20, 30, 50, 60, 70] Example #2: da = DynamicArray([1024]) print(da) for i in range(17): da.insert_at_index(i, i) print(da.size, da.capacity) for i in range(16, -1, -1): da.remove_at_index(0) print(da) Output: DYN_ARR Size/Cap: 1/ [1024] 18 32 DYN_ARR Size/Cap: 1/10 [1024]

Example #3: da = DynamicArray() print(da.size, da.capacity) [da.append(1) for i in range(100)] # step 1 - add 100 elements print(da.size, da.capacity) [da.remove_at_index(0) for i in range(68)] # step 2 - remove 68 elements print(da.size, da.capacity) da.remove_at_index(0) # step 3 - remove 1 element print(da.size, da.capacity) da.remove_at_index(0) # step 4 - remove 1 element print(da.size, da.capacity) [da.remove_at_index(0) for i in range(14)] # step 5 - remove 14 elements print(da.size, da.capacity) da.remove_at_index(0) # step 6 - remove 1 element print(da.size, da.capacity) da.remove_at_index(0) # step 7 - remove 1 element print(da.size, da.capacity) for i in range(14): print("Before remove_at_index(): ", da.size, da.capacity, end="") da.remove_at_index(0) print(" After remove_at_index(): ", da.size, da.capacity) Output: 0 4 100 128 32 128 31 128 30 62 16 62 15 62 14 30 Before remove_at_index(): 14 30 After remove_at_index(): 13 30 Before remove_at_index(): 13 30 After remove_at_index(): 12 30 Before remove_at_index(): 12 30 After remove_at_index(): 11 30 Before remove_at_index(): 11 30 After remove_at_index(): 10 30 Before remove_at_index(): 10 30 After remove_at_index(): 9 30 Before remove_at_index(): 9 30 After remove_at_index(): 8 30 Before remove_at_index(): 8 30 After remove_at_index(): 7 30 Before remove_at_index(): 7 30 After remove_at_index(): 6 14 Before remove_at_index(): 6 14 After remove_at_index(): 5 14 Before remove_at_index(): 5 14 After remove_at_index(): 4 14 Before remove_at_index(): 4 14 After remove_at_index(): 3 14 Before remove_at_index(): 3 14 After remove_at_index(): 2 10 Before remove_at_index(): 2 10 After remove_at_index(): 1 10 Before remove_at_index(): 1 10 After remove_at_index(): 0 10

slice (self, start_index: int, size: int) -> object:

This method returns a new Dynamic Array object that contains the requested number of elements from the original array starting with the element located at the requested start index. If the array contains N elements, a valid start_index is in range [0, N - 1] inclusive. A valid size is a non-negative integer. If the provided start index or size is invalid, or if there are not enough elements between the start index and the end of the array to make the slice of the requested size, this method raises a custom “DynamicArrayException”. Code for the exception is provided in the skeleton file. Example #1: da = DynamicArray([1, 2, 3, 4, 5, 6, 7, 8, 9]) da_slice = da.slice(1, 3) print(da, da_slice, sep="
n") da_slice.remove_at_index(0) print(da, da_slice, sep="
n") Output: DYN_ARR Size/Cap: 9/16 [1, 2, 3, 4, 5,

6, 7, 8, 9]

DYN_ARR Size/Cap: 3/4 [2, 3, 4] DYN_ARR Size/Cap: 9/16 [1, 2, 3, 4, 5,

6, 7, 8, 9]

DYN_ARR Size/Cap: 2/4 [3, 4] Example #2: da = DynamicArray([10, 11, 12, 13, 14, 15, 16]) print("SOURCE:", da) slices = [(0, 7), (-1, 7), (0, 8), (2, 3), (5, 0), (5, 3), (6, 1), (6, -1)] for i, cnt in slices: print("Slice", i, "/", cnt, end="") try: print(" --- OK: ", da.slice(i, cnt)) except: print(" --- exception occurred.") Output: SOURCE: DYN_ARR Size/Cap: 7/8 [10, 11,

12, 13, 14, 15, 16]

Slice 0 / 7 --- OK: DYN_ARR Size/Cap:

7/8 [10, 11, 12, 13, 14, 15,

16]

Slice -1 / 7 --- exception occurred. Slice 0 / 8 --- exception occurred. Slice 2 / 3 --- OK: DYN_ARR Size/Cap: 3/4 [12, 13, 14] Slice 5 / 0 --- OK: DYN_ARR Size/Cap: 0/4 []

Slice 5 / 3 --- exception occurred. Slice 6 / 1 --- OK: DYN_ARR Size/Cap: 1/ [16] Slice 6 / -1 --- exception occurred.

map (self, map_func) ->object:

This method creates a new Dynamic Array where the value of each element is derived by applying a given map_func to the corresponding value from the original array. It works similarly to the built-in Python map() function. If you would like to review how Python’s map() works, the following are good resources: Example #1: da = DynamicArray([1, 5, 10, 15, 20, 25]) print(da) print(da.map(lambda x: (x ** 2))) Output: DYN_ARR Size/Cap: 6/8 [1, 5, 10, 15, 20, 25] DYN_ARR Size/Cap: 6/8 [1, 25, 100, 225, 400, 625] Example #2: def double(value): return value * 2 def square(value): return value ** 2 def cube(value): return value ** 3 def plus_one(value): return value + 1 da = DynamicArray([plus_one, double, square, cube]) for value in [1, 10, 20]: print(da.map(lambda x: x(value))) Output: DYN_ARR Size/Cap: 4/4 [2, 2, 1, 1] DYN_ARR Size/Cap: 4/4 [11, 20, 100, 1000] DYN_ARR Size/Cap: 4/4 [21, 40, 400, 8000]

filter (self, filter_func) ->object:

This method creates a new Dynamic Array populated only with those elements from the original array for which filter_func returns True. It works similarly to the built-in Python filter() function. If you would like to review how Python’s filter() works, the following are good resources: Example #1: def filter_a(e): return e > 10 da = DynamicArray([1, 5, 10, 15, 20, 25]) print(da) result = da.filter(filter_a) print(result) print(da.filter(lambda x: (10 <= x <= 20))) Output: DYN_ARR Size/Cap: 6/8 [1, 5, 10, 15, 20, 25] DYN_ARR Size/Cap: 3/4 [15, 20, 25] DYN_ARR Size/Cap: 3/4 [10, 15, 20] Example #2: def is_long_word(word, length): return len(word)

length da = DynamicArray("This is a sentence with some long words".split()) print(da) for length in [3, 4, 7]: print(da.filter(lambda word: is_long_word(word, length))) Output: DYN_ARR Size/Cap: 8/8 [This, is, a, sentence, with, some, long, words] DYN_ARR Size/Cap: 6/8 [This, sentence, with, some, long, words] DYN_ARR Size/Cap: 2/4 [sentence, words] DYN_ARR Size/Cap: 1/4 [sentence]

Part 2 - Summary and Specific Instructions

  1. Implement a Bag ADT class by completing skeleton code provided in the file bag_da.py. You will use the Dynamic Array data structure that you implemented in part 1 of this assignment as the underlying data storage for your Bag ADT.
  2. Once completed, your implementation will include the following methods: add() remove() count() clear() equal()
  3. We will test your implementation with different types of objects, not just integers. We guarantee that all such objects will have correct implementation of methods eq , lt , gt , ge , le , and str.
  4. The number of objects stored in the Bag at any given time will be between 0 and 1,000,000 inclusive. The bag must allow for the storage of duplicate objects.
  5. RESTRICTIONS: You are not allowed to use ANY built-in Python data structures and/or their methods. You must solve this portion of the assignment by importing the DynamicArray class that you wrote in part 1 and using class methods to write your solution. You are also not allowed to directly access any variables of the DynamicArray class (like self.size, self.capacity and self.data in part 1). All work must be done by using only class methods.

add (self, value: object) -> None:

This method adds a new element to the bag. It must be implemented with O(1) amortized runtime complexity. Example #1: bag = Bag() print(bag) values = [10, 20, 30, 10, 20, 30] for value in values: bag.add(value) print(bag) Output: BAG: 0 elements. [] BAG: 6 elements. [10, 20, 30, 10, 20, 30]

remove (self, value: object) -> bool:

This method removes any one element from the bag that matches the provided “value” object. The method returns True if some object was actually removed from the bag. Otherwise it returns False. It must be implemented with O(N) runtime complexity. Example #1: bag = Bag([1, 2, 3, 1, 2, 3, 1, 2, 3]) print(bag) print(bag.remove(7), bag) print(bag.remove(3), bag) print(bag.remove(3), bag) print(bag.remove(3), bag) print(bag.remove(3), bag) Output: BAG: 9 elements. [1, 2, 3, 1, 2, 3, 1, 2, 3] False BAG: 9 elements. [1, 2, 3, 1, 2, 3, 1, 2, 3] True BAG: 8 elements. [1, 2, 1, 2, 3, 1, 2, 3] True BAG: 7 elements. [1, 2, 1, 2, 1, 2, 3] True BAG: 6 elements. [1, 2, 1, 2, 1, 2] False BAG: 6 elements. [1, 2, 1, 2, 1, 2]