Skip to content

Commit b4b594a

Browse files
committed
prio-queue: priority queue of pointers to structs
Traditionally we used a singly linked list of commits to hold a set of in-flight commits while traversing history. The most typical use of the list is to add commits that are newly discovered to it, keep the list sorted by commit timestamp, pick up the newest one from the list, and keep digging. The cost of keeping the singly linked list sorted is nontrivial, and this typical use pattern better matches a priority queue. Introduce a prio-queue structure, that can be used either as a LIFO stack, or a priority queue. This will be used in the next patch to hold in-flight commits during sort-in-topological-order. Tests and the idea to make it usable for any "void *" pointers to "things" are by Jeff King. Bugs are mine. Signed-off-by: Junio C Hamano <[email protected]>
1 parent 08f704f commit b4b594a

File tree

6 files changed

+209
-0
lines changed

6 files changed

+209
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@
190190
/test-mktemp
191191
/test-parse-options
192192
/test-path-utils
193+
/test-prio-queue
193194
/test-regex
194195
/test-revision-walking
195196
/test-run-command

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,7 @@ TEST_PROGRAMS_NEED_X += test-mergesort
552552
TEST_PROGRAMS_NEED_X += test-mktemp
553553
TEST_PROGRAMS_NEED_X += test-parse-options
554554
TEST_PROGRAMS_NEED_X += test-path-utils
555+
TEST_PROGRAMS_NEED_X += test-prio-queue
555556
TEST_PROGRAMS_NEED_X += test-regex
556557
TEST_PROGRAMS_NEED_X += test-revision-walking
557558
TEST_PROGRAMS_NEED_X += test-run-command
@@ -685,6 +686,7 @@ LIB_H += parse-options.h
685686
LIB_H += patch-ids.h
686687
LIB_H += pathspec.h
687688
LIB_H += pkt-line.h
689+
LIB_H += prio-queue.h
688690
LIB_H += progress.h
689691
LIB_H += prompt.h
690692
LIB_H += quote.h
@@ -824,6 +826,7 @@ LIB_OBJS += pathspec.o
824826
LIB_OBJS += pkt-line.o
825827
LIB_OBJS += preload-index.o
826828
LIB_OBJS += pretty.o
829+
LIB_OBJS += prio-queue.o
827830
LIB_OBJS += progress.o
828831
LIB_OBJS += prompt.o
829832
LIB_OBJS += quote.o

prio-queue.c

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#include "cache.h"
2+
#include "commit.h"
3+
#include "prio-queue.h"
4+
5+
void clear_prio_queue(struct prio_queue *queue)
6+
{
7+
free(queue->array);
8+
queue->nr = 0;
9+
queue->alloc = 0;
10+
queue->array = NULL;
11+
}
12+
13+
void prio_queue_put(struct prio_queue *queue, void *thing)
14+
{
15+
prio_queue_compare_fn compare = queue->compare;
16+
int ix, parent;
17+
18+
/* Append at the end */
19+
ALLOC_GROW(queue->array, queue->nr + 1, queue->alloc);
20+
queue->array[queue->nr++] = thing;
21+
if (!compare)
22+
return; /* LIFO */
23+
24+
/* Bubble up the new one */
25+
for (ix = queue->nr - 1; ix; ix = parent) {
26+
parent = (ix - 1) / 2;
27+
if (compare(queue->array[parent], queue->array[ix],
28+
queue->cb_data) <= 0)
29+
break;
30+
31+
thing = queue->array[parent];
32+
queue->array[parent] = queue->array[ix];
33+
queue->array[ix] = thing;
34+
}
35+
}
36+
37+
void *prio_queue_get(struct prio_queue *queue)
38+
{
39+
void *result, *swap;
40+
int ix, child;
41+
prio_queue_compare_fn compare = queue->compare;
42+
43+
if (!queue->nr)
44+
return NULL;
45+
if (!compare)
46+
return queue->array[--queue->nr]; /* LIFO */
47+
48+
result = queue->array[0];
49+
if (!--queue->nr)
50+
return result;
51+
52+
queue->array[0] = queue->array[queue->nr];
53+
54+
/* Push down the one at the root */
55+
for (ix = 0; ix * 2 + 1 < queue->nr; ix = child) {
56+
child = ix * 2 + 1; /* left */
57+
if ((child + 1 < queue->nr) &&
58+
(compare(queue->array[child], queue->array[child + 1],
59+
queue->cb_data) >= 0))
60+
child++; /* use right child */
61+
62+
if (compare(queue->array[ix], queue->array[child],
63+
queue->cb_data) <= 0)
64+
break;
65+
66+
swap = queue->array[child];
67+
queue->array[child] = queue->array[ix];
68+
queue->array[ix] = swap;
69+
}
70+
return result;
71+
}

prio-queue.h

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#ifndef PRIO_QUEUE_H
2+
#define PRIO_QUEUE_H
3+
4+
/*
5+
* A priority queue implementation, primarily for keeping track of
6+
* commits in the 'date-order' so that we process them from new to old
7+
* as they are discovered, but can be used to hold any pointer to
8+
* struct. The caller is responsible for supplying a function to
9+
* compare two "things".
10+
*
11+
* Alternatively, this data structure can also be used as a LIFO stack
12+
* by specifying NULL as the comparison function.
13+
*/
14+
15+
/*
16+
* Compare two "things", one and two; the third parameter is cb_data
17+
* in the prio_queue structure. The result is returned as a sign of
18+
* the return value, being the same as the sign of the result of
19+
* subtracting "two" from "one" (i.e. negative if "one" sorts earlier
20+
* than "two").
21+
*/
22+
typedef int (*prio_queue_compare_fn)(const void *one, const void *two, void *cb_data);
23+
24+
struct prio_queue {
25+
prio_queue_compare_fn compare;
26+
void *cb_data;
27+
int alloc, nr;
28+
void **array;
29+
};
30+
31+
/*
32+
* Add the "thing" to the queue.
33+
*/
34+
extern void prio_queue_put(struct prio_queue *, void *thing);
35+
36+
/*
37+
* Extract the "thing" that compares the smallest out of the queue,
38+
* or NULL. If compare function is NULL, the queue acts as a LIFO
39+
* stack.
40+
*/
41+
extern void *prio_queue_get(struct prio_queue *);
42+
43+
extern void clear_prio_queue(struct prio_queue *);
44+
45+
#endif /* PRIO_QUEUE_H */

t/t0009-prio-queue.sh

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/bin/sh
2+
3+
test_description='basic tests for priority queue implementation'
4+
. ./test-lib.sh
5+
6+
cat >expect <<'EOF'
7+
1
8+
2
9+
3
10+
4
11+
5
12+
5
13+
6
14+
7
15+
8
16+
9
17+
10
18+
EOF
19+
test_expect_success 'basic ordering' '
20+
test-prio-queue 2 6 3 10 9 5 7 4 5 8 1 dump >actual &&
21+
test_cmp expect actual
22+
'
23+
24+
cat >expect <<'EOF'
25+
2
26+
3
27+
4
28+
1
29+
5
30+
6
31+
EOF
32+
test_expect_success 'mixed put and get' '
33+
test-prio-queue 6 2 4 get 5 3 get get 1 dump >actual &&
34+
test_cmp expect actual
35+
'
36+
37+
cat >expect <<'EOF'
38+
1
39+
2
40+
NULL
41+
1
42+
2
43+
NULL
44+
EOF
45+
test_expect_success 'notice empty queue' '
46+
test-prio-queue 1 2 get get get 1 2 get get get >actual &&
47+
test_cmp expect actual
48+
'
49+
50+
test_done

test-prio-queue.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#include "cache.h"
2+
#include "prio-queue.h"
3+
4+
static int intcmp(const void *va, const void *vb, void *data)
5+
{
6+
const int *a = va, *b = vb;
7+
return *a - *b;
8+
}
9+
10+
static void show(int *v)
11+
{
12+
if (!v)
13+
printf("NULL\n");
14+
else
15+
printf("%d\n", *v);
16+
free(v);
17+
}
18+
19+
int main(int argc, char **argv)
20+
{
21+
struct prio_queue pq = { intcmp };
22+
23+
while (*++argv) {
24+
if (!strcmp(*argv, "get"))
25+
show(prio_queue_get(&pq));
26+
else if (!strcmp(*argv, "dump")) {
27+
int *v;
28+
while ((v = prio_queue_get(&pq)))
29+
show(v);
30+
}
31+
else {
32+
int *v = malloc(sizeof(*v));
33+
*v = atoi(*argv);
34+
prio_queue_put(&pq, v);
35+
}
36+
}
37+
38+
return 0;
39+
}

0 commit comments

Comments
 (0)