// Recursive Boolean

function recurBSearch(array, target) { // Our base case: // If our array is empty, we do not have the target if (array.length === 0) { return false; } // Get a reference to the middle index, and middle element const midIdx = Math.floor(array.length / 2); const midEl = array[midIdx] // We get a subarray that represents our left half by slicing up to but not // including our midIdx. const leftHalf = array.slice(0, midIdx); // We get a subarray that represents our right half by slicing from the // midIdx + 1 all the way to the end of our array (no second argument needed). const rightHalf = array.slice(midIdx + 1); // If our target is smaller than the middle element, repeat this process with // the left half of our array. if (target < midEl) { return recurBSearch(leftHalf, target); } // If our target is larger than the middle element, repeat this process with // the right half of our array. else if (target > midEl) { return recurBSearch(rightHalf, target); } // If neither of these occurred, we found our element and return true. else { return true; } }

// Iterative Boolean

function iterBSearch(array, target) { // Get a reference to our lower and upper bounds that we would like to search // within our array. At the start, this is the entire array, so the indices are // 0 and our length - 1. let lowerIdx = 0; let upperIdx = array.length - 1; // We also create a midIdx variable because we will reassign it at each iteration let midIdx; // While our lowerIdx <= upperIdx, we still have elements that we haven't ruled // out as being our target, so we want our iteration to continue. while (lowerIdx <= upperIdx) { // Get a reference to the middle element within our current bounds. // We are using Math.floor in order to get an integer/valid index. // (If we used ceiling, we would have to do some subtraction in order to get // our first element. For example, [14] has a length 1, so // Math.ceil((0 + 1)/2)) = 1, which is outside our bounds. midIdx = Math.floor((lowerIdx + upperIdx) / 2); // If our target is larger than our current middle element, our lower bound // needs to be moved up past our midIdx so that we look at the right half. if (array[midIdx] < target) { lowerIdx = midIdx + 1; // If our target is smaller than our current middle element, our upper bound // needs to be moved down past our midIdx so that we look at the left half. } else if (array[midIdx] > target) { upperIdx = midIdx - 1; // Otherwise, we have found our target at the midIdx and can return true. } else { return true; } } // If we made it outside of our loop without returning, our target is not in // the array, so we can return false. return false; }

// Recursive Index

function recurBSearchIdx(array, target) { // Our base case // This is another way of checking if the length is 0 // Since 0 is a falsey value, !0 would be truthy, meaning our array is empty // -1 here indicates that the value is not found (very common for -1 to be used // in these situations so that all return values are numbers) if (!array.length) return -1; // Getting a reference to our middle element for our comparisons const midIdx = Math.floor(array.length / 2); const midEl = array[midIdx]; // We get a subarray that represents our left half by slicing up to but not // including our midIdx. const leftHalf = array.slice(0, midIdx); // We get a subarray that represents our right half by slicing from the // midIdx + 1 all the way to the end of our array (no second argument needed). const rightHalf = array.slice(midIdx + 1); // If our target is less than our current middle element, we can recursively // call our function with the left half of the array and return that value. if (target < midEl) { return recurBSearchIdx(leftHalf, target); // If our target is greater than our current middle element we have two scenarios // The first scenario, we did not find our target in the subarray (right side). // In this case, we also want to return -1 to indicate that the value isn't // in our array. // The second scenario is that we find our element in the subarray. // In this case, the return value is going to be the index IN THE SUBARRAY. // In order for us to be able to return what index that corresponds to in // our larger original array, we need to shift this value by where this // subarray starts in our larger array. // Ultimately this means taking our return value and adding on our midIdx + 1 // Take a look at the comments below this function for an example } else if (target > midEl) { const idxShift = recurBSearchIdx(rightHalf, target) if (idxShift === -1) { return -1 } else { return idxShift + midIdx + 1 } // If neither of the above cases are true, we found our element and return that // index (the midIdx that we compared) } else { return midIdx; } }

// Using the right-side shift example: // Array: [1, 2, 3, 4, 5], Target: 5 // The first index that we are going to compare is Math.floor(array.length/2) = 2, // which is our element 3. // Our target is greater than this value, so we need to check the right subarray. // The new array is [4, 5] with the same target of 5. // In this recursive call, the index that we are checking is Math.floor(array.length/2) = 1, // which is our element 5. // Since we found our element, we are hitting our last case of the conditional, // return our index of 1 to where our recursive function was called. // This value of 1 does not line up with the index of our original array, though, // it was the index of our subarray. // In order to convert this into an index that aligns with our current, larger // array, we need to add on the amount that the indices were shifted. Our slicing // of the subarray was acheived by taking the first three elements off. We know // this intuitively, but it can be calculated by adding on our midIdx + 1 that // we originally passed to our slice method. // We end up getting our returned index from the recursion (1) added on to our // midIdx + 1 (2 + 1), getting a final index of 4, which corresponds to where // our element is in the larger array. // This shifting didn’t have to take place on the left side because the indices // of the subarrays are the same as the indices of our larger array (index 1 of // the left subarray is the same element as index 1 of the larger original).

// Recursive Index v2

function recurBSearchIdxV2(array, target, lo = 0, hi = array.length - 1) { // I'm adding a second condition to this base case that Alvin doesn't do. It's // possible that our last element of the array is the target, which would mean // that lo === hi, but we haven't checked that element yet. // This second part of the condition will only ever be evaluated when the first // is true, making that final check. // (This is an edge case that I don't think Alvin accounts for) if (lo === hi && array[lo] !== target) { return -1; } let midIdx = Math.floor((lo + hi) / 2); if (target < array[midIdx]) { return recurBSearchIdxV2(array, target, lo, midIdx); } else if (target > array[midIdx]) { return recurBSearchIdxV2(array, target, midIdx + 1, hi); } else { return midIdx; } }

// Iterative Index

function iterBSearchIdx(array, target) {
  // The implementation of this function is exactly the same as returning a boolean
  // Instead of returning true/false, we return the midIdx.
  // No index shifting needs to take place like in the right-side scenario above
  // because we are never making subarrays; we are only dealing with the indices
  // as they relate to the original array.
  let lowerIdx = 0;
  let upperIdx = array.length - 1;
  let midIdx;

  while (lowerIdx <= upperIdx) {
    midIdx = Math.floor((lowerIdx + upperIdx) / 2);
    if (array[midIdx] < target) {
      lowerIdx = midIdx + 1;
    } else if (array[midIdx] > target) {
      upperIdx = midIdx - 1;
    } else {
      return midIdx;
    }
  }

  return -1;
}
module.exports = { recurBSearch, iterBSearch, recurBSearchIdx, recurBSearchIdxV2, iterBSearchIdx }