Here are lists of functions to implement for the vector:
Private function
resize(new_capacity)
- double the capacity when vector is full
- halve the capacity if size is 1/4 of capacity
Public function
size()
- returns number of items in vectorcapacity()
- returns number of items it can holdis_empty()
- returns true if empty, else falseat(index)
- returns item at given indexpush_back(data)
- adds data at the end of the vectorpop_back()
- removes the data from the end and returns itprepend(data)
- inserts data at index 0insert(index, data)
- inserts data at index, shifts that index's value and trailing elements to the rightdelete(index)
- deletes data at index and shift all trailing elements leftremove(data)
- find and remove all data from the vectorfind(data)
- looks for value and returns first index with that value, -1 if not found
Things to keep in mind:
- Time complexity
- constant time to add and remove data at the end, access, or update.
- linear time to insert or remove elsewhere because of shifting.
- Space complexity
- linear -> (array capacity) * (size of data)
Implementation
Vector structure
I first defined a structure for the vector:
1typedef struct { 2 int *arr; 3 int capacity; 4 int size; 5} Vector;
arr
is the array and it's declared as a pointer since it's a mutable array. So it needs to be dynamic not static.
I implemented init()
function to initialize the vector and destroy()
function to destory the instance at the end.
1#define INF 0x7FFFFFFF 2#define DEFAULT_SIZE 16 3 4void init(Vector *vec) { 5 vec->capacity = DEFAULT_SIZE; 6 vec->size = 0; 7 vec->arr = (int *)(malloc(DEFAULT_SIZE * sizeof(int))); 8 9 // initialize the element as infinity 10 for(int i=0; i<DEFAULT_SIZE; ++i) { 11 (vec->arr)[i] = INF; 12 } 13} 14 15void destroy(Vector *vec) { 16 free(vec->arr); 17 vec->arr = NULL; 18 vec->size = 0; 19 vec->capacity = 0; 20} 21 22int main() { 23 Vector vec; 24 init(&vec); 25 26 ... 27 28 destory(&vec); 29 30 return 0; 31}
Resize the vector
Before we talk about adding/removing data, lets take a look at how resizing is done.
We have 2 cases for resizing:
- if the vector is full and need to double the capacity.
- if number of data in vector is 1/4 of capacity and need to halve the capacity.
We can check conditions and determine whether to double or halve the capacity in resize
, but I didn't wanted to do that.
1static void resize(Vector *vec, int capacity) { 2 vec->capacity = capacity; 3 int *temp = (int *)(malloc(vec->capacity * sizeof(int))); 4 5 // copy over data 6 for(int i=0; i<vec->size; ++i) { 7 temp[i] = (vec->arr)[i]; 8 } 9 10 free(vec->arr); 11 vec->arr = temp; 12}
When adding or removing data, we will check the size and pass in the appropriate capacity in resize
. Then this function will create a new array with the new capacity, copy over existing data, and re-assign the instance's array.
Add data at the end
If we have 5 elements in the vector, vec->size
will be 5
, but the index 5 means the 6th element. So we can use vec->size
to add a data at the end in constant time.
But we need to make sure that we have enough space. When vector is full, we double the capacity by calling the function resize
with doubled capacity
.
1void push_back(Vector *vec, int size) { 2 // double the size if vector is full 3 if(vec->size == vec->capacity) { 4 resize(vec, vec->capacity << 1); 5 } 6 7 (vec->arr)[vec->size] = size; 8 ++(vec->size); 9}
Insert data in the beginning
We need to shift all elements once to the right to empty the first element. Resizing is already covered, so no need worry about going off the boundary when shifting data.
1void prepend(Vector *vec, int data) { 2 // double the size if vector is full 3 if(vec->size == vec->capacity) { 4 resize(vec, vec->capacity << 1); 5 } 6 7 ++(vec->size); 8 for(int i=(vec->size)-1; i>0; --i) { 9 (vec->arr)[i] = (vec->arr)[i-1]; 10 } 11 12 (vec->arr)[0] = data; 13}
Insert data at any index
If inserting in the beginning or at the end, I utilized functions that I implemented earlier: prepend
and push_back
.
For the other case, I shifted all elements from given idx
to the end once to the right.
1void insert(Vector *vec, int idx, int data) { 2 // double the size if vector is full 3 if(vec->size == vec->capacity) { 4 resize(vec, vec->capacity << 1); 5 } 6 7 if(idx == 0) { 8 prepend(vec, data); 9 } else if (idx >= vec->size) { 10 push_back(vec, data); 11 } else { 12 ++(vec->size); 13 for(int i=vec->size-1; i>idx; --i) { 14 (vec->arr)[i] = (vec->arr)[i-1]; 15 } 16 17 (vec->arr)[idx] = data; 18 } 19}
Remove data at the end
Removing is straightforward. Simply set the last element to null
or INF
in this case.
Once you removed an element, check if the size is smaller than that of quarter of the capacity. If so, halve the total capacity.
1int pop_back(Vector *vec) { 2 if(is_empty(vec)) { 3 printf(".... vector is empty\n"); 4 return -1; 5 } 6 7 // if size is 1/4 of capacity, halve the capacity 8 int quart = vec->capacity / 4; 9 if(vec->capacity > DEFAULT_SIZE && vec->size <= quart) { 10 resize(vec, vec->capacity >> 1); 11 } 12 13 --(vec->size); 14 int data = (vec->arr)[vec->size]; 15 (vec->arr)[vec->size] = INF; 16 17 return data; 18}
Remove data at any index
When given index is greater than the total number of elements, instead of throwing an error, I decided to remove the last element by calling pop_back
.
When the index is somewhere between, I shift all the elements once to the left starting from the given idx
.
1int delete(Vector *vec, int idx) { 2 if(idx >= vec->size) { 3 return pop_back(vec); 4 } 5 6 int data = (vec->arr)[idx]; 7 8 for(int i=idx; i<vec->size; ++i) { 9 (vec->arr)[i] = (vec->arr)[i+1]; 10 } 11 12 --(vec->size); 13 (vec->arr)[vec->size] = INF; 14 15 return data; 16}
Remove all data that matches the given value
I used a temporary array to store all elements that isn't the value
to delete.
As I iterate the array, I will have a separate index(p
) which tracks down the element that we're going to keep.
Eventually the p
will become the total size after the removal.
1void _remove(Vector *vec, int value) { 2 if(is_empty(vec)) return; 3 4 int p = 0; 5 int *temp = (int *)(malloc(vec->capacity * sizeof(int))); 6 for(int i=0; i<vec->size; ++i) { 7 if((vec->arr)[i] != data) { 8 temp[p] = (vec->arr)[i]; 9 ++p; 10 } 11 } 12 13 vec->size = p; 14 free(vec->arr); 15 vec->arr = temp; 16 17 // if size is 1/4 of capacity, halve the capacity 18 int quart = vec->capacity / 4; 19 if(vec->capacity > DEFAULT_SIZE && vec->size <= quart) { 20 resize(vec, vec->capacity >> 1); 21 } 22}
Find data
Iterate the array and check if the data is what we're looking for. If found, return that index else return -1
.
1int find(Vector *vec, int data) { 2 int idx = -1; 3 4 for(int i=0; i<vec->size; ++i) { 5 if((vec->arr)[i] == data) { 6 idx = i; 7 break; 8 } 9 } 10 11 return idx; 12}