3

I am trying to implement a binary search on an array. I need to find the position a value should be placed to keep order in the array. I can find plenty of posts searching for existing values but not necessarily for correct position of a non-existent value. I would need this to work for arrays with even or odd lengths.

$vals= [1,2,4,6,7,9,13];

$test_1 = 5; // should return 3
$test_2 = 10; // should return 6

function mySearch(array $array, $start, $end, $value){
    if($end<$start){return false;}

    $mid = floor(($end + $start)/2);

    while($mid != $value){
        if($mid > $value){
            return mySearch($array, $start, $mid-1, $value);
        }elseif($mid < $value){
            return mySearch($array, $mid+1, $end, $value);
        }
    }
    echo $mid;

}
Newb 4 You BB
  • 782
  • 2
  • 18
  • Would you ever have a scenario where the value would already exist in the array? For example you're checking for 10 and 10 is already present – Brett Gregson Sep 18 '19 at 15:01
  • @BrettGregson, if this function comes up we would have already searched the array for the existence of the value. So it shouldnt be present. – Newb 4 You BB Sep 18 '19 at 15:02

1 Answers1

1

I believe that what you're trying to do can be achieved by verifying the values on the left and right sides to which you are searching, something like this:

function mySearch(array $array, $start, $end, $value) {
    if ($end < $start){ 
        return false;
    }

    $index = floor(($end + $start)/2);
    $indexValue = $array[$index];

    // edge case when the value already exists on the array
    if ($indexValue == $value) { 
        return $index;
    }

    // test the value on the left side when value for the current index is greater
    // than the value being searched
    $previousIndex = $index - 1;
    if (
        $value < $indexValue 
        && (
            $previousIndex < 0 // edge case when there's no more values on the left side
            || $array[$previousIndex] <= $value
        )
    ) {
        return $index;
    }

    // test the value on the right side when value for the current index is greater
    // than the value being searched
    if ($value > $indexValue) {
        $nextIndex = $index + 1;
        if ($nextIndex > $end || $array[$nextIndex] >= $value) {
            return $nextIndex;
        } 

        // edge case when the value would be appended to the array
        if ($nextIndex + 1 > $end) {
            return $nextIndex + 1;
        }
    }

    // normal binary search logic
    if ($indexValue < $value) {
        return mySearch($array, $index, $end, $value);
    } else {
        return mySearch($array, $start, $index, $value);
    }
}

I've written this method to verify it works:

$testValues = range(0, 15);
$vals  = [1,2,4,6,7,9,13];

foreach ($testValues as $value) {
    if (in_array($value, $vals)) {
        continue;
    }

    $index = mySearch($vals, 0, count($vals) - 1, $value);
    echo "For value: {$value} index: {$index} Array: " . printArray($vals, $index, $value);
}


function printArray(array $values, int $index, int $value) : string {
    $toPrint = [];
    $isAdded = false;

    foreach ($values as $i => $val) {
        if (!$isAdded && $i >= $index) {
            $toPrint[] = "({$value})";
            $isAdded  = true;
        }

        $toPrint[] = $val;
    }

    if (!$isAdded) {
        $toPrint[] = "({$value})";
    }

    return "[" . implode(", ", $toPrint) . "]\n";
}

And the output is:

For value: 0 index: 0 Array: [(0), 1, 2, 4, 6, 7, 9, 13]
For value: 3 index: 2 Array: [1, 2, (3), 4, 6, 7, 9, 13]
For value: 5 index: 3 Array: [1, 2, 4, (5), 6, 7, 9, 13]
For value: 8 index: 5 Array: [1, 2, 4, 6, 7, (8), 9, 13]
For value: 10 index: 6 Array: [1, 2, 4, 6, 7, 9, (10), 13]
For value: 11 index: 6 Array: [1, 2, 4, 6, 7, 9, (11), 13]
For value: 12 index: 6 Array: [1, 2, 4, 6, 7, 9, (12), 13]
For value: 14 index: 7 Array: [1, 2, 4, 6, 7, 9, 13, (14)]
For value: 15 index: 7 Array: [1, 2, 4, 6, 7, 9, 13, (15)]
Claudio
  • 4,302
  • 1
  • 20
  • 30