]>
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)))
26 typedef int (*cmp_ptr_t
)(const void *, const void *);
28 static int binsearch(void **dst
, const void *x
, size_t size
, cmp_ptr_t cmp
)
40 /* check for beginning conditions */
44 else if (cmp(x
, lx
) == 0) {
46 while (cmp(x
, dst
[i
]) == 0)
51 /* guaranteed not to be >= rx */
54 const int val
= cmp(x
, cx
);
56 if (c
- l
<= 1) return c
;
59 if (r
- c
<= 1) return c
+ 1;
65 } while (cmp(x
, cx
) == 0);
68 c
= l
+ ((r
- l
) >> 1);
73 /* Binary insertion sort, but knowing that the first "start" entries are sorted. Used in timsort. */
74 static void bisort(void **dst
, size_t start
, size_t size
, cmp_ptr_t cmp
)
80 for (i
= start
; i
< size
; i
++) {
82 /* If this entry is already correct, just move along */
83 if (cmp(dst
[i
- 1], dst
[i
]) <= 0)
86 /* Else we need to find the right place, shift everything over, and squeeze in */
88 location
= binsearch(dst
, x
, i
, cmp
);
89 for (j
= (int)i
- 1; j
>= location
; j
--) {
97 /* timsort implementation, based on timsort.txt */
109 static void reverse_elements(void **dst
, ssize_t start
, ssize_t end
)
111 while (start
< end
) {
112 void *tmp
= dst
[start
];
113 dst
[start
] = dst
[end
];
121 static ssize_t
count_run(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]) > 0) {
130 void *tmp
= dst
[size
- 1];
131 dst
[size
- 1] = dst
[size
- 2];
138 if (store
->cmp(dst
[start
], dst
[start
+ 1]) <= 0) {
139 while (curr
< size
- 1 && store
->cmp(dst
[curr
- 1], dst
[curr
]) <= 0)
144 while (curr
< size
- 1 && store
->cmp(dst
[curr
- 1], dst
[curr
]) > 0)
147 /* reverse in-place */
148 reverse_elements(dst
, start
, curr
- 1);
153 static size_t compute_minrun(size_t n
)
163 static int check_invariant(struct tsort_run
*stack
, ssize_t stack_curr
)
168 else if (stack_curr
== 2) {
169 const ssize_t A
= stack
[stack_curr
- 2].length
;
170 const ssize_t B
= stack
[stack_curr
- 1].length
;
173 const ssize_t A
= stack
[stack_curr
- 3].length
;
174 const ssize_t B
= stack
[stack_curr
- 2].length
;
175 const ssize_t C
= stack
[stack_curr
- 1].length
;
176 return !((A
<= B
+ C
) || (B
<= C
));
180 static int resize(struct tsort_store
*store
, size_t new_size
)
182 if (store
->alloc
< new_size
) {
183 void **tempstore
= git__realloc(store
->storage
, new_size
* sizeof(void *));
186 * Do not propagate on OOM; this will abort the sort and
187 * leave the array unsorted, but no error code will be
190 if (tempstore
== NULL
)
193 store
->storage
= tempstore
;
194 store
->alloc
= new_size
;
200 static void merge(void **dst
, const struct tsort_run
*stack
, ssize_t stack_curr
, struct tsort_store
*store
)
202 const ssize_t A
= stack
[stack_curr
- 2].length
;
203 const ssize_t B
= stack
[stack_curr
- 1].length
;
204 const ssize_t curr
= stack
[stack_curr
- 2].start
;
209 if (resize(store
, MIN(A
, B
)) < 0)
212 storage
= store
->storage
;
216 memcpy(storage
, &dst
[curr
], A
* sizeof(void *));
220 for (k
= curr
; k
< curr
+ A
+ B
; k
++) {
221 if ((i
< A
) && (j
< curr
+ A
+ B
)) {
222 if (store
->cmp(storage
[i
], dst
[j
]) <= 0)
223 dst
[k
] = storage
[i
++];
227 dst
[k
] = storage
[i
++];
232 memcpy(storage
, &dst
[curr
+ A
], B
* sizeof(void *));
236 for (k
= curr
+ A
+ B
- 1; k
>= curr
; k
--) {
237 if ((i
>= 0) && (j
>= curr
)) {
238 if (store
->cmp(dst
[j
], storage
[i
]) > 0)
241 dst
[k
] = storage
[i
--];
243 dst
[k
] = storage
[i
--];
250 static ssize_t
collapse(void **dst
, struct tsort_run
*stack
, ssize_t stack_curr
, struct tsort_store
*store
, ssize_t size
)
255 /* if the stack only has one thing on it, we are done with the collapse */
259 /* if this is the last merge, just do it */
260 if ((stack_curr
== 2) && (stack
[0].length
+ stack
[1].length
== size
)) {
261 merge(dst
, stack
, stack_curr
, store
);
262 stack
[0].length
+= stack
[1].length
;
267 /* check if the invariant is off for a stack of 2 elements */
268 else if ((stack_curr
== 2) && (stack
[0].length
<= stack
[1].length
)) {
269 merge(dst
, stack
, stack_curr
, store
);
270 stack
[0].length
+= stack
[1].length
;
274 else if (stack_curr
== 2)
277 A
= stack
[stack_curr
- 3].length
;
278 B
= stack
[stack_curr
- 2].length
;
279 C
= stack
[stack_curr
- 1].length
;
281 /* check first invariant */
284 merge(dst
, stack
, stack_curr
- 1, store
);
285 stack
[stack_curr
- 3].length
+= stack
[stack_curr
- 2].length
;
286 stack
[stack_curr
- 2] = stack
[stack_curr
- 1];
289 merge(dst
, stack
, stack_curr
, store
);
290 stack
[stack_curr
- 2].length
+= stack
[stack_curr
- 1].length
;
294 merge(dst
, stack
, stack_curr
, store
);
295 stack
[stack_curr
- 2].length
+= stack
[stack_curr
- 1].length
;
304 #define PUSH_NEXT() do {\
305 len = count_run(dst, curr, size, store);\
307 if (run < minrun) run = minrun;\
308 if (run > (ssize_t)size - curr) run = size - curr;\
310 bisort(&dst[curr], len, run, cmp);\
313 run_stack[stack_curr].start = curr;\
314 run_stack[stack_curr++].length = len;\
316 if (curr == (ssize_t)size) {\
318 while (stack_curr > 1) { \
319 merge(dst, run_stack, stack_curr, store); \
320 run_stack[stack_curr - 2].length += run_stack[stack_curr - 1].length; \
323 if (store->storage != NULL) {\
324 git__free(store->storage);\
325 store->storage = NULL;\
332 void git__tsort(void **dst
, size_t size
, cmp_ptr_t cmp
)
334 struct tsort_store _store
, *store
= &_store
;
335 struct tsort_run run_stack
[128];
337 ssize_t stack_curr
= 0;
343 bisort(dst
, 1, size
, cmp
);
347 /* compute the minimum run length */
348 minrun
= (ssize_t
)compute_minrun(size
);
350 /* temporary storage for merges */
352 store
->storage
= NULL
;
360 if (!check_invariant(run_stack
, stack_curr
)) {
361 stack_curr
= collapse(dst
, run_stack
, stack_curr
, store
, size
);