//****************************************************************//
//**************Recursion - January 25th, 2017*******************//
//**************************************************************//

-AV difficulties, as per usual...well, okay, not THAT usual, but Tech and AV problems go together like planes and turbulence
-Go to the career fair! It's good for you! Eat your social-economic fruits and veggies!
---------------------------------------------------
-So, the next data structures we'll be talking about are TREES! BUT, before we go into those, we need to have a solid grasp of...

-RECURSION: a programming process where, most basically, a method calls itself REPEATEDLY until a defined end point
    -A failure to exit will cause an infinite loop, which will cause a crash, which will cause tears

-Probably the most classic "pedagogical example" of recursion, in Math/CS/Advanced Cookery classes, is with factorials
    -"n = 1*2*3*...*n"
    -Recursive definition:
        f(n) = 1) 1, if n == 0
               2) n * f(n-1), otherwise
        -In good ol' Java:
            public int factorial(int n) {
                if (n == 0) {
                    return 1;
                } else {
                    return n * factorial(n - 1);
                }
            }

-Side Note: Computers now handle infinite loops MUCH more gracefully. In the paper-punch days, an infinite recursive loop would...well...it wasn't pretty (especially since you had to pay for operations by the minute, and they tended to run for a WHILE)

-EVERY recursive method has to have a way of eventually reaching a terminating condition, no matter the input
    -this "terminating condition" is also called the "BASE CASE"; when it is reached, the method does NOT call itself again, but returns control to the previous method call (i.e. whatever called it)
        -There can be multiple base cases in a method, or multiple ways of reaching it, as long as it meets the criteria that it WILL END eventually (w/o catastrophic failure, like a memory crash, the computer rusting away, wild axe attacks, etc.)
        -Also, while you usually do, you do not HAVE to return something in a recursive method
-Behind the scenes, recursive calls are kept track of using a stack

-Another example of a recursive method: BINARY SEARCH
    -It searches an ORDERED LIST for an element by cutting the list in half over and over to find it
        -Initially, low = 0, high = length - 1
    -PSEUDOCODE (for searching integers):
        binarySearch(dataset, targetObj, low, high) {
            if (low > high)
                return false
            else
                mid = (low + high)/2;
                if (targetObj == data[mid])
                    return true
                else if (target < data[mid])
                    return binarySearch(data, targetObj, low, mid-1) //could floor/ceiling, doesn't matter if done properly
                else
                    return binarySearch(data, targetObj, mid+1, high)
        }
        [Example] Searching for "6":
            |1   2   3   4   5|
            1   2  |3   4   5|
            1   2   3   4   |5|
            1   2   3   4   5||
            return false
        -2 terminators: if the low/high indices pass each other (have searched the entire list), OR if we find the target
        -Runs in O(log n) time, which is MUCH better than a linear search
            -Initially searching area in list of size "high - low + 1", which is cut in half each times

-Another function that can be defined recursively is exponentiation, i.e. the "power" function
    - P(a, n) = a^n
    -Recursively: P(a, n) = 1) 1, if n == 0
                            2) a * P(a, n-1), otherwise
        -In Java, the Most Highly Esteemed Steamed Coffee:
            int power(a, n) {
                if (n < 0)
                    return a / power(a, n+1);
                if (n == 0)
                    return 1;
                return a * power(a, n-1);
            }
        -This version runs in O(n) time, and is quite clear to understand...
        -...but WE CAN DO BETTER
    -Recursive squaring! :
        -P(a, n) = 1) 1, if n == 0
                   2) a * P(a, (n-1)/2)^2, if a > 0 and a is odd
                   3) P(a, (n-1)/2)^2, if a > 0 and a is even
        -In PSEUDOCODE (on slides):
            power(a, n) {
                if (n == 0)
                    return 1
                if (a % 2 == 1)
                    y = power(a, (n-1)/2)
                    return a*y*y
                else
                    y = power(a, n/2)
                    return y*y
            }

-There's also a special form of recursion called "Tail Recursion"
    -Tail recursion is when EVERY recursive method call is the VERY LAST STEP before returning from the method
        -This helps with optimization, as it means that methods aren't left "hanging" in the call stack waiting to receive something else before they continue
            -Java does NOT optimize for tail recursions; many languages do when compiling, however

-QUICK EXERCISE: Write a recursive method for determining if a string is a palindrome ("race car", "wow", "1331", "a man a plan a canal panama")
    //My idea: check repeatedly if the first/last letters match (possibly after removing spaces), then do a recursive call with the first/last letters lopped off (via the substring method)
    public boolean isPalindrome(String s) {
        if (s.length == 1) {
            return true;
        }
    }