Created
December 24, 2020 04:55
-
-
Save hanxi/96094b32b5713f21affc7d12676b554a to your computer and use it in GitHub Desktop.
skiplist
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// fork from https://github.com/xjdrew/lua-zset | |
// skiplist similar with the version in redis | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <math.h> | |
#include "skiplist.h" | |
skiplistNode *slCreateNode(int level, slobj *obj) { | |
skiplistNode *n = malloc(sizeof(*n) + level * sizeof(struct skiplistLevel)); | |
n->obj = obj; | |
return n; | |
} | |
skiplist *slCreate(void) { | |
int j; | |
skiplist *sl; | |
sl = malloc(sizeof(*sl)); | |
sl->level = 1; | |
sl->length = 0; | |
sl->header = slCreateNode(SKIPLIST_MAXLEVEL, NULL); | |
for (j=0; j < SKIPLIST_MAXLEVEL; j++) { | |
sl->header->level[j].forward = NULL; | |
sl->header->level[j].span = 0; | |
} | |
sl->header->backward = NULL; | |
sl->tail = NULL; | |
return sl; | |
} | |
slobj* slCreateObj(const char* ptr, size_t length) { | |
slobj *obj = malloc(sizeof(*obj)); | |
obj->ptr = malloc(length + 1); | |
if(ptr) { | |
memcpy(obj->ptr, ptr, length); | |
} | |
obj->ptr[length] = '\0'; | |
obj->length = length; | |
return obj; | |
} | |
void slFreeObj(slobj *obj) { | |
free(obj->ptr); | |
free(obj); | |
} | |
void slFreeNode(skiplistNode *node) { | |
slFreeObj(node->obj); | |
free(node); | |
} | |
void slFree(skiplist *sl) { | |
skiplistNode *node = sl->header->level[0].forward, *next; | |
free(sl->header); | |
while(node) { | |
next = node->level[0].forward; | |
slFreeNode(node); | |
node = next; | |
} | |
free(sl); | |
} | |
int slRandomLevel(void) { | |
int level = 1; | |
while((random() & 0xffff) < (SKIPLIST_P * 0xffff)) | |
level += 1; | |
return (level < SKIPLIST_MAXLEVEL) ? level : SKIPLIST_MAXLEVEL; | |
} | |
int compareslObj(slobj *a, slobj *b) { | |
int cmp = memcmp(a->ptr, b->ptr, a->length <= b->length ? a->length : b->length); | |
if(cmp == 0) return a->length - b->length; | |
return cmp; | |
} | |
int equalslObj(slobj *a, slobj *b) { | |
return compareslObj(a, b) == 0; | |
} | |
void slInsert(skiplist *sl, slobj *obj) { | |
skiplistNode *update[SKIPLIST_MAXLEVEL], *x; | |
unsigned int rank[SKIPLIST_MAXLEVEL]; | |
int i, level; | |
x = sl->header; | |
for (i = sl->level-1; i >= 0; i--) { | |
/* store rank that is crossed to reach the insert position */ | |
rank[i] = i == (sl->level-1) ? 0 : rank[i+1]; | |
while (x->level[i].forward && | |
(sl->compareCb(sl->ud, x->level[i].forward->obj, obj) < 0 || | |
(sl->compareCb(sl->ud, x->level[i].forward->obj, obj) == 0 && | |
compareslObj(x->level[i].forward->obj,obj) < 0))) { | |
rank[i] += x->level[i].span; | |
x = x->level[i].forward; | |
} | |
update[i] = x; | |
} | |
/* we assume the key is not already inside, since we allow duplicated | |
* keys, and the re-insertion key should never | |
* happen since the caller of slInsert() should test in the hash table | |
* if the element is already inside or not. */ | |
level = slRandomLevel(); | |
if (level > sl->level) { | |
for (i = sl->level; i < level; i++) { | |
rank[i] = 0; | |
update[i] = sl->header; | |
update[i]->level[i].span = sl->length; | |
} | |
sl->level = level; | |
} | |
x = slCreateNode(level,obj); | |
for (i = 0; i < level; i++) { | |
x->level[i].forward = update[i]->level[i].forward; | |
update[i]->level[i].forward = x; | |
/* update span covered by update[i] as x is inserted here */ | |
x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]); | |
update[i]->level[i].span = (rank[0] - rank[i]) + 1; | |
} | |
/* increment span for untouched levels */ | |
for (i = level; i < sl->level; i++) { | |
update[i]->level[i].span++; | |
} | |
x->backward = (update[0] == sl->header) ? NULL : update[0]; | |
if (x->level[0].forward) | |
x->level[0].forward->backward = x; | |
else | |
sl->tail = x; | |
sl->length++; | |
} | |
/* Internal function used by slDelete */ | |
void slDeleteNode(skiplist *sl, skiplistNode *x, skiplistNode **update) { | |
int i; | |
for (i = 0; i < sl->level; i++) { | |
if (update[i]->level[i].forward == x) { | |
update[i]->level[i].span += x->level[i].span - 1; | |
update[i]->level[i].forward = x->level[i].forward; | |
} else { | |
update[i]->level[i].span -= 1; | |
} | |
} | |
if (x->level[0].forward) { | |
x->level[0].forward->backward = x->backward; | |
} else { | |
sl->tail = x->backward; | |
} | |
while(sl->level > 1 && sl->header->level[sl->level-1].forward == NULL) | |
sl->level--; | |
sl->length--; | |
} | |
/* Delete an element with matching object from the skiplist. */ | |
int slDelete(skiplist *sl, slobj *obj) { | |
skiplistNode *update[SKIPLIST_MAXLEVEL], *x; | |
int i; | |
x = sl->header; | |
for (i = sl->level-1; i >= 0; i--) { | |
while (x->level[i].forward && | |
(sl->compareCb(sl->ud, x->level[i].forward->obj, obj) < 0 || | |
(sl->compareCb(sl->ud, x->level[i].forward->obj, obj) == 0 && | |
compareslObj(x->level[i].forward->obj,obj) < 0))) | |
x = x->level[i].forward; | |
update[i] = x; | |
} | |
/* We may have multiple elements with the same object, | |
* will delete firt find element. */ | |
x = x->level[0].forward; | |
if (x && sl->compareCb(sl->ud, obj, x->obj) == 0 && equalslObj(x->obj,obj)) { | |
slDeleteNode(sl, x, update); | |
slFreeNode(x); | |
return 1; | |
} else { | |
return 0; /* not found */ | |
} | |
return 0; /* not found */ | |
} | |
/* Delete all the elements with rank between start and end from the skiplist. | |
* Start and end are inclusive. Note that start and end need to be 1-based */ | |
unsigned long slDeleteByRank(skiplist *sl, unsigned int start, unsigned int end, slDeleteCb cb, void* ud) { | |
skiplistNode *update[SKIPLIST_MAXLEVEL], *x; | |
unsigned long traversed = 0, removed = 0; | |
int i; | |
x = sl->header; | |
for (i = sl->level-1; i >= 0; i--) { | |
while (x->level[i].forward && (traversed + x->level[i].span) < start) { | |
traversed += x->level[i].span; | |
x = x->level[i].forward; | |
} | |
update[i] = x; | |
} | |
traversed++; | |
x = x->level[0].forward; | |
while (x && traversed <= end) { | |
skiplistNode *next = x->level[0].forward; | |
slDeleteNode(sl,x,update); | |
cb(ud, x->obj); | |
slFreeNode(x); | |
removed++; | |
traversed++; | |
x = next; | |
} | |
return removed; | |
} | |
/* Find the rank for an element by key. | |
* Returns 0 when the element cannot be found, rank otherwise. | |
* Note that the rank is 1-based due to the span of sl->header to the | |
* first element. */ | |
unsigned long slGetRank(skiplist *sl, slobj *obj) { | |
skiplistNode *x; | |
unsigned long rank = 0; | |
int i; | |
x = sl->header; | |
for (i = sl->level-1; i >= 0; i--) { | |
while (x->level[i].forward && | |
(sl->compareCb(sl->ud, x->level[i].forward->obj, obj) < 0 || | |
(sl->compareCb(sl->ud, x->level[i].forward->obj, obj) == 0 && | |
compareslObj(x->level[i].forward->obj,obj) <= 0))) { | |
rank += x->level[i].span; | |
x = x->level[i].forward; | |
} | |
/* x might be equal to sl->header, so test if obj is non-NULL */ | |
if (x->obj && equalslObj(x->obj, obj)) { | |
return rank; | |
} | |
} | |
return 0; | |
} | |
/* Finds an element by its rank. The rank argument needs to be 1-based. */ | |
skiplistNode* slGetNodeByRank(skiplist *sl, unsigned long rank) { | |
if(rank == 0 || rank > sl->length) { | |
return NULL; | |
} | |
skiplistNode *x; | |
unsigned long traversed = 0; | |
int i; | |
x = sl->header; | |
for (i = sl->level-1; i >= 0; i--) { | |
while (x->level[i].forward && (traversed + x->level[i].span) <= rank) | |
{ | |
traversed += x->level[i].span; | |
x = x->level[i].forward; | |
} | |
if (traversed == rank) { | |
return x; | |
} | |
} | |
return NULL; | |
} | |
/* range [min, max], left & right both include */ | |
/* Returns if there is a part of the zset is in range. */ | |
int slIsInRange(skiplist *sl, slobj *minobj, slobj *maxobj) { | |
skiplistNode *x; | |
/* Test for ranges that will always be empty. */ | |
if (sl->compareCb(sl->ud, minobj, maxobj) > 0) { | |
return 0; | |
} | |
x = sl->tail; | |
if (x == NULL || sl->compareCb(sl->ud, x->obj, minobj) < 0) | |
return 0; | |
x = sl->header->level[0].forward; | |
if (x == NULL || sl->compareCb(sl->ud, x->obj, maxobj) > 0) | |
return 0; | |
return 1; | |
} | |
/* Find the first node that is contained in the specified range. | |
* Returns NULL when no element is contained in the range. */ | |
skiplistNode *slFirstInRange(skiplist *sl, slobj *minobj, slobj *maxobj) { | |
skiplistNode *x; | |
int i; | |
/* If everything is out of range, return early. */ | |
if (!slIsInRange(sl, minobj, maxobj)) return NULL; | |
x = sl->header; | |
for (i = sl->level-1; i >= 0; i--) { | |
/* Go forward while *OUT* of range. */ | |
while (x->level[i].forward && sl->compareCb(sl->ud, x->level[i].forward->obj, minobj) < 0) | |
x = x->level[i].forward; | |
} | |
/* This is an inner range, so the next node cannot be NULL. */ | |
x = x->level[0].forward; | |
return x; | |
} | |
/* Find the last node that is contained in the specified range. | |
* Returns NULL when no element is contained in the range. */ | |
skiplistNode *slLastInRange(skiplist *sl, slobj *minobj, slobj *maxobj) { | |
skiplistNode *x; | |
int i; | |
/* If everything is out of range, return early. */ | |
if (!slIsInRange(sl, minobj, maxobj)) return NULL; | |
x = sl->header; | |
for (i = sl->level-1; i >= 0; i--) { | |
/* Go forward while *IN* range. */ | |
while (x->level[i].forward && | |
sl->compareCb(sl->ud, x->level[i].forward->obj, maxobj) <= 0) | |
x = x->level[i].forward; | |
} | |
/* This is an inner range, so this node cannot be NULL. */ | |
return x; | |
} | |
void slDump(skiplist *sl) { | |
skiplistNode *x; | |
int i; | |
x = sl->header; | |
i = 0; | |
while(x->level[0].forward) { | |
x = x->level[0].forward; | |
i++; | |
printf("node %d: member:%s\n", i, x->obj->ptr); | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
#include <stdlib.h> | |
#define SKIPLIST_MAXLEVEL 32 | |
#define SKIPLIST_P 0.25 | |
typedef struct slobj { | |
char *ptr; | |
size_t length; | |
} slobj; | |
typedef struct skiplistNode { | |
slobj* obj; | |
struct skiplistNode *backward; | |
struct skiplistLevel { | |
struct skiplistNode *forward; | |
unsigned int span; | |
}level[]; | |
} skiplistNode; | |
/* | |
* -1 : obj1 < obj2 | |
* 0 : obj1 == obj2 | |
* 1 : obj1 > obj2 | |
*/ | |
typedef int (*slCompareCb) (void *ud, slobj *obj1, slobj *obj2); | |
typedef struct skiplist { | |
struct skiplistNode *header, *tail; | |
unsigned long length; | |
int level; | |
slCompareCb compareCb; | |
void * ud; | |
} skiplist; | |
typedef void (*slDeleteCb) (void *ud, slobj *obj); | |
slobj* slCreateObj(const char* ptr, size_t length); | |
void slFreeObj(slobj *obj); | |
skiplist *slCreate(void); | |
void slFree(skiplist *sl); | |
void slDump(skiplist *sl); | |
void slInsert(skiplist *sl, slobj *obj); | |
int slDelete(skiplist *sl, slobj *obj); | |
unsigned long slDeleteByRank(skiplist *sl, unsigned int start, unsigned int end, slDeleteCb cb, void* ud); | |
unsigned long slGetRank(skiplist *sl, slobj *o); | |
skiplistNode* slGetNodeByRank(skiplist *sl, unsigned long rank); | |
skiplistNode *slFirstInRange(skiplist *sl, slobj *minobj, slobj *maxobj); | |
skiplistNode *slLastInRange(skiplist *sl, slobj *minobj, slobj *maxobj); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment