Skip to content

Commit aba90b7

Browse files
committed
New Vector_sort function, replacing insertion and quick sort
Introduce the new Vector_sort() function and obsolete the old Vector_quickSortCustomCompare() and Vector_insertionSort() APIs. This new sort function is a natural, in-place merge sort. I.e. it takes advantage of partially sorted data, and it's stable. Space complexity: O(log(n)) worst case Time complexity: O(n) best case, O(n*log(n)*log(n)) worst case Signed-off-by: Kang-Che Sung <[email protected]>
1 parent c85222d commit aba90b7

File tree

7 files changed

+157
-77
lines changed

7 files changed

+157
-77
lines changed

Action.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ static Htop_Reaction actionFilterByUser(State* st) {
542542
Panel_setHeader(usersPanel, "Show processes of:");
543543
Machine* host = st->host;
544544
UsersTable_foreach(host->usersTable, addUserToVector, usersPanel);
545-
Vector_insertionSort(usersPanel->items);
545+
Vector_sort(usersPanel->items, NULL, NULL);
546546
ListItem* allUsers = ListItem_new("All users", -1);
547547
Panel_insert(usersPanel, 0, (Object*) allUsers);
548548
const ListItem* picked = (ListItem*) Action_pickFromVector(st, usersPanel, 19, false);

EnvScreen.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ static void EnvScreen_scan(InfoScreen* this) {
5252
InfoScreen_addLine(this, "Could not read process environment.");
5353
}
5454

55-
Vector_insertionSort(this->lines);
56-
Vector_insertionSort(panel->items);
55+
Vector_sort(this->lines, NULL, NULL);
56+
Vector_sort(panel->items, NULL, NULL);
5757
Panel_setSelected(panel, idx);
5858
}
5959

OpenFilesScreen.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,8 @@ static void OpenFilesScreen_scan(InfoScreen* super) {
300300
OpenFiles_Data_clear(&pdata->data);
301301
}
302302
free(pdata);
303-
Vector_insertionSort(super->lines);
304-
Vector_insertionSort(panel->items);
303+
Vector_sort(super->lines, NULL, NULL);
304+
Vector_sort(panel->items, NULL, NULL);
305305
Panel_setSelected(panel, idx);
306306
}
307307

ProcessLocksScreen.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ static void ProcessLocksScreen_scan(InfoScreen* this) {
9191
}
9292
}
9393
free(pdata);
94-
Vector_insertionSort(this->lines);
95-
Vector_insertionSort(panel->items);
94+
Vector_sort(this->lines, NULL, NULL);
95+
Vector_sort(panel->items, NULL, NULL);
9696
Panel_setSelected(panel, idx);
9797
}
9898

Table.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ static void Table_buildTree(Table* this) {
168168
}
169169

170170
// Sort by known parent (roots first), then row ID
171-
Vector_quickSortCustomCompare(this->rows, compareRowByKnownParentThenNatural);
171+
Vector_sort(this->rows, compareRowByKnownParentThenNatural, NULL);
172172

173173
// Find all processes whose parent is not visible
174174
for (int i = 0; i < vsize; i++) {
@@ -199,7 +199,7 @@ void Table_updateDisplayList(Table* this) {
199199
Table_buildTree(this);
200200
} else {
201201
if (this->needsSort)
202-
Vector_insertionSort(this->rows);
202+
Vector_sort(this->rows, NULL, NULL);
203203
Vector_prune(this->displayList);
204204
int size = Vector_size(this->rows);
205205
for (int i = 0; i < size; i++)

Vector.c

Lines changed: 147 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,20 @@ in the source distribution for its full text.
1010
#include "Vector.h"
1111

1212
#include <assert.h>
13+
#include <stdint.h>
1314
#include <stdlib.h>
1415
#include <string.h>
1516

1617
#include "XUtils.h"
1718

1819

20+
typedef int(*CompareWithContext)(const void*, const void*, void*);
21+
22+
typedef struct VectorSortContext_ {
23+
Object_Compare compare;
24+
void* compareContext;
25+
} VectorSortContext;
26+
1927
Vector* Vector_new(const ObjectClass* type, bool owner, int size) {
2028
Vector* this;
2129

@@ -93,91 +101,168 @@ void Vector_prune(Vector* this) {
93101
memset(this->array, '\0', this->arraySize * sizeof(Object*));
94102
}
95103

96-
//static int comparisons = 0;
97-
98-
static void swap(Object** array, int indexA, int indexB) {
99-
assert(indexA >= 0);
100-
assert(indexB >= 0);
101-
Object* tmp = array[indexA];
102-
array[indexA] = array[indexB];
103-
array[indexB] = tmp;
104+
ATTR_NONNULL
105+
static void swapByte(char* p1, char* p2) {
106+
char temp = *p1;
107+
*p1 = *p2;
108+
*p2 = temp;
104109
}
105110

106-
static int partition(Object** array, int left, int right, int pivotIndex, Object_Compare compare) {
107-
const Object* pivotValue = array[pivotIndex];
108-
swap(array, pivotIndex, right);
109-
int storeIndex = left;
110-
for (int i = left; i < right; i++) {
111-
//comparisons++;
112-
if (compare(array[i], pivotValue) <= 0) {
113-
swap(array, i, storeIndex);
114-
storeIndex++;
111+
ATTR_NONNULL
112+
static void rotate(void* buffer, size_t leftSize, size_t rightSize) {
113+
if (rightSize == 0)
114+
return;
115+
116+
char* p1 = buffer;
117+
char* p2 = p1 + leftSize;
118+
char* mid = p2;
119+
const char* const end = mid + rightSize;
120+
121+
while (true) {
122+
// Ensure there is no arithmetic overflow on input.
123+
assert(p1 <= mid);
124+
assert(mid <= p2);
125+
assert(p2 <= end);
126+
127+
if (p2 >= end) {
128+
assert(mid < end);
129+
p2 = mid;
115130
}
131+
132+
if (p1 >= p2)
133+
break;
134+
135+
if (p1 >= mid)
136+
mid = p2;
137+
138+
swapByte(p1, p2);
139+
p1 += 1;
140+
p2 += 1;
116141
}
117-
swap(array, storeIndex, right);
118-
return storeIndex;
119142
}
120143

121-
static void quickSort(Object** array, int left, int right, Object_Compare compare) {
122-
if (left >= right)
144+
ATTR_NONNULL_N(1, 5)
145+
static void mergeRuns(void* array, size_t leftLen, size_t rightLen, size_t size, CompareWithContext compare, void* context) {
146+
assert(size > 0);
147+
if (leftLen == 0 || rightLen == 0 || size == 0)
123148
return;
124149

125-
int pivotIndex = left + (right - left) / 2;
126-
int pivotNewIndex = partition(array, left, right, pivotIndex, compare);
127-
quickSort(array, left, pivotNewIndex - 1, compare);
128-
quickSort(array, pivotNewIndex + 1, right, compare);
129-
}
150+
assert(leftLen <= SIZE_MAX / size);
151+
assert(rightLen <= SIZE_MAX / size);
130152

131-
// If I were to use only one sorting algorithm for both cases, it would probably be this one:
132-
/*
153+
char* p1 = array;
154+
char* p2 = p1 + leftLen * size;
155+
char* mid = p2;
156+
const char* const end = mid + rightLen * size;
133157

134-
static void combSort(Object** array, int left, int right, Object_Compare compare) {
135-
int gap = right - left;
136-
bool swapped = true;
137-
while ((gap > 1) || swapped) {
138-
if (gap > 1) {
139-
gap = (int)((double)gap / 1.247330950103979);
140-
}
141-
swapped = false;
142-
for (int i = left; gap + i <= right; i++) {
143-
comparisons++;
144-
if (compare(array[i], array[i+gap]) > 0) {
145-
swap(array, i, i+gap);
146-
swapped = true;
147-
}
158+
for (size_t limit = (leftLen + rightLen) / 2; limit > 0; limit--) {
159+
// Ensure there is no arithmetic overflow on input.
160+
assert(p1 <= mid);
161+
assert(mid <= p2);
162+
assert(p2 <= end);
163+
164+
if (p1 >= mid || p2 >= end)
165+
break;
166+
167+
if (compare(p1, p2, context) <= 0) {
168+
p1 += size;
169+
} else {
170+
p2 += size;
148171
}
149172
}
150-
}
151173

152-
*/
174+
rotate(p1, (size_t)(mid - p1), (size_t)(p2 - mid));
175+
176+
leftLen = (size_t)(p1 - (char*)array) / size;
177+
rightLen = (size_t)(p2 - mid) / size;
178+
mergeRuns(array, leftLen, rightLen, size, compare, context);
153179

154-
static void insertionSort(Object** array, int left, int right, Object_Compare compare) {
155-
for (int i = left + 1; i <= right; i++) {
156-
Object* t = array[i];
157-
int j = i - 1;
158-
while (j >= left) {
159-
//comparisons++;
160-
if (compare(array[j], t) <= 0)
180+
leftLen = (size_t)(mid - p1) / size;
181+
rightLen = (size_t)(end - p2) / size;
182+
mergeRuns(p1 + (p2 - mid), leftLen, rightLen, size, compare, context);
183+
}
184+
185+
ATTR_NONNULL_N(1, 5)
186+
static size_t mergeSortSubarray(void* array, size_t unsortedLen, size_t limit, size_t size, CompareWithContext compare, void* context) {
187+
assert(size > 0);
188+
if (size == 0)
189+
return 0;
190+
191+
// The initial level of this function call must set "limit" to 0. Subsequent
192+
// levels of recursion will have "limit" no less than the previous level.
193+
194+
// A run is a sorted subarray. Each recursive call of this function keeps
195+
// the lengths of two runs. At most O(log(n)) lengths of runs will be
196+
// tracked on the call stack.
197+
size_t runLen[3] = {0};
198+
while (unsortedLen > 0) {
199+
size_t totalLen = unsortedLen;
200+
assert(totalLen <= SIZE_MAX / size);
201+
while (true) {
202+
--unsortedLen;
203+
204+
const char* p2 = (const char*)array + unsortedLen * size;
205+
// Ensure there is no arithmetic overflow on input.
206+
assert(p2 > (const char*)array);
207+
208+
if (unsortedLen < limit)
209+
return 0;
210+
211+
if (unsortedLen == 0 || compare(p2 - 1 * size, p2, context) > 0) {
161212
break;
213+
}
214+
}
215+
runLen[1] = totalLen - unsortedLen;
216+
217+
bool reachesLimit = false;
218+
219+
assert(runLen[2] > 0 || runLen[0] == 0);
220+
if (runLen[2] > 0) {
221+
size_t nextLimit = limit;
222+
if (unsortedLen > runLen[2] + limit) {
223+
nextLimit = unsortedLen - runLen[2];
224+
} else {
225+
reachesLimit = true;
226+
}
227+
228+
runLen[0] = mergeSortSubarray(array, unsortedLen, nextLimit, size, compare, context);
229+
unsortedLen -= runLen[0];
230+
231+
char* p1 = (char*)array + unsortedLen * size;
232+
mergeRuns(p1, runLen[0], runLen[1], size, compare, context);
233+
runLen[1] += runLen[0];
234+
runLen[0] = 0;
162235

163-
array[j + 1] = array[j];
164-
j--;
236+
mergeRuns(p1, runLen[1], runLen[2], size, compare, context);
237+
}
238+
runLen[2] += runLen[1];
239+
runLen[1] = 0;
240+
241+
if (reachesLimit) {
242+
break;
165243
}
166-
array[j + 1] = t;
167244
}
245+
return runLen[2];
168246
}
169247

170-
void Vector_quickSortCustomCompare(Vector* this, Object_Compare compare) {
171-
assert(compare);
172-
assert(Vector_isConsistent(this));
173-
quickSort(this->array, 0, this->items - 1, compare);
174-
assert(Vector_isConsistent(this));
248+
ATTR_NONNULL
249+
static int Vector_sortCompare(const void* p1, const void* p2, void* context) {
250+
VectorSortContext* vc = (VectorSortContext*) context;
251+
252+
return vc->compare(*(const void* const*)p1, *(const void* const*)p2);
175253
}
176254

177-
void Vector_insertionSort(Vector* this) {
178-
assert(this->type->compare);
255+
ATTR_NONNULL_N(1)
256+
void Vector_sort(Vector* this, Object_Compare compare, void* context) {
257+
VectorSortContext vc = {
258+
.compare = compare ? compare : this->type->compare,
259+
.compareContext = context,
260+
};
261+
assert(vc.compare);
179262
assert(Vector_isConsistent(this));
180-
insertionSort(this->array, 0, this->items - 1, this->type->compare);
263+
264+
(void)mergeSortSubarray(this->array, this->items, 0, sizeof(*this->array), Vector_sortCompare, &vc);
265+
181266
assert(Vector_isConsistent(this));
182267
}
183268

Vector.h

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,7 @@ void Vector_delete(Vector* this);
3535

3636
void Vector_prune(Vector* this);
3737

38-
void Vector_quickSortCustomCompare(Vector* this, Object_Compare compare);
39-
static inline void Vector_quickSort(Vector* this) {
40-
Vector_quickSortCustomCompare(this, this->type->compare);
41-
}
42-
43-
void Vector_insertionSort(Vector* this);
38+
void Vector_sort(Vector* this, Object_Compare compare, void* context);
4439

4540
void Vector_insert(Vector* this, int idx, void* data_);
4641

0 commit comments

Comments
 (0)