Even in our "Structural Recursion" examples such as Bullseye and OurList, we were certainly using recursive functions in our implementation. The issue is that in structural recursion, there was some state information inherently stored in the objects and we sometimes used the identical parameterization for a recursive call but new that we were calling the function upon a different object.
For more pure functional recursion all "state" of the computational process must be captured purely within the parameterization of the function call. That is, the behavior of the call depends solely on the information given in the parameters. We will not want to have a recursive function that calls itself with the identical parameters because that is a sure case of infinite recursion (because the next call will behave in exactly the same way).
Customizing Parameterization
Because of the need to express more through parameterization, we may sometimes find that we want to have additional parameters to help define the recursive structure. For example, in our binary search example, we preferred a parameterization that was search(lexicon, target, start, stop) so that we could pass relevant indices that defined the current scope of the search. Of course, from a user's perspective, we shouldn't care about whether recursion is being used to solve a problem. So the more natural way to phrase a question of whether a given target is in a lexicon is the simpler calling syntax search(lexicon, target).
So a common approach is to hide the complexity from the public interface by defining the recursive function more privately, thus as the underscored _search(lexicon, target, start, stop). Then we can have the body of the public-facing function invoke the private recursive function with appropriate parameterization to get the process started.
Binary Search vs Sequential Search
Binary search is faster. So why use Sequential Search? Because binary search requires that data be sorted and sometimes we might not have control over keeping the data sorted, or we might not want to take the extra time that is required to keep the data sorted as it is updated.
So sequential search is still used quite often, but of course we would certainly prefer binary search if we know we have a sorted list!
Recursion vs Loops
"Single Recursion", "Tail Recursion"
Multiple Recursion
e.g., our graphical Tree implementation, or the anagrams function
Approximate Searches and Range Searches
Note that if you are just looking for whether or not an item is in a collection, while binary search is faster than sequential search, the hashing-based approaches used by sets and dictionaries are even faster than binary search! So we'd still typically use the hash-based implementations for doing things like trying to retrieve a customers account based on an account number or unique username.
However, binary search has a great advantage that it can be easily customized to do things that are NOT possible with the hash-based structures. In particular, if you want to find the nearest match to a value (even if the exact value cannot be found), that can be done with binary search.
Similarly, if you want to find all entries that are in some given range (e.g., find me all scores between 80 and 90) that can be done quite naturally using variants of binary search, but those can't be done with hash-based because all the efficiency of those structures rely on the fact that they are intentionally shuffling the data and NOT keeping in in order. More importantly, a hash function tells you where to find some value V but it doesn't help at all in knowing where some nearby V' might be found.
The focus of Monday's quiz activity will be on such variants of binary search.
Michael Goldwasser
CSCI 1300/5001, Fall 2019
Last modified: Monday, 30 December 2019