BinarySearch::find()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 24
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 10
c 1
b 0
f 0
nc 4
nop 4
dl 0
loc 24
ccs 10
cts 10
cp 1
crap 4
rs 9.9332
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yoanm\CommonDSA\Algorithm;
6
7
class BinarySearch
8
{
9
    /**
10
     * Find an index where value is equal to $target
11
     *
12
     *
13
     * ### Time/Space complexity
14
     * With:
15
     * - ๐‘› equals the provided list length.
16
     *
17
     * TC: ๐‘‚โŸฎใ’ ๐‘›โŸฏ - classic for binary search
18
     *
19
     * SC: ๐‘‚โŸฎ๐ŸทโŸฏ - Constant extra space
20
     *
21
     *
22
     * @template TValue of (int|float)
23
     *
24
     *
25
     * @param array<int|float> $list โš  Must be a 0 indexed list, 0 to n consecutive indexes, sorted in non-decreasing
26
     *                               order (minโ†’max)!<br>
27
     *                               May contain duplicates.
28
     * @phpstan-param list<TValue> $list
29
     * @phpstan-param TValue $target
30
     * @param int $headIdx Lookup start index.<br>
31
     *                     Default to 0 (head index).
32
     * @param int|null $tailIdx Lookup end index.<br>
33
     *                          Default to tail index
34
     *
35
     * @return int Index where $target has been found, -1 if not found
36
     */
37 5
    public static function find(
38
        array $list,
39
        int|float $target,
40
        int $headIdx = 0,
41
        int|null $tailIdx = null,
42
    ): int {
43 5
        $tailIdx ??= count($list) - 1;
44
45 5
        while ($headIdx <= $tailIdx) {
46
            // Prevent ($lowIdx + $highIdx) overflow !
47 4
            $midIdx = $headIdx + (($tailIdx - $headIdx) >> 1);
48
49 4
            if ($list[$midIdx] < $target) {
50
                // Current value is too small ? => Ignore left side and current
51 4
                $headIdx = $midIdx + 1;
52 4
            } elseif ($list[$midIdx] > $target) {
53
                // Current value is greater ? => Ignore right side and current
54 3
                $tailIdx = $midIdx - 1;
55
            } else {
56 2
                return $midIdx;
57
            }
58
        }
59
60 3
        return -1;
61
    }
62
63
    /**
64
     * Finds the index of left-most element which is greater than or equal to $target (=not strictly lower than $target)
65
     *
66
     *
67
     * ### Time/Space complexity
68
     * With:
69
     * - ๐‘› equals the provided list length.
70
     *
71
     * TC: ๐‘‚โŸฎใ’ ๐‘›โŸฏ - classic for binary search
72
     *
73
     * SC: ๐‘‚โŸฎ๐ŸทโŸฏ - Constant extra space
74
     *
75
     *
76
     * @template TValue of (int|float)
77
     *
78
     *
79
     * @param array<int|float> $list โš  Must be a 0 indexed list, 0 to n consecutive indexes, sorted in non-decreasing
80
     *                               order (minโ†’max)!<br>
81
     *                               May contain duplicates.
82
     * @phpstan-param list<TValue> $list
83
     * @phpstan-param TValue $target
84
     * @param int $lowIdx Lookup start index.<br>
85
     *                    Default to 0 (head index).
86
     * @param int|null $highIdx Lookup end index.<br>
87
     *                          Default to $list length (=tail index + 1)
88
     *
89
     * @return int Index where $target can be inserted in $list while keeping it sorted.<br>
90
     *             โš  Might be beyond $list current indexes if $target is greater than tail value !
91
     */
92 16
    public static function lowerBound(
93
        array $list,
94
        int|float $target,
95
        int $lowIdx = 0,
96
        int|null $highIdx = null,
97
    ): int {
98 16
        $highIdx ??= count($list);
99
100 16
        while ($lowIdx < $highIdx) {
101
            // Prevent ($lowIdx + $highIdx) overflow !
102 15
            $midIdx = $lowIdx + (($highIdx - $lowIdx) >> 1);
103
104 15
            if ($list[$midIdx] < $target) {
105
                // Current value is too small ? => Ignore left side and current
106 10
                $lowIdx = $midIdx + 1;
107
            } else {
108
                // Current value is either equal or greater ?
109
                // => Ignore right side BUT keep current (might be the right index)
110 13
                $highIdx = $midIdx;
111
            }
112
        }
113
114 16
        return $lowIdx;
115
    }
116
117
    /**
118
     * Finds the index of left-most element which is strictly greater than $target
119
     *
120
     *
121
     * ### Time/Space complexity
122
     * With:
123
     * - ๐‘› equals the provided list length.
124
     *
125
     * TC: ๐‘‚โŸฎใ’ ๐‘›โŸฏ - classic for binary search
126
     *
127
     * SC: ๐‘‚โŸฎ๐ŸทโŸฏ - Constant extra space
128
     *
129
     *
130
     * @template TValue of (int|float)
131
     *
132
     *
133
     * @param array<int|float> $list โš  Must be a 0 indexed list, 0 to n consecutive indexes, sorted in non-decreasing
134
     *                               order (minโ†’max)!<br>
135
     *                               May contain duplicates.
136
     * @phpstan-param list<TValue> $list
137
     * @phpstan-param TValue $target
138
     * @param int $lowIdx Lookup start index.<br>
139
     *                    Default to 0 (head index).
140
     * @param int|null $highIdx Lookup end index.<br>
141
     *                          Default to $list length (=tail index + 1)
142
     *
143
     * @return int Index where $target can be inserted in $list while keeping it sorted.<br>
144
     *             โš  Might be beyond $list current indexes if $target is greater than or equal to tail value !
145
     */
146 16
    public static function upperBound(
147
        array $list,
148
        int|float $target,
149
        int $lowIdx = 0,
150
        int|null $highIdx = null,
151
    ): int {
152 16
        $highIdx ??= count($list);
153
154 16
        while ($lowIdx < $highIdx) {
155
            // Prevent ($lowIdx + $highIdx) overflow !
156 15
            $middleIdx = $lowIdx + (($highIdx - $lowIdx) >> 1);
157
158 15
            if ($list[$middleIdx] <= $target) {
159
                // Current value is either equal or too small ? => Ignore left side and current
160 13
                $lowIdx = $middleIdx + 1;
161
            } else {
162
                // Current value is greater ? => Ignore right side BUT keep current (might be the right index)
163 10
                $highIdx = $middleIdx;
164
            }
165
        }
166
167 16
        return $lowIdx;
168
    }
169
}
170