22 Lecture 22: ArrayLists
Binary search over sorted ArrayLists, sorting ArrayLists
In the last lecture we began implementing several functions over ArrayLists as methods in a helper utility class. We continue that work in this lecture, designing methods to find an item in an ArrayList matching a predicate, and to sort an ArrayList according to some comparator.
22.1 Finding an item in an arbitrary ArrayList
// In ArrayUtils <T> ??? find(ArrayList<T> arr, IPred<T> whichOne) { ??? }
// In ArrayUtils // Returns the index of the first item passing the predicate, // or -1 if no such item was found <T> int find(ArrayList<T> arr, IPred<T> whichOne) { ??? }
// In ArrayUtils // Returns the index of the first item passing the predicate at or after the // given index, or -1 if no such such item was found <T> int findHelp(ArrayList<T> arr, IPred<T> whichOne, int index) { if (whichOne.apply(arr.get(index)) { return index; } else { return findHelp(arr, whichOne, index + 1); } }
Do Now!
What’s wrong with this code?
// In ArrayUtils // Returns the index of the first item passing the predicate at or after the // given index, or -1 if no such such item was found <T> int findHelp(ArrayList<T> arr, IPred<T> whichOne, int index) { if (index >= arr.size()) { return -1; } else if (whichOne.apply(arr.get(index)) { return index; } else { return findHelp(arr, whichOne, index + 1); } }
Do Now!
What would happen if we had used > instead of >=?
22.2 Finding an item in a sorted ArrayList
Suppose we happen to know that our ArrayList contains items that are comparable, and that the ArrayList itself is sorted. Can we do better than blindly scanning through the entire ArrayList? For concreteness, let’s assume our ArrayList is an ArrayList<String> and we’ll use the built-in comparisons on Strings. We’ll revisit this decision after we’ve developed the method, and generalize it to arbitrary element types.
0 1 2 3 4 5 6 7 8 |
[apple, banana, cherry, date, fig, grape, honeydew, kiwi, watermelon] |
Do Now!
What indices would we check if we were searching for “blueberry”?
// In ArrayUtils // Returns the index of the target string in the given ArrayList, or -1 if the string is not found // Assumes that the given ArrayList is sorted aphabetically int binarySearch(ArrayList<String> strings, String target) { ??? }
// In ArrayUtils // Returns the index of the target string in the given ArrayList, or -1 if the string is not found // Assumes that the given ArrayList is sorted aphabetically int binarySearchHelp(ArrayList<String> strings, String target, int lowIdx, int highIdx) { int midIdx = (lowIdx + highIdx) / 2; if (target.compareTo(strings.get(midIdx)) == 0) { return midIdx; // found it! } else if (target.compareTo(strings.get(midIdx)) < 0) { return this.binarySearchHelp(strings, target, midIdx + 1, highIdx); // too low } else { return this.binarySearchHelp(strings, target, lowIdx, midIdx - 1); // too high } }
Do Now!
What’s wrong with this code?
// In ArrayUtils // Returns the index of the target string in the given ArrayList, or -1 if the string is not found // Assumes that the given ArrayList is sorted aphabetically int binarySearchHelp(ArrayList<String> strings, String target, int lowIdx, int highIdx) { int midIdx = (lowIdx + highIdx) / 2; if (lowIdx > highIdx) { return -1; // not found } else if (target.compareTo(strings.get(midIdx)) == 0) { return midIdx; // found it! } else if (target.compareTo(strings.get(midIdx)) < 0) { return this.binarySearchHelp(strings, target, midIdx + 1, highIdx); // too low } else { return this.binarySearchHelp(strings, target, lowIdx, midIdx - 1); // too high } }
Do Now!
What would happen if we didn’t add or subtract 1 from midIdx in the recursive calls?
We start the search between indices 0 and 8. The middle index is 4, and “fig” is bigger than “clementine”, so we search from the lower bound to the middle index.
We search between indices 0 and 4. The middle index is 2, and “banana” is smaller than “clementine”, so we search from the middle index to the upper bound.
We search between indices 2 and 4. The middle index is 3, and “cherry” is smaller than “clementine”, so we search from the middle index to the upper bound.
We search between indices 3 and 4. The middle index is 3, and “cherry” is smaller than “clementine”, so we search from the middle index to the upper bound.
We search between indices 3 and 4...
Do Now!
What would happen if our exit condition were if (loxIdx >= highIdx)...?
// In ArrayUtils int binarySearch(ArrayList<String> strings, String target) { return this.binarySearchHelp(strings, target, 0, strings.size() - 1); }
22.3 Generalizing to arbitrary element types
// In ArrayUtils <T> int binarySearch(ArrayList<T> arr, T target, IComparator<T> comp) { return this.binarySearchHelp(arr, target, comp, 0, arr.size() - 1); } <T> int binarySearchHelp(ArrayList<T> arr, T target, IComparator<T> comp, int lowIdx, int highIdx) { int midIdx = (lowIdx + highIdx) / 2; if (lowIdx > highIdx) { return -1; } else if (comp.compare(target, strings.get(midIdx)) == 0) { return midIdx; } else if (comp.compare(target, strings.get(midIdx)) < 0) { return this.binarySearchHelp(strings, target, comp, midIdx + 1, highIdx); } else { return this.binarySearchHelp(strings, target, comp, lowIdx, midIdx - 1); } }
22.4 Sorting an ArrayList
0 1 2 3 4 5 6 7 8 |
[kiwi, cherry, apple, date, banana, fig, watermelon, grape, honeydew] |
0 1 2 3 4 5 6 7 8 |
[apple, cherry, kiwi, date, banana, fig, watermelon, grape, honeydew] |
Do Now!
How did we decide that “apple” was the appropriate replacement for “kiwi”?
0 1 2 3 4 5 6 7 8 |
[apple, banana, kiwi, date, cherry, fig, watermelon, grape, honeydew] |
Do Now!
How did we decide that “banana” was the appropriate replacement for “cherry”?
0 1 || 2 3 4 5 6 7 8 |
[apple, banana,|| kiwi, date, cherry, fig, watermelon, grape, honeydew] |
SORTED <--++--> NOT YET SORTED |
MIN |
0 1 || 2 3 4 5 6 7 8 |
[apple, banana,|| kiwi, date, cherry, fig, watermelon, grape, honeydew] |
SORTED <--++--> NOT YET SORTED |
|
Swap items at index 2 and index 3... |
|
0 1 2 || 3 4 5 6 7 8 |
[apple, banana, date,|| kiwi, cherry, fig, watermelon, grape, honeydew] |
SORTED <--++--> NOT YET SORTED |
// In ArrayUtil // EFFECT: Sorts the given list of strings alphabetically void sort(ArrayList<String> arr) { this.sortHelp(arr, 0); // (1) } // EFFECT: Sorts the given list of strings alphabetically, starting at the given index void sortHelp(ArrayList<String> arr, int minIdx) { if (minIdx >= arr.size()) { // (2) return; } else { // (3) int idxOfMinValue = ...find minimum value in not-yet-sorted part... this.swap(arr, minIdx, idxOfMinValue); this.sortHelp(arr, minIdx + 1); // (4) } }
// In ArrayUtil // EFFECT: Sorts the given list of strings alphabetically void sort(ArrayList<String> arr) { for (int idx = 0; // (1) idx < arr.size(); // (2) idx = idx + 1) { // (4) // (3) int idxOfMinValue = ...find minimum value in not-yet-sorted part... this.swap(arr, minIdx, idxOfMinValue); } }
A for loop consists of four parts, which are numbered here (and their corresponding parts are numbered in the recursive version of the code). First is the initialization statement, which declares the loop variable and initializes it to its starting value. This is run only once, before the loop begins. Second is the termination condition, which is checked before every iteration of the loop body. As soon as the condition evaluates to false, the loop terminates. Third is the loop body, which is executed every iteration of the loop. Fourth is the update statement, which is executed after each loop body and is used to advance the loop variable to its next value. Read this loop aloud as “For each value of idx starting at 0 and continuing while idx < arr.size(), advancing by 1, execute the body.”
for (int idx = bigNumber; idx >= smallNumber; idx = idx - 1) { ... }
for (int idx = smallOddNumber; idx < bigNumber; idx = idx + 2) { ... }
Exercise
Practice using the counted-for loop: design a method
<T> ArrayList<T> interleave(ArrayList<T> arr1, ArrayList<T> arr2) that takes two ArrayLists of the same size, and produces an output ArrayList consisting of one item from arr1, then one from arr2, then another from arr1, etc.Design a method
<T> ArrayList<T> unshuffle(ArrayList<T> arr) that takes an input ArrayList and produces a new list containing the first, third, fifth ... items of the list, followed by the second, fourth, sixth ... items.
22.5 Finding the minimum value
Exercise
Design the missing method to finish the sort method above: this method should find the minimum value in the not-yet-sorted part of the given ArrayList<String>.