diff --git a/reactos/lib/CMakeLists.txt b/reactos/lib/CMakeLists.txt index b75a58c9bd0..8a1bdcc27aa 100644 --- a/reactos/lib/CMakeLists.txt +++ b/reactos/lib/CMakeLists.txt @@ -27,6 +27,7 @@ endif() add_subdirectory(rtl) add_subdirectory(sdk) +add_subdirectory(skiplist) add_subdirectory(smlib) add_subdirectory(tdilib) diff --git a/reactos/lib/skiplist/CMakeLists.txt b/reactos/lib/skiplist/CMakeLists.txt new file mode 100644 index 00000000000..f0867b21bfe --- /dev/null +++ b/reactos/lib/skiplist/CMakeLists.txt @@ -0,0 +1,10 @@ + +# The library +add_definitions(-DSKIPLIST_LEVELS=16) +add_library(skiplist16 skiplist.c) + +# The Test program +add_executable(skiplist_test skiplist_test.c) +target_link_libraries(skiplist_test skiplist16) +set_module_type(skiplist_test win32cui) +add_importlibs(skiplist_test msvcrt kernel32 ntdll) diff --git a/reactos/lib/skiplist/skiplist.c b/reactos/lib/skiplist/skiplist.c new file mode 100644 index 00000000000..929453da2b6 --- /dev/null +++ b/reactos/lib/skiplist/skiplist.c @@ -0,0 +1,397 @@ +/* + * PROJECT: Skiplist implementation for the ReactOS Project + * LICENSE: GNU LGPL v2.1 or any later version as published by the Free Software Foundation + * PURPOSE: All implemented functions operating on the Skiplist + * COPYRIGHT: Copyright 2015 Colin Finck + */ + +#include +#include +#include +#include "skiplist.h" + +/** + * @name _GetRandomLevel + * + * Returns a random level for the next element to be inserted. + * This level is geometrically distributed for p = 0.5, so perfectly suitable for an efficient Skiplist implementation. + * + * @return + * A value between 0 and SKIPLIST_LEVELS - 1. + */ +static __inline CHAR +_GetRandomLevel() +{ + // Using a simple fixed seed and the Park-Miller Lehmer Minimal Standard Random Number Generator gives an acceptable distribution for our "random" levels. + static DWORD dwRandom = 1; + + DWORD dwLevel = 0; + DWORD dwShifted; + + // Generate 32 uniformly distributed pseudo-random bits using the Park-Miller Lehmer Minimal Standard Random Number Generator. + dwRandom = (DWORD)(((ULONGLONG)dwRandom * 48271UL) % 2147483647UL); + + // Shift out (32 - SKIPLIST_LEVELS) bits to the right to have no more than SKIPLIST_LEVELS bits set. + dwShifted = dwRandom >> (32 - SKIPLIST_LEVELS); + + // BitScanForward doesn't operate on a zero input value. + if (dwShifted) + { + // BitScanForward sets dwLevel to the zero-based position of the first set bit (from LSB to MSB). + // This makes dwLevel a geometrically distributed value between 0 and SKIPLIST_LEVELS - 1 for p = 0.5. + BitScanForward(&dwLevel, dwShifted); + } + + // dwLevel can't have a value higher than 31 this way, so a CHAR is more than enough. + return (CHAR)dwLevel; +} + +/** + * @name _InsertElementSkiplistWithInformation + * + * Determines a level for the new element and inserts it at the given position in the Skiplist. + * This function is internally used by the Skiplist insertion functions. + * + * @param Skiplist + * Pointer to the SKIPLIST structure to operate on. + * + * @param Element + * The element to insert. + * + * @param pUpdate + * Array containing the last nodes before our new node on each level. + * + * @param dwDistance + * Array containing the distance to the last node before our new node on each level. + * + * @return + * TRUE if the node was successfully inserted, FALSE if no memory could be allocated for it. + */ +static BOOL +_InsertElementSkiplistWithInformation(PSKIPLIST Skiplist, PVOID Element, PSKIPLIST_NODE* pUpdate, DWORD* dwDistance) +{ + CHAR chNewLevel; + CHAR i; + PSKIPLIST_NODE pNode; + + // Get the highest level, on which the node shall be inserted. + chNewLevel = _GetRandomLevel(); + + // Check if the new level is higher than the maximum level we currently have in the Skiplist. + if (chNewLevel > Skiplist->MaximumLevel) + { + // It is, so we also need to insert the new node right after the Head node on some levels. + // These are the levels higher than the current maximum level up to the new level. + // We also need to set the distance of these elements to the new node count to account for the calculations below. + for (i = Skiplist->MaximumLevel + 1; i <= chNewLevel; i++) + { + pUpdate[i] = &Skiplist->Head; + pUpdate[i]->Distance[i] = Skiplist->NodeCount + 1; + } + + // The new level is the new maximum level of the entire Skiplist. + Skiplist->MaximumLevel = chNewLevel; + } + + // Finally create our new Skiplist node. + pNode = Skiplist->AllocateRoutine(sizeof(SKIPLIST_NODE)); + if (!pNode) + return FALSE; + + pNode->Element = Element; + + // For each used level, insert us between the saved node for this level and its current next node. + for (i = 0; i <= chNewLevel; i++) + { + pNode->Next[i] = pUpdate[i]->Next[i]; + pUpdate[i]->Next[i] = pNode; + + // We know the walked distance in this level: dwDistance[i] + // We also know the element index of the new node: dwDistance[0] + // The new node's distance is now the walked distance in this level plus the difference between the saved node's distance and the element index. + pNode->Distance[i] = dwDistance[i] + (pUpdate[i]->Distance[i] - dwDistance[0]); + + // The saved node's distance is now the element index plus one (to account for the added node) minus the walked distance in this level. + pUpdate[i]->Distance[i] = dwDistance[0] + 1 - dwDistance[i]; + } + + // For all levels above the new node's level, we need to increment the distance, because we've just added our new node. + for (i = chNewLevel + 1; i <= Skiplist->MaximumLevel; i++) + ++pUpdate[i]->Distance[i]; + + // We've successfully added a node :) + ++Skiplist->NodeCount; + return TRUE; +} + +/** + * @name DeleteElementSkiplist + * + * Deletes an element from the Skiplist. The efficiency of this operation is O(log N) on average. + * + * Instead of the result of a LookupElementSkiplist call, it's sufficient to provide a dummy element with just enough information for your CompareRoutine. + * A lookup for the element to be deleted needs to be performed in any case. + * + * @param Skiplist + * Pointer to the SKIPLIST structure to operate on. + * + * @param Element + * Information about the element to be deleted. + * + * @return + * Returns the deleted element or NULL if no such element was found. + * You can then free memory for the deleted element if necessary. + */ +PVOID +DeleteElementSkiplist(PSKIPLIST Skiplist, PVOID Element) +{ + CHAR i; + PSKIPLIST_NODE pLastComparedNode = NULL; + PSKIPLIST_NODE pNode = &Skiplist->Head; + PSKIPLIST_NODE pUpdate[SKIPLIST_LEVELS]; + PVOID pReturnValue; + + // Find the node on every currently used level, after which the node to be deleted must follow. + // This can be done efficiently by starting from the maximum level and going down a level each time a position has been found. + for (i = Skiplist->MaximumLevel + 1; --i >= 0;) + { + while (pNode->Next[i] && pNode->Next[i] != pLastComparedNode && Skiplist->CompareRoutine(pNode->Next[i]->Element, Element) < 0) + pNode = pNode->Next[i]; + + // Reduce the number of comparisons by not comparing the same node on different levels twice. + pLastComparedNode = pNode->Next[i]; + pUpdate[i] = pNode; + } + + // Check if the node we're looking for has been found. + pNode = pNode->Next[0]; + if (!pNode || Skiplist->CompareRoutine(pNode->Element, Element) != 0) + { + // It hasn't been found, so there's nothing to delete. + return NULL; + } + + // Beginning at the lowest level, remove the node from each level of the list and merge distances. + // We can stop as soon as we found the first level that doesn't contain the node. + for (i = 0; i <= Skiplist->MaximumLevel && pUpdate[i]->Next[i] == pNode; i++) + { + pUpdate[i]->Distance[i] += pNode->Distance[i] - 1; + pUpdate[i]->Next[i] = pNode->Next[i]; + } + + // Now decrement the distance of the corresponding node in levels higher than the deleted node's level to account for the deleted node. + while (i <= Skiplist->MaximumLevel) + { + --pUpdate[i]->Distance[i]; + i++; + } + + // Return the deleted element (so the caller can free it if necessary) and free the memory for the node itself (allocated by us). + pReturnValue = pNode->Element; + Skiplist->FreeRoutine(pNode); + + // Find all levels which now contain no more nodes and reduce the maximum level of the entire Skiplist accordingly. + while (Skiplist->MaximumLevel > 0 && !Skiplist->Head.Next[Skiplist->MaximumLevel]) + --Skiplist->MaximumLevel; + + // We've successfully deleted the node :) + --Skiplist->NodeCount; + return pReturnValue; +} + +/** + * @name InitializeSkiplist + * + * Initializes a new Skiplist structure. + * + * @param Skiplist + * Pointer to the SKIPLIST structure to operate on. + * + * @param AllocateRoutine + * Pointer to a SKIPLIST_ALLOCATE_ROUTINE for allocating memory for new Skiplist nodes. + * + * @param CompareRoutine + * Pointer to a SKIPLIST_COMPARE_ROUTINE for comparing two elements of the Skiplist. + * + * @param FreeRoutine + * Pointer to a SKIPLIST_FREE_ROUTINE for freeing memory allocated with AllocateRoutine. + */ +void +InitializeSkiplist(PSKIPLIST Skiplist, PSKIPLIST_ALLOCATE_ROUTINE AllocateRoutine, PSKIPLIST_COMPARE_ROUTINE CompareRoutine, PSKIPLIST_FREE_ROUTINE FreeRoutine) +{ + // Store the routines. + Skiplist->AllocateRoutine = AllocateRoutine; + Skiplist->CompareRoutine = CompareRoutine; + Skiplist->FreeRoutine = FreeRoutine; + + // Initialize the members and pointers. + // The Distance array is only used when a node is non-NULL, so it doesn't need initialization. + Skiplist->MaximumLevel = 0; + Skiplist->NodeCount = 0; + ZeroMemory(Skiplist->Head.Next, sizeof(Skiplist->Head.Next)); +} + +/** + * @name InsertElementSkiplist + * + * Inserts a new element into the Skiplist. The efficiency of this operation is O(log N) on average. + * Uses CompareRoutine to find the right position for the insertion. + * + * @param Skiplist + * Pointer to the SKIPLIST structure to operate on. + * + * @param Element + * The element to insert. + * + * @return + * TRUE if the node was successfully inserted, FALSE if it already exists or no memory could be allocated for it. + */ +BOOL +InsertElementSkiplist(PSKIPLIST Skiplist, PVOID Element) +{ + CHAR i; + DWORD dwDistance[SKIPLIST_LEVELS + 1] = { 0 }; + PSKIPLIST_NODE pLastComparedNode = NULL; + PSKIPLIST_NODE pNode = &Skiplist->Head; + PSKIPLIST_NODE pUpdate[SKIPLIST_LEVELS]; + + // Find the node on every currently used level, after which the new node needs to be inserted. + // This can be done efficiently by starting from the maximum level and going down a level each time a position has been found. + for (i = Skiplist->MaximumLevel + 1; --i >= 0;) + { + // When entering this level, we begin at the distance of the last level we walked through. + dwDistance[i] = dwDistance[i + 1]; + + while (pNode->Next[i] && pNode->Next[i] != pLastComparedNode && Skiplist->CompareRoutine(pNode->Next[i]->Element, Element) < 0) + { + // Save our position in every level when walking through the nodes. + dwDistance[i] += pNode->Distance[i]; + + // Advance to the next node. + pNode = pNode->Next[i]; + } + + // Reduce the number of comparisons by not comparing the same node on different levels twice. + pLastComparedNode = pNode->Next[i]; + pUpdate[i] = pNode; + } + + // Check if the node already exists in the Skiplist. + pNode = pNode->Next[0]; + if (pNode && Skiplist->CompareRoutine(pNode->Element, Element) == 0) + { + // All elements to be inserted mustn't exist in the list, so we see this as a failure. + return FALSE; + } + + // The rest of the procedure is the same for both insertion functions. + return _InsertElementSkiplistWithInformation(Skiplist, Element, pUpdate, dwDistance); +} + +/** + * @name InsertTailElementSkiplist + * + * Inserts a new element at the end of the Skiplist. The efficiency of this operation is O(log N) on average. + * In contrast to InsertElementSkiplist, this function is more efficient by not calling CompareRoutine at all and always inserting the element at the end. + * You're responsible for calling this function only when you can guarantee that InsertElementSkiplist would also insert the element at the end. + * + * @param Skiplist + * Pointer to the SKIPLIST structure to operate on. + * + * @param Element + * The element to insert. + * + * @return + * TRUE if the node was successfully inserted, FALSE if it already exists or no memory could be allocated for it. + */ +BOOL +InsertTailElementSkiplist(PSKIPLIST Skiplist, PVOID Element) +{ + CHAR i; + DWORD dwDistance[SKIPLIST_LEVELS + 1] = { 0 }; + PSKIPLIST_NODE pNode = &Skiplist->Head; + PSKIPLIST_NODE pUpdate[SKIPLIST_LEVELS]; + + // Find the last node on every currently used level, after which the new node needs to be inserted. + // This can be done efficiently by starting from the maximum level and going down a level each time a position has been found. + for (i = Skiplist->MaximumLevel + 1; --i >= 0;) + { + // When entering this level, we begin at the distance of the last level we walked through. + dwDistance[i] = dwDistance[i + 1]; + + while (pNode->Next[i]) + { + // Save our position in every level when walking through the nodes. + dwDistance[i] += pNode->Distance[i]; + + // Advance to the next node. + pNode = pNode->Next[i]; + } + + pUpdate[i] = pNode; + } + + // The rest of the procedure is the same for both insertion functions. + return _InsertElementSkiplistWithInformation(Skiplist, Element, pUpdate, dwDistance); +} + +/** + * @name LookupElementSkiplist + * + * Looks up an element in the Skiplist. The efficiency of this operation is O(log N) on average. + * + * @param Skiplist + * Pointer to the SKIPLIST structure to operate on. + * + * @param Element + * Information about the element to look for. + * + * @param ElementIndex + * Pointer to a DWORD that will contain the zero-based index of the element in the Skiplist. + * If you're not interested in the index, you can set this parameter to NULL. + * + * @return + * Returns the found element or NULL if no such element was found. + */ +PVOID +LookupElementSkiplist(PSKIPLIST Skiplist, PVOID Element, PDWORD ElementIndex) +{ + CHAR i; + DWORD dwIndex = 0; + PSKIPLIST_NODE pLastComparedNode = NULL; + PSKIPLIST_NODE pNode = &Skiplist->Head; + + // Do the efficient lookup in Skiplists: + // * Start from the maximum level. + // * Walk through all nodes on this level that come before the node we're looking for. + // * When we have reached such a node, go down a level and continue there. + // * Repeat these steps till we're in level 0, right in front of the node we're looking for. + pNode = &Skiplist->Head; + + for (i = Skiplist->MaximumLevel + 1; --i >= 0;) + { + while (pNode->Next[i] && pNode->Next[i] != pLastComparedNode && Skiplist->CompareRoutine(pNode->Next[i]->Element, Element) < 0) + { + dwIndex += pNode->Distance[i]; + pNode = pNode->Next[i]; + } + + // Reduce the number of comparisons by not comparing the same node on different levels twice. + pLastComparedNode = pNode->Next[i]; + } + + // We must be right in front of the node we're looking for now, otherwise it doesn't exist in the Skiplist at all. + pNode = pNode->Next[0]; + if (!pNode || Skiplist->CompareRoutine(pNode->Element, Element) != 0) + { + // It hasn't been found, so there's nothing to return. + return NULL; + } + + // Return the index of the element if the caller is interested. + if (ElementIndex) + *ElementIndex = dwIndex; + + // Return the stored element of the found node. + return pNode->Element; +} diff --git a/reactos/lib/skiplist/skiplist.h b/reactos/lib/skiplist/skiplist.h new file mode 100644 index 00000000000..1313768f4a9 --- /dev/null +++ b/reactos/lib/skiplist/skiplist.h @@ -0,0 +1,50 @@ +/* + * PROJECT: Skiplist implementation for the ReactOS Project + * LICENSE: GNU LGPL v2.1 or any later version as published by the Free Software Foundation + * PURPOSE: Interfaces for the Skiplist + * COPYRIGHT: Copyright 2015 Colin Finck + */ + +#ifndef _REACTOS_SKIPLIST_H +#define _REACTOS_SKIPLIST_H + +// Define SKIPLIST_LEVELS to a value between 1 and 32 before including this header. +// This specifies the maximum number of levels you want your Skiplist to have. +// A value of X is recommended for handling up to 2^X elements. +#ifndef SKIPLIST_LEVELS +#error Please define SKIPLIST_LEVELS to a value between 1 and 32. +#endif + +// Function pointer definitions +typedef PVOID (WINAPI *PSKIPLIST_ALLOCATE_ROUTINE)(DWORD); +typedef int (WINAPI *PSKIPLIST_COMPARE_ROUTINE)(PVOID, PVOID); +typedef void (WINAPI *PSKIPLIST_FREE_ROUTINE)(PVOID); + +// Structure definitions +typedef struct _SKIPLIST_NODE +{ + DWORD Distance[SKIPLIST_LEVELS]; + struct _SKIPLIST_NODE* Next[SKIPLIST_LEVELS]; + PVOID Element; +} +SKIPLIST_NODE, *PSKIPLIST_NODE; + +typedef struct _SKIPLIST +{ + SKIPLIST_NODE Head; + CHAR MaximumLevel; + DWORD NodeCount; + PSKIPLIST_ALLOCATE_ROUTINE AllocateRoutine; + PSKIPLIST_COMPARE_ROUTINE CompareRoutine; + PSKIPLIST_FREE_ROUTINE FreeRoutine; +} +SKIPLIST, *PSKIPLIST; + +// Function prototypes +void InitializeSkiplist(PSKIPLIST Skiplist, PSKIPLIST_ALLOCATE_ROUTINE AllocateRoutine, PSKIPLIST_COMPARE_ROUTINE CompareRoutine, PSKIPLIST_FREE_ROUTINE FreeRoutine); +BOOL InsertElementSkiplist(PSKIPLIST Skiplist, PVOID Element); +BOOL InsertTailElementSkiplist(PSKIPLIST Skiplist, PVOID Element); +PVOID DeleteElementSkiplist(PSKIPLIST Skiplist, PVOID Element); +PVOID LookupElementSkiplist(PSKIPLIST Skiplist, PVOID Element, PDWORD ElementIndex); + +#endif diff --git a/reactos/lib/skiplist/skiplist_test.c b/reactos/lib/skiplist/skiplist_test.c new file mode 100644 index 00000000000..66996be5e16 --- /dev/null +++ b/reactos/lib/skiplist/skiplist_test.c @@ -0,0 +1,93 @@ +/* + * PROJECT: Skiplist implementation for the ReactOS Project + * LICENSE: GNU LGPL v2.1 or any later version as published by the Free Software Foundation + * PURPOSE: A simple program for testing the Skiplist implementation + * COPYRIGHT: Copyright 2015 Colin Finck + */ + +#include +#include +#include "skiplist.h" + +void +DumpSkiplist(PSKIPLIST Skiplist) +{ + CHAR i; + DWORD j; + PSKIPLIST_NODE pNode; + + printf("======= DUMPING SKIPLIST =======\n"); + + for (i = Skiplist->MaximumLevel + 1; --i >= 0;) + { + pNode = &Skiplist->Head; + printf("H"); + + while (pNode->Next[i]) + { + printf("-"); + + // By using the Distance array for painting the lines, we verify both the links and the distances for correctness. + for (j = 1; j < pNode->Distance[i]; j++) + printf("---"); + + printf("%02lu", (DWORD)pNode->Next[i]->Element); + + pNode = pNode->Next[i]; + } + + printf("\n"); + } + + printf("================================\n\n"); +} + +PVOID WINAPI +MyAlloc(DWORD Size) +{ + return HeapAlloc(GetProcessHeap(), 0, Size); +} + +int WINAPI +MyCompare(PVOID A, PVOID B) +{ + return (DWORD)A - (DWORD)B; +} + +void WINAPI +MyFree(PVOID Ptr) +{ + HeapFree(GetProcessHeap(), 0, Ptr); +} + +int +main() +{ + DWORD Element; + DWORD ElementIndex; + DWORD i; + SKIPLIST Skiplist; + + system("mode con cols=300"); + InitializeSkiplist(&Skiplist, MyAlloc, MyCompare, MyFree); + + // Insert some random elements with random numbers. + for (i = 0; i < 40; i++) + InsertElementSkiplist(&Skiplist, (PVOID)(rand() % 100)); + + // Delete all with index 0 to 29. + for (i = 0; i < 30; i++) + DeleteElementSkiplist(&Skiplist, (PVOID)i); + + // Insert some more random elements. + for (i = 0; i < 40; i++) + InsertElementSkiplist(&Skiplist, (PVOID)(rand() % 100)); + + // Check if an element with number 44 is in the list and output its index. + Element = (DWORD)LookupElementSkiplist(&Skiplist, (PVOID)44, &ElementIndex); + printf("Element = %lu, ElementIndex = %lu\n\n", Element, ElementIndex); + + DumpSkiplist(&Skiplist); + + return 0; +}