Sollen wir gemeinsam eine nette Double-linked- oder vielleicht sogar Skip List in C schreiben?
-
Topic ist programm. Nachdem wir hier in letzter Zeit ziemlich ... Fragen darüber hatten ... zeigen wie es geht?
-
Oder doch einen Artikel über alle möglichen Arten von Pointers?
-
Warum in C? Wir sind hier doch in einem C++ Forum.
Write in C
-
@Swordfish
Nein. Das würde suggerieren, LL wären zu irgendwas nütze.
-
@Wutz
Naja, sie sind schon zu was nütze. Für die meisten Fälle reicht ein Vektor, es gibt Fälle, wo DLL besser abschneiden.
-
-
Kann man machen. Würde mich auch so interessieren wie man das in der Praxis sauber macht. Ich hab was, komplett makrotisiert damits mit eigenen Typen läuft + Pointer auf ein Destructor-Callback wenn man so will. Aber das hab ich grad nicht zur Hand.
-
Ich hol schonmal das Popcorn.
-
@5cript bring' was mit.
@eigenartig sagte in Sollen wir gemeinsam eine nette Double-linked- oder vielleicht sogar Skip List in C schreiben?:
komplett makrotisiert damits mit eigenen Typen läuft
void *data; // done.
-
@Swordfish sagte in Sollen wir gemeinsam eine nette Double-linked- oder vielleicht sogar Skip List in C schreiben?:
@5cript bring' was mit.
Wie wärs wenn du einfach selbst ein Artikel schreibst, damit wir den hier lesen, und damit der dann hier einfach eingepflegt werden kann, damit du glücklich bist,
die Fresse hältstund immer was zum Zurückschauen hast.
-
@eigenartig sagte in Sollen wir gemeinsam eine nette Double-linked- oder vielleicht sogar Skip List in C schreiben?:
die Fresse hältst
ok.
-
@Swordfish Ich bin dafür dass du selbst was bringst.
-
Vorschlag für eine Node und eine List:
#include <stddef.h> typedef struct node_tag node_t; struct node_tag { void *data; node_t *next; node_t *prev; }; typedef struct list_tag { node_t *head; node_t *tail; size_t length; } list_t;
Einwände? Vorschläge?
//edit: Boah ey, Tabsize hier ist immer noch 8
// edit2: Sollen wir uns auch die payload size merken?
-
@Swordfish
Bitte haue mich nicht, ich hatte gerade Lust die Liste zu implementieren.Wenn du diese mal ausprobieren möchtest, dann kopiere den folgenden Code in LinkedList.h und inkludiere diese in einem C Projekt. Die Funktion LinkedList_Test_Function() ist eine Art Testreihe für die Liste und überprüft diverse Eigenschaften.
#pragma once #include <malloc.h> #include <assert.h> #include <stdbool.h> // ******************** Defines ******************** #define LINKED_LIST_VERSION 1 // ******************** Typedefs ******************** typedef int LinkedList_NodeValueType; /** * @brief Definition der Callback Funktion für LinkedList_ForEach() */ typedef void (*ForEachFunctionCallback)(LinkedList_NodeValueType); /** * @brief Knoten der Liste */ typedef struct LinkedList_Node { struct LinkedList_Node* mPrev; struct LinkedList_Node* mSucc; LinkedList_NodeValueType mValue; } LinkedList_Node; /** * @brief Definition der doppelt verkettete Liste */ typedef struct { LinkedList_Node* mAnchor; } LinkedList; // ******************** Funktionen ******************** /** * @brief Initíalisiert die Liste */ void LinkedList_Create(LinkedList* const List) { List->mAnchor = NULL; } /** * @brief Bestimmt die Anzahl der in der Liste gespeicherten Elemente. */ size_t LinkedList_Size(LinkedList const* const List) { LinkedList_Node const* Node = List->mAnchor; size_t Count = 0; while (Node != NULL) { Count++; Node = Node->mSucc; } return Count; } /** * @brief Fügt ein Element an das Ende der Liste an. */ void LinkedList_PushBack(LinkedList* const List, LinkedList_NodeValueType Value) { if (List->mAnchor == NULL) { List->mAnchor = malloc(sizeof(LinkedList_Node)); List->mAnchor->mPrev = NULL; List->mAnchor->mSucc = NULL; List->mAnchor->mValue = Value; } else { LinkedList_Node* Node = List->mAnchor; while(Node->mSucc != NULL) Node = Node->mSucc; Node->mSucc = malloc(sizeof(LinkedList_Node)); Node->mSucc->mPrev = Node; Node->mSucc->mSucc = NULL; Node->mSucc->mValue = Value; } } /** * @brief Fügt ein Element der Liste an. */ bool LinkedList_Add(LinkedList* const List, size_t Index, LinkedList_NodeValueType Value) { size_t i = 0; LinkedList_Node* Node = List->mAnchor; while (i != Index) { if (Node == NULL) return false; Node = Node->mSucc; i++; } if (Node != NULL) { LinkedList_Node* PrevNode = Node->mPrev; LinkedList_Node* NewNode = malloc(sizeof(LinkedList_Node)); NewNode->mValue = Value; NewNode->mSucc = Node; NewNode->mPrev = PrevNode; if (PrevNode == NULL) List->mAnchor = NewNode; else PrevNode->mSucc = NewNode; Node->mPrev = NewNode; return true; } else if (Index == 0) { LinkedList_Node* SuccNode = List->mAnchor; List->mAnchor = malloc(sizeof(LinkedList_Node)); List->mAnchor->mSucc = SuccNode; List->mAnchor->mPrev = NULL; List->mAnchor->mValue = Value; return true; } else if (i == Index) { Node = List->mAnchor; if (Node == NULL) return false; while (Node->mSucc != NULL) Node = Node->mSucc; Node->mSucc = malloc(sizeof(LinkedList_Node)); Node->mSucc->mSucc = NULL; Node->mSucc->mPrev = Node; Node->mSucc->mValue = Value; return true; } return false; } /** * @brief Löscht das i-te Element der Liste */ bool LinkedList_Remove(LinkedList* const List, size_t Index) { size_t i = 0; LinkedList_Node* Node = List->mAnchor; while (i != Index) { if (Node == NULL) return false; Node = Node->mSucc; i++; } if (Node != NULL) { LinkedList_Node* SuccNode = Node->mSucc; LinkedList_Node* PrevNode = Node->mPrev; free(Node); if (SuccNode != NULL) SuccNode->mPrev = PrevNode; if (PrevNode != NULL) PrevNode->mSucc = SuccNode; else List->mAnchor = SuccNode; return true; } return false; } /** * @brief Löscht die komplette Liste */ void LinkedList_Release(LinkedList* const List) { LinkedList_Node* Node = List->mAnchor; while (Node != NULL) { LinkedList_Node* SuccNode = Node->mSucc; free(Node); Node = SuccNode; } List->mAnchor = NULL; } /** * @brief Bestimmt das i-te Element der Liste */ bool LinkedList_At(LinkedList const* const List, size_t Index, LinkedList_NodeValueType* Value) { LinkedList_Node const * Node = List->mAnchor; size_t i = 0; while (i != Index) { Node = Node->mSucc; if (Node == NULL) return false; i++; } if (Value != NULL) *Value = Node->mValue; return true; } // Vorschlag für weitere Funktionen // void LinkedList_Transform(LinkedList const* const List, LinkedList_NodeValueType Value) // void LinkedList_ForEach(LinkedList const* const List, ForEachFunctionCallback Callback) // void LinkedList_ForEachReverse(LinkedList const* const List, ForEachFunctionCallback Callback) // void LinkedList_FindIf(LinkedList const* const List, LinkedList_NodeValueType Value) // Die folgenden Funktionen dienen rein zur Überprüfung/Debugging der Liste. /** * @brief Testfunktion: Überprüft den Inhalt der Liste. Wird nur für LinkedList_Test_Function() benötigt. */ void LinkedList_Test_ListContains(LinkedList const* const List, LinkedList_NodeValueType const* const Content, size_t Size) { LinkedList_NodeValueType Value; assert(LinkedList_Size(List) == Size); for (size_t i = 0; i < Size; i++) { assert(LinkedList_At(List, i, &Value)); assert(Value == Content[i]); } if (List->mAnchor == NULL) return; // Der Vorgänger des ersten Elements muss immer NULL sein assert(List->mAnchor->mPrev == NULL); // Nun prüfen wir die mPrev, mSucc Beziehungen LinkedList_Node const* Node = List->mAnchor; while (Node != NULL) { if (Node->mSucc != NULL) assert(Node->mSucc->mPrev == Node); if (Node->mPrev != NULL) assert(Node->mPrev->mSucc == Node); // doppelt gemoppelt hält halt besser Node = Node->mSucc; } } /** * @brief Testfunktion: Stellt diverse Eigenschaften der Implementierung sicher */ void LinkedList_Test_Function() { LinkedList L; LinkedList_Create(&L); assert(L.mAnchor == NULL); assert(LinkedList_Size(&L) == 0); // Hänge ans Ende der Liste 10 und prüfe Inhalt static const LinkedList_NodeValueType R1[] = { 10 }; LinkedList_PushBack(&L, 10); LinkedList_Test_ListContains(&L, R1, 1); // Hänge ans Ende der Liste 20 und prüfe Inhalt static const LinkedList_NodeValueType R2[] = { 10, 20 }; LinkedList_PushBack(&L, 20); LinkedList_Test_ListContains(&L, R2, 2); // Hänge ans Ende der Liste 30 und prüfe Inhalt static const LinkedList_NodeValueType R3[] = { 10, 20, 30 }; LinkedList_PushBack(&L, 30); LinkedList_Test_ListContains(&L, R3, 3); // Füge 10 an der ersten Stelle ein und prüfe Inhalt static const LinkedList_NodeValueType R4[] = {10, 40, 20, 30}; assert(LinkedList_Add(&L, 1, 40)); LinkedList_Test_ListContains(&L, R4, 4); // Füge 50 an der ersten Stelle ein und prüfe Inhalt static const LinkedList_NodeValueType R5[] = {50, 10, 40, 20, 30}; assert(LinkedList_Add(&L, 0, 50)); LinkedList_Test_ListContains(&L, R5, 5); // Lösche Element 3 und prüfe Inhalt static const LinkedList_NodeValueType R6[] = {50, 10, 40, 30}; assert(LinkedList_Remove(&L, 3)); LinkedList_Test_ListContains(&L, R6, 4); // Lösche Element 0 und prüfe Inhalt static const LinkedList_NodeValueType R7[] = {10, 40, 30}; assert(LinkedList_Remove(&L, 0)); LinkedList_Test_ListContains(&L, R7, 3); // Füge Element 60 ans Ende hinzu und prüfe Inhalt static const LinkedList_NodeValueType R8[] = {10, 40, 30, 60}; assert(LinkedList_Add(&L, 3, 60)); LinkedList_Test_ListContains(&L, R8, 4); // Lösche letztes Element und prüfe Inhalt static const LinkedList_NodeValueType R9[] = {10, 40, 30}; assert(LinkedList_Remove(&L, 3)); LinkedList_Test_ListContains(&L, R9, 3); // Am Ende die Liste löschen LinkedList_Release(&L); }
-
@Swordfish sagte in [Sollen wir gemeinsam eine nette Double-linked- oder vielleicht sogar
//edit: Boah ey, Tabsize hier ist immer noch 8
Ich benutze das Stylus-Browser-Plugin und damit
code{ tab-size: 4 !important; }
Funktioniert wunderbar.
-
@Swordfish sagte in Sollen wir gemeinsam eine nette Double-linked- oder vielleicht sogar Skip List in C schreiben?:
Vorschlag für eine Node und eine List:
#include <stddef.h> typedef struct node_tag node_t; struct node_tag { void *data; node_t *next; node_t *prev; }; typedef struct list_tag { node_t *head; node_t *tail; size_t length; } list_t;
Einwände? Vorschläge?
Ich würde empfehlen die Liste als Ring-Liste zu implementieren. Damit spart man sich einiges an Code. Ein Nachteil ist dass dabei "next" beim letzten Element nicht NULL ist (genau so wie "prev" beim ersten). Die einfachere Implementierung wiegt das mMn. aber auf.
Weiters finde ich es in C fragwürdig einen "data" Zeiger in der Liste zu haben. Normalerweise verwendet man da intrusive Lists, macht also
struct MyData { MyListNode node; // ... };
und bastelt sich dann Makros mit denen man von der Node zur äusseren Struct kommt (falls die Node nicht das erste Element ist).
Als Beispiel kann man sich an der Implementierung im WDK orientieren, die haben das recht nett gemacht: https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/singly-and-doubly-linked-lists
-
Und wie sieht es aus?
-
@john-0 Nachdem hustbaer dieses vollständige Beispiel aus dem WDK gepostet hat habe ich das nicht weiterverfolgt.
-
Den Thread habe ich irgend wie übersehen.
Da ich keine Motivation hatte, den x-ten Aufguss in C zu machen. Das ganze einmal in Fortran. Das auskommentierte ist untested.
module doubly_linked_list use iso_fortran_env implicit none private public :: node, list, node_create type :: node class(*), allocatable, public :: value class (node), pointer, private :: prevptr => null(), nextptr => null() contains procedure, pass, non_overridable :: next => node_next procedure, pass, non_overridable :: prev => node_prev final :: node_destruct end type node type :: list class (node), pointer, private :: headptr => null(), tailptr => null() contains procedure, pass, non_overridable :: add_tail => list_add_tail procedure, pass, non_overridable :: add_head => list_add_head procedure, pass, non_overridable :: delete => list_delete !procedure, pass, non_overridable :: add_before_node => list_add_before_node !procedure, pass, non_overridable :: add_after_node => list_add_after_node procedure, pass, non_overridable :: head => list_head procedure, pass, non_overridable :: tail => list_tail final :: list_destruct end type list contains function node_create (data) result (new_node) class (node), pointer :: new_node class (*), intent(in) :: data integer :: error new_node => null() allocate (new_node, STAT=error) if (0 /= error) then write (error_unit,*) 'cannot allocate' stop end if allocate (new_node%value, STAT=error, SOURCE=data) if (0 /= error) then write (error_unit,*) 'cannot allocate' stop end if end function node_create function node_next (this) result (next_node) class (node), pointer :: next_node class (node) :: this next_node => this%nextptr end function node_next function node_prev (this) result (prev_node) class (node), pointer :: prev_node class (node) :: this prev_node => this%prevptr end function node_prev subroutine node_destruct (this) type (node) :: this this%nextptr => null() this%prevptr => null() deallocate(this%value) end subroutine node_destruct subroutine list_add_tail (this, node_pointer) class (list) :: this class (node), pointer, intent(in) :: node_pointer if (.NOT.associated(this%tailptr)) then this%tailptr => node_pointer this%headptr => node_pointer else node_pointer%prevptr => this%tailptr node_pointer%nextptr => null() this%tailptr => node_pointer end if end subroutine list_add_tail subroutine list_add_head (this, node_pointer) class (list) :: this class (node), pointer, intent (in) :: node_pointer if (.NOT.associated(this%headptr)) then this%headptr => node_pointer this%tailptr => node_pointer else node_pointer%nextptr => this%headptr node_pointer%prevptr => null() this%headptr => node_pointer end if end subroutine list_add_head subroutine list_delete (this) class (list) :: this type (node), pointer :: p1 => null(), p2 => null() p1 => this%headptr if (associated(p1)) then do p2 => p1%nextptr p1%nextptr => null() p1%prevptr => null() deallocate (p1) if (.NOT.associated(p2)) then exit end if p1 => p2 end do this%headptr => null() this%tailptr => null() end if end subroutine list_delete subroutine list_destruct (this) type (list) :: this call this%delete end subroutine list_destruct ! subroutine list_add_before_node (this, position, object) ! class (list) :: this ! class (node), pointer, intent(in) :: position, object ! object%prevptr => position%prevptr ! object%nextptr => position ! if (.NOT.associated(position%prevptr)) then ! this%headptr => object ! end if ! position%prevptr => object ! end subroutine list_add_before_node ! subroutine list_add_after_node (this, position, object) ! class (list) :: this ! class (node), pointer, intent(in) :: position, object ! object%nextptr => position%nextptr ! object%prevptr => position ! if (.NOT.associated(position%nextptr)) then ! this%tailptr => object ! end if ! position%nextptr => object ! end subroutine list_add_after_node function list_head (this) result (node_ptr) class (list) :: this class (node), pointer :: node_ptr node_ptr => this%headptr end function list_head function list_tail (this) result (node_ptr) class (list) :: this class (node), pointer :: node_ptr node_ptr => this%tailptr end function list_tail end module doubly_linked_list
Und ein kurzes Testprogramm was die Nuutzung des Moduls verdeutlicht bzw. es rudimentär testet.
program doubly_linked_list_test implicit none type :: my_data integer :: number integer :: serial end type my_data type :: my_data2 integer:: code end type my_data2 call main contains subroutine main use doubly_linked_list use iso_fortran_env implicit none type (my_data) :: data1, data2 type (my_data2) :: data3 type (list) :: my_list type (node), pointer :: a_node data1%number = 1 data1%serial = 1 data2%number = 1 data2%serial = 2 data3%code = -5 a_node => node_create (data1) call my_list%add_head (a_node) a_node => null() a_node => node_create (data2) call my_list%add_head (a_node) a_node => null() a_node => node_create (data3) call my_list%add_head (a_node) a_node => my_list%head () a_node => null() data1%serial = 3 a_node => node_create (data1) call my_list%add_tail (a_node) end subroutine main end program doubly_linked_list_test