]>
git.proxmox.com Git - libgit2.git/blob - src/tsort.c
2 * Copyright (C) the libgit2 contributors. All rights reserved.
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
11 * An array-of-pointers implementation of Python's Timsort
12 * Based on code by Christopher Swenson under the MIT license
14 * Copyright (c) 2010 Christopher Swenson
15 * Copyright (c) 2011 Vicent Marti
19 # define MAX(x,y) (((x) > (y) ? (x) : (y)))
23 # define MIN(x,y) (((x) < (y) ? (x) : (y)))
27 void **dst
, const void *x
, size_t size
, git__sort_r_cmp cmp
, void *payload
)
37 /* check for beginning conditions */
38 if (cmp(x
, lx
, payload
) < 0)
41 else if (cmp(x
, lx
, payload
) == 0) {
43 while (cmp(x
, dst
[i
], payload
) == 0)
48 /* guaranteed not to be >= rx */
51 const int val
= cmp(x
, cx
, payload
);
53 if (c
- l
<= 1) return c
;
56 if (r
- c
<= 1) return c
+ 1;
62 } while (cmp(x
, cx
, payload
) == 0);
65 c
= l
+ ((r
- l
) >> 1);
70 /* Binary insertion sort, but knowing that the first "start" entries are sorted. Used in timsort. */
72 void **dst
, size_t start
, size_t size
, git__sort_r_cmp cmp
, void *payload
)
78 for (i
= start
; i
< size
; i
++) {
80 /* If this entry is already correct, just move along */
81 if (cmp(dst
[i
- 1], dst
[i
], payload
) <= 0)
84 /* Else we need to find the right place, shift everything over, and squeeze in */
86 location
= binsearch(dst
, x
, i
, cmp
, payload
);
87 for (j
= (int)i
- 1; j
>= location
; j
--) {
95 /* timsort implementation, based on timsort.txt */
108 static void reverse_elements(void **dst
, ssize_t start
, ssize_t end
)
110 while (start
< end
) {
111 void *tmp
= dst
[start
];
112 dst
[start
] = dst
[end
];
120 static ssize_t
count_run(
121 void **dst
, ssize_t start
, ssize_t size
, struct tsort_store
*store
)
123 ssize_t curr
= start
+ 2;
125 if (size
- start
== 1)
128 if (start
>= size
- 2) {
129 if (store
->cmp(dst
[size
- 2], dst
[size
- 1], store
->payload
) > 0) {
130 void *tmp
= dst
[size
- 1];
131 dst
[size
- 1] = dst
[size
- 2];
138 if (store
->cmp(dst
[start
], dst
[start
+ 1], store
->payload
) <= 0) {
139 while (curr
< size
- 1 &&
140 store
->cmp(dst
[curr
- 1], dst
[curr
], store
->payload
) <= 0)
145 while (curr
< size
- 1 &&
146 store
->cmp(dst
[curr
- 1], dst
[curr
], store
->payload
) > 0)
149 /* reverse in-place */
150 reverse_elements(dst
, start
, curr
- 1);
155 static size_t compute_minrun(size_t n
)
165 static int check_invariant(struct tsort_run
*stack
, ssize_t stack_curr
)
170 else if (stack_curr
== 2) {
171 const ssize_t A
= stack
[stack_curr
- 2].length
;
172 const ssize_t B
= stack
[stack_curr
- 1].length
;
175 const ssize_t A
= stack
[stack_curr
- 3].length
;
176 const ssize_t B
= stack
[stack_curr
- 2].length
;
177 const ssize_t C
= stack
[stack_curr
- 1].length
;
178 return !((A
<= B
+ C
) || (B
<= C
));
182 static int resize(struct tsort_store
*store
, size_t new_size
)
184 if (store
->alloc
< new_size
) {
187 tempstore
= git__reallocarray(store
->storage
, new_size
, sizeof(void *));
190 * Do not propagate on OOM; this will abort the sort and
191 * leave the array unsorted, but no error code will be
194 if (tempstore
== NULL
)
197 store
->storage
= tempstore
;
198 store
->alloc
= new_size
;
204 static void merge(void **dst
, const struct tsort_run
*stack
, ssize_t stack_curr
, struct tsort_store
*store
)
206 const ssize_t A
= stack
[stack_curr
- 2].length
;
207 const ssize_t B
= stack
[stack_curr
- 1].length
;
208 const ssize_t curr
= stack
[stack_curr
- 2].start
;
213 if (resize(store
, MIN(A
, B
)) < 0)
216 storage
= store
->storage
;
220 memcpy(storage
, &dst
[curr
], A
* sizeof(void *));
224 for (k
= curr
; k
< curr
+ A
+ B
; k
++) {
225 if ((i
< A
) && (j
< curr
+ A
+ B
)) {
226 if (store
->cmp(storage
[i
], dst
[j
], store
->payload
) <= 0)
227 dst
[k
] = storage
[i
++];
231 dst
[k
] = storage
[i
++];
236 memcpy(storage
, &dst
[curr
+ A
], B
* sizeof(void *));
240 for (k
= curr
+ A
+ B
- 1; k
>= curr
; k
--) {
241 if ((i
>= 0) && (j
>= curr
)) {
242 if (store
->cmp(dst
[j
], storage
[i
], store
->payload
) > 0)
245 dst
[k
] = storage
[i
--];
247 dst
[k
] = storage
[i
--];
254 static ssize_t
collapse(void **dst
, struct tsort_run
*stack
, ssize_t stack_curr
, struct tsort_store
*store
, ssize_t size
)
259 /* if the stack only has one thing on it, we are done with the collapse */
263 /* if this is the last merge, just do it */
264 if ((stack_curr
== 2) && (stack
[0].length
+ stack
[1].length
== size
)) {
265 merge(dst
, stack
, stack_curr
, store
);
266 stack
[0].length
+= stack
[1].length
;
271 /* check if the invariant is off for a stack of 2 elements */
272 else if ((stack_curr
== 2) && (stack
[0].length
<= stack
[1].length
)) {
273 merge(dst
, stack
, stack_curr
, store
);
274 stack
[0].length
+= stack
[1].length
;
278 else if (stack_curr
== 2)
281 A
= stack
[stack_curr
- 3].length
;
282 B
= stack
[stack_curr
- 2].length
;
283 C
= stack
[stack_curr
- 1].length
;
285 /* check first invariant */
288 merge(dst
, stack
, stack_curr
- 1, store
);
289 stack
[stack_curr
- 3].length
+= stack
[stack_curr
- 2].length
;
290 stack
[stack_curr
- 2] = stack
[stack_curr
- 1];
293 merge(dst
, stack
, stack_curr
, store
);
294 stack
[stack_curr
- 2].length
+= stack
[stack_curr
- 1].length
;
298 merge(dst
, stack
, stack_curr
, store
);
299 stack
[stack_curr
- 2].length
+= stack
[stack_curr
- 1].length
;
308 #define PUSH_NEXT() do {\
309 len = count_run(dst, curr, size, store);\
311 if (run > (ssize_t)size - curr) run = size - curr;\
313 bisort(&dst[curr], len, run, cmp, payload);\
316 run_stack[stack_curr].start = curr;\
317 run_stack[stack_curr++].length = len;\
319 if (curr == (ssize_t)size) {\
321 while (stack_curr > 1) { \
322 merge(dst, run_stack, stack_curr, store); \
323 run_stack[stack_curr - 2].length += run_stack[stack_curr - 1].length; \
326 if (store->storage != NULL) {\
327 git__free(store->storage);\
328 store->storage = NULL;\
336 void **dst
, size_t size
, git__sort_r_cmp cmp
, void *payload
)
338 struct tsort_store _store
, *store
= &_store
;
339 struct tsort_run run_stack
[128];
341 ssize_t stack_curr
= 0;
347 bisort(dst
, 1, size
, cmp
, payload
);
351 /* compute the minimum run length */
352 minrun
= (ssize_t
)compute_minrun(size
);
354 /* temporary storage for merges */
356 store
->storage
= NULL
;
358 store
->payload
= payload
;
365 if (!check_invariant(run_stack
, stack_curr
)) {
366 stack_curr
= collapse(dst
, run_stack
, stack_curr
, store
, size
);
374 static int tsort_r_cmp(const void *a
, const void *b
, void *payload
)
376 return ((git__tsort_cmp
)payload
)(a
, b
);
379 void git__tsort(void **dst
, size_t size
, git__tsort_cmp cmp
)
381 git__tsort_r(dst
, size
, tsort_r_cmp
, cmp
);