Skip to content

Commit 0ec958a

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 <explorer09@gmail.com>
1 parent c85222d commit 0ec958a

7 files changed

Lines changed: 146 additions & 76 deletions

File tree

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: 136 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ in the source distribution for its full text.
1616
#include "XUtils.h"
1717

1818

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

@@ -93,91 +100,159 @@ void Vector_prune(Vector* this) {
93100
memset(this->array, '\0', this->arraySize * sizeof(Object*));
94101
}
95102

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;
103+
ATTR_NONNULL
104+
static void swapByte(char* p1, char* p2) {
105+
char temp = *p1;
106+
*p1 = *p2;
107+
*p2 = temp;
104108
}
105109

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++;
110+
ATTR_NONNULL
111+
static void rotate(void* buffer, size_t leftSize, size_t rightSize) {
112+
if (rightSize == 0)
113+
return;
114+
115+
char* p1 = buffer;
116+
char* p2 = p1 + leftSize;
117+
char* mid = p2;
118+
const char* const end = mid + rightSize;
119+
120+
while (true) {
121+
assert(p1 <= mid);
122+
assert(mid <= p2);
123+
assert(p2 <= end);
124+
if (p2 >= end) {
125+
assert(mid < end);
126+
p2 = mid;
115127
}
128+
129+
if (p1 >= p2)
130+
break;
131+
132+
if (p1 >= mid)
133+
mid = p2;
134+
135+
swapByte(p1, p2);
136+
p1 += 1;
137+
p2 += 1;
116138
}
117-
swap(array, storeIndex, right);
118-
return storeIndex;
119139
}
120140

121-
static void quickSort(Object** array, int left, int right, Object_Compare compare) {
122-
if (left >= right)
141+
ATTR_NONNULL_N(1, 5)
142+
static void mergeRuns(void* array, size_t leftLen, size_t rightLen, size_t size, CompareWithContext compare, void* context) {
143+
assert(size > 0);
144+
if (leftLen == 0 || rightLen == 0 || size == 0)
123145
return;
124146

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-
}
147+
char* p1 = array;
148+
char* p2 = p1 + leftLen * size;
149+
char* mid = p2;
150+
const char* const end = mid + rightLen * size;
130151

131-
// If I were to use only one sorting algorithm for both cases, it would probably be this one:
132-
/*
152+
for (size_t limit = (leftLen + rightLen) / 2; limit > 0; limit--) {
153+
assert(p1 <= mid);
154+
assert(mid <= p2);
155+
assert(p2 <= end);
133156

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-
}
157+
if (p1 >= mid || p2 >= end)
158+
break;
159+
160+
if (compare(p1, p2, context) <= 0) {
161+
p1 += size;
162+
} else {
163+
p2 += size;
148164
}
149165
}
166+
167+
rotate(p1, (size_t)(mid - p1), (size_t)(p2 - mid));
168+
169+
leftLen = (size_t)(p1 - (char*)array) / size;
170+
rightLen = (size_t)(p2 - mid) / size;
171+
mergeRuns(array, leftLen, rightLen, size, compare, context);
172+
173+
leftLen = (size_t)(mid - p1) / size;
174+
rightLen = (size_t)(end - p2) / size;
175+
mergeRuns(p1 + (p2 - mid), leftLen, rightLen, size, compare, context);
150176
}
151177

152-
*/
178+
ATTR_NONNULL_N(1, 5)
179+
static size_t mergeSortSubarray(void* array, size_t unsortedLen, size_t limit, size_t size, CompareWithContext compare, void* context) {
180+
assert(size > 0);
181+
if (size == 0)
182+
return 0;
183+
184+
// The initial level of this function call must set "limit" to 0. Subsequent
185+
// levels of recursion will have "limit" no less than the previous level.
186+
187+
// A run is a sorted subarray. Each recursive call of this function keeps
188+
// the lengths of two runs. At most O(log(n)) lengths of runs will be
189+
// tracked on the call stack.
190+
size_t runLen[3] = {0};
191+
while (unsortedLen > 0) {
192+
size_t totalLen = unsortedLen;
193+
while (true) {
194+
if (--unsortedLen < limit)
195+
return 0;
196+
197+
if (unsortedLen == 0)
198+
break;
153199

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)
200+
const char* p2 = (const char*)array + unsortedLen * size;
201+
if (compare(p2 - 1 * size, p2, context) > 0) {
161202
break;
203+
}
204+
}
205+
runLen[1] = totalLen - unsortedLen;
206+
207+
bool reachesLimit = false;
208+
209+
assert(runLen[2] > 0 || runLen[0] == 0);
210+
if (runLen[2] > 0) {
211+
size_t nextLimit = limit;
212+
if (unsortedLen > runLen[2] + limit) {
213+
nextLimit = unsortedLen - runLen[2];
214+
} else {
215+
reachesLimit = true;
216+
}
217+
218+
runLen[0] = mergeSortSubarray(array, unsortedLen, nextLimit, size, compare, context);
219+
unsortedLen -= runLen[0];
220+
221+
char* p1 = (char*)array + unsortedLen * size;
222+
mergeRuns(p1, runLen[0], runLen[1], size, compare, context);
223+
runLen[1] += runLen[0];
224+
runLen[0] = 0;
162225

163-
array[j + 1] = array[j];
164-
j--;
226+
mergeRuns(p1, runLen[1], runLen[2], size, compare, context);
227+
}
228+
runLen[2] += runLen[1];
229+
runLen[1] = 0;
230+
231+
if (reachesLimit) {
232+
break;
165233
}
166-
array[j + 1] = t;
167234
}
235+
return runLen[2];
168236
}
169237

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));
238+
ATTR_NONNULL
239+
static int Vector_sortCompare(const void* p1, const void* p2, void* context) {
240+
VectorSortContext* vc = (VectorSortContext*) context;
241+
242+
return vc->compare(*(const void* const*)p1, *(const void* const*)p2);
175243
}
176244

177-
void Vector_insertionSort(Vector* this) {
178-
assert(this->type->compare);
245+
ATTR_NONNULL_N(1)
246+
void Vector_sort(Vector* this, Object_Compare compare, void* context) {
247+
VectorSortContext vc = {
248+
.compare = compare ? compare : this->type->compare,
249+
.compareContext = context,
250+
};
251+
assert(vc.compare);
179252
assert(Vector_isConsistent(this));
180-
insertionSort(this->array, 0, this->items - 1, this->type->compare);
253+
254+
(void)mergeSortSubarray(this->array, this->items, 0, sizeof(*this->array), Vector_sortCompare, &vc);
255+
181256
assert(Vector_isConsistent(this));
182257
}
183258

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)