Skip to content

Commit aee16de

Browse files
authored
Merge pull request #1 from sahildando/codex/optimize-code-for-performance-and-documentation
Optimize SelectK with introspective quickselect and robust partitioning
2 parents 5ba447e + 32cb078 commit aee16de

3 files changed

Lines changed: 91 additions & 20 deletions

File tree

search/doc.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
1-
// Package search is a subpackage dedicated to all searching algorithms related to slices/arrays.
1+
// Package search contains classic and practical search algorithms for arrays and slices.
2+
//
3+
// The implementations in this package are designed to be easy to read first,
4+
// while still reflecting good algorithmic choices (time/space complexity,
5+
// boundary validation, and benchmark coverage).
26
package search

search/selectk.go

Lines changed: 82 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,96 @@
11
package search
22

3+
import (
4+
"math/bits"
5+
"sort"
6+
)
7+
8+
// SelectK returns the k-th largest element in array.
9+
//
10+
// Time complexity is expected O(n) thanks to quickselect partitioning.
11+
// A depth limit is applied and falls back to sorting the narrowed range,
12+
// which guarantees O(n log n) worst-case behavior.
13+
//
14+
// The function mutates the input slice in-place.
315
func SelectK(array []int, k int) (int, error) {
4-
if k > len(array) {
16+
n := len(array)
17+
if n == 0 || k < 1 || k > n {
518
return -1, ErrNotFound
619
}
7-
return selectK(array, 0, len(array), len(array)-k), nil
20+
21+
// k-th largest -> index in zero-based ascending order.
22+
idx := n - k
23+
return selectK(array, idx), nil
824
}
925

10-
// search the element which index is idx
11-
func selectK(array []int, l, r, idx int) int {
12-
index := partition(array, l, r)
13-
if index == idx {
14-
return array[index]
26+
// selectK returns the element that would appear at idx in sorted order.
27+
func selectK(array []int, idx int) int {
28+
l, r := 0, len(array)
29+
depthLimit := 2 * bits.Len(uint(len(array)))
30+
31+
for r-l > 1 {
32+
if depthLimit == 0 {
33+
sort.Ints(array[l:r])
34+
return array[idx]
35+
}
36+
depthLimit--
37+
38+
leftPivot, rightPivot := partition3(array, l, r)
39+
switch {
40+
case idx < leftPivot:
41+
r = leftPivot
42+
case idx >= rightPivot:
43+
l = rightPivot
44+
default:
45+
return array[idx]
46+
}
1547
}
16-
if index < idx {
17-
return selectK(array, index+1, r, idx)
48+
49+
return array[l]
50+
}
51+
52+
// partition3 applies a Dutch National Flag partition around a pivot and
53+
// returns [leftPivot, rightPivot), the range that equals the pivot.
54+
func partition3(array []int, l, r int) (leftPivot, rightPivot int) {
55+
pivotIdx := medianOfThreeIndex(array, l, l+(r-l)/2, r-1)
56+
pivot := array[pivotIdx]
57+
array[l], array[pivotIdx] = array[pivotIdx], array[l]
58+
59+
lt, i, gt := l, l+1, r
60+
for i < gt {
61+
switch {
62+
case array[i] < pivot:
63+
lt++
64+
array[i], array[lt] = array[lt], array[i]
65+
i++
66+
case array[i] > pivot:
67+
gt--
68+
array[i], array[gt] = array[gt], array[i]
69+
default:
70+
i++
71+
}
1872
}
19-
return selectK(array, l, index, idx)
73+
74+
array[l], array[lt] = array[lt], array[l]
75+
return lt, gt
2076
}
2177

22-
func partition(array []int, l, r int) int {
23-
elem, j := array[l], l+1
24-
for i := l + 1; i < r; i++ {
25-
if array[i] <= elem {
26-
array[i], array[j] = array[j], array[i]
27-
j++
78+
func medianOfThreeIndex(array []int, a, b, c int) int {
79+
if array[a] < array[b] {
80+
if array[b] < array[c] {
81+
return b
2882
}
83+
if array[a] < array[c] {
84+
return c
85+
}
86+
return a
87+
}
88+
89+
if array[a] < array[c] {
90+
return a
91+
}
92+
if array[b] < array[c] {
93+
return c
2994
}
30-
array[l], array[j-1] = array[j-1], array[l]
31-
return j - 1
95+
return b
3296
}

search/selectk_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ func TestSelectK(t *testing.T) {
1515
{[]int{1, 2, 3, 4, 5}, 1, 5, nil, "sorted data"},
1616
{[]int{5, 4, 3, 2, 1}, 2, 4, nil, "reversed data"},
1717
{[]int{3, 1, 2, 5, 4}, 3, 3, nil, "random data"},
18-
{[]int{3, 2, 1, 5, 4}, 10, -1, ErrNotFound, " absent data"},
18+
{[]int{3, 2, 1, 5, 4}, 10, -1, ErrNotFound, "absent data"},
19+
{[]int{3, 2, 1, 5, 4}, 0, -1, ErrNotFound, "invalid zero k"},
20+
{[]int{}, 1, -1, ErrNotFound, "empty data"},
21+
{[]int{5, 5, 5, 5, 5}, 3, 5, nil, "duplicate data"},
1922
}
2023
for _, tc := range tests {
2124
t.Run(tc.name, func(t *testing.T) {

0 commit comments

Comments
 (0)