Insight 23.14.1.
Great code is both efficient and easy to understand. But sometimes those two goals are at odds with each other. In that case, you have to strike a balance or decide which goal is more important for a given situation.
{10, 20, 30, 40, 50} and we want to split it at index 2, we would end up with two ArrayLists: one containing {10, 20} and the other containing {30, 40, 50}.
splitAt and will plan on it taking an index as a parameter. The original ArrayList we call it on will be modified to only contain the items up to that point. To store the items after the split point, we will return a new ArrayList that contains those items. So the function signature might look like this:
ArrayList<T> ArrayList<T>::splitAt(int index);
{10, 20, 30, 40, 50} and we call splitAt(2) on it. Memory might look like the left hand at the start of the call and the right hand at the end of the call (Click to enlarge):
this is pointing to. Nor do we need to actually delete anything. We can just change the logical size of this to reflect that it now only contains the first two items. The new ArrayList we will call rear. It needs to have storage space for the number of items being split off (size - index) in a new array. It might be OK to have more space, but certainly not less. And we will need to copy the items being split off into that new array.
template<typename T>
ArrayList<T> ArrayList<T>::splitAt(int index) {
ArrayList<T> rear;
int numItemsToSplit = m_size - index;
if (rear.m_capacity < numItemsToSplit) {
delete[] rear.m_arr; // free the default array
// change capacity and allocate new array
rear.m_capacity = numItemsToSplit;
rear.m_arr = new T[numItemsToSplit];
}
// copy items from index to the end into rear's array
// "remove" them from the original ArrayList
m_size = index;
return rear;
}
rear has enough capacity and if not, allocates a new array with sufficient capacity. Line 15 handles βremovingβ the items from the original ArrayList by changing the logical size of this.
numItemsToSplit variable. We can use that to loop through the items being split off and copy them into the new array. The items will go into index 0 through numItemsToSplit - 1 of the new array. In the original array, the items we want to copy start at index index. So we need to offset the loop counter by index to get the correct item from the original array.
template<typename T>
ArrayList<T> ArrayList<T>::splitAt(int index) {
... same as before ...
// copy items from index to the end into rear's array
for (int i = 0; i < numItemsToSplit; ++i) {
int oldArrayIndex = index + i;
rear.m_arr[i] = m_arr[oldArrayIndex];
}
... same as before ...
}
removeAt and insertAt to move items. Doing that could look as simple as:
template<typename T>
ArrayList<T> ArrayList<T>::splitAt(int index) {
ArrayList<T> rear;
int numItemsToSplit = m_size - index;
for(int i = 0; i < numItemsToSplit; ++i) {
int value = m_arr[index]; // get the value at the split point
rear.insertEnd(value);
removeAt(index); // remove the item at the split point from "this"
}
return rear;
}
insertEnd function will take care of growing if we need to!
{10, 20, 30, 40, 50}, the first time we remove at index 2, we take the have to shift 40 and 50 down so that {10, 20, 40, 50} are compact in memory. The second time we remove at index 2, we have to shift 50 down. It would be even worse if we were splitting an ArrayList with 1,000 items at index 2 (997 items would need to shift, then 996, then 995, etc...)
removeAt. We can just not remove during the loop and go back to changing the logical size after the loop is complete.
insertEnd is not as problematic. Because it always adds to the end of the ArrayList, we donβt have to do any shifting of existing items. Yes, it might need to grow a few times, but unless we build a way to create the new ArrayList with the correct capacity from the start, we will need to do that work somewhere.
template<typename T>
ArrayList<T> ArrayList<T>::splitAt(int index) {
ArrayList<T> rear;
int numItemsToSplit = m_size - index;
for(int i = 0; i < numItemsToSplit; ++i) {
int oldArrayIndex = index + i;
int value = this->m_arr[oldArrayIndex];
rear.insertEnd(value);
}
m_size = index; // "remove" the items from the original ArrayList
return rear;
}
ArrayList<T> removeSlice(int index, int length) function for ArrayList. (ArrayList<T> is abbreviated as AL<T> in the code.)
index. For example, if we have an ArrayList that contains {10, 20, 30, 40, 50, 60} and we call removeSlice(1, 3), we would remove and return a new list with {20, 30, 40}, leaving behind {10, 50, 60}.
removeAt by first copying items to the slicedItems array, and then moving each item after the slice down to fill the gap.