Reputation: 1324
I'm writing a LinkedList cause an exercise and I'm going much further cause I'm lovin'it. However, I'm stuck in this:
I'ld remove an element from list by value, like C#.
I've this code:
int M_List_RemoveItemFromList(List list, void* value)
{
NodeList lastNode = NULL;
NodeList currentNode = *(list->nodes);
while (currentNode)
{
// 1. if (memcmp(value, currentNode->data, list->elementSize) == 0)
// 2. if (value == currentNode->data)
{
if (lastNode)
{
lastNode->next = currentNode->next;
}
else {
*(list->nodes) = currentNode->next;
}
currentNode->next = NULL;
--list->length;
return 0;
}
lastNode = currentNode;
currentNode = currentNode->next;
}
return 1;
}
I commented two rows cause they works in differenct contexts.
Which is the right way? How C# does it?
Tell me if you need further informations.
List list = NewList(char);
char* stringa = "Test 001";
AppendList(list, stringa);
AppendList(list, "Test 002");
AppendList(list, "Test 003");
AppendList(list, "Test 004");
AppendList(list, "Test 005");
AppendList(list, "Test 006");
PrintStringList(list);
PrintNewSection();
M_List_RemoveItemFromList(list, "Test 004"); // It removes the first element with (1. commented row) and the right one with (2. commented row)
M_List_RemoveItemFromList(list, "Test 001"); // It removes the first element with (1. commented row) and the right one with (2. commented row)
M_List_RemoveItemFromList(list, "Test 006"); // It removes the first element with (1. commented row) and the right one with (2. commented row)
PrintStringList(list);
PrintNewSection();
List list2 = NewList(int);
int a = 101;
int b = 2;
int c = 2;
AppendList(list2, &a);
AppendList(list2, &b);
M_List_RemoveItemFromList(list2, &c); // it doesn't remove anything with 2. and the right one with 1.
PrintIntList(list2);
NodeList M_List_Append(List list, void* value)
{
NodeList element = malloc(sizeof(T_NodeList) * list->elementSize);
if (!element) return NULL;
element->data = value;
NodeList tail = M_List_GetTail(list);
if (!tail)
{
*(list->nodes) = element;
}
else
{
tail->next = element;
}
element->next = NULL;
++list->length;
if (list->length > list->capacity - 2)
M_List_IncreaseSize(list);
return element;
}
and here it's the List structs:
typedef struct S_NodeList
{
void* data;
struct S_NodeList* next;
} T_NodeList;
#define NodeList T_NodeList*
#define HeadList NodeList*
typedef struct S_List
{
HeadList nodes;
size_t elementSize;
size_t capacity;
size_t length;
} T_List;
#define List T_List*
Upvotes: 0
Views: 229
Reputation: 180181
This example code ...
List list = NewList(char);
... suggests that NewList
is implemented as a macro. Given that List
is apparently a structure type with a member named elementSize
, and the macro consumes only a type name, the macro must compute elementSize
from the type name (or else leave it uninitialized).
But how is that macro supposed to know from type char
how long the actual strings presented as members will be? And do you in fact intend that when the members are strings, they must all be the same length? Because that's the implication of comparing elements for equality via
memcmp(value, currentNode->data, list->elementSize)
I suppose that NewList
is just using sizeof(type)
to set elementSize
, but sizeof(char)
is 1, and with that elementSize
, the memcmp
will compare only the first character of each string. (So try test strings "a"
, "b"
, and "c"
.) That is, the problem with approach (1) for strings is that you are specifying the wrong type. For the specific test strings you are presenting, you could probably use
List list = NewList(char[9]);
, as all the list elements are 9-element arrays of char
.
On the other hand, your approach (2) compares the values of the pointers stored in the list. That is semantically quite different, but not necessarily wrong. It evaluates whether the specified pointer points to the same object that the one stored in the list does. You're (un)lucking out a bit that it works at all for your string test -- your compiler is evidently merging identical string literals, which it is not required to do.
Which is the right way? How C# does it?
Neither is the way C# does it, at least if by "C#" you mean its List<T>
class. According to that class's documentation:
The
List<T>
class uses both an equality comparer and an ordering comparer.
Methods such as
Contains
,IndexOf
,LastIndexOf
, andRemove
use an equality comparer for the list elements. The default equality comparer for typeT
is determined as follows. If typeT
implements theIEquatable<T>
generic interface, then the equalitycomparer
is theEquals(T)
method of that interface; otherwise, the default equality comparer isObject.Equals(Object)
.Methods such as
BinarySearch
andSort
use an ordering comparer for the list elements. [...]
Other implementations of IList<T>
may do things differently. In fact, its docs (inherited from ICollection<T>
) explicitly say "Implementations can vary in how they determine equality of objects".
The closest equivalent in C would be to give your List
struct a member that points to a function to use for comparing elements and test values for equality. For example,
/*
* returns less than zero, zero, or greater than zero, corresponding to
* whether o1 is to be considered less than, equal to, or greater than o2
*/
typedef int compare_func(const void *o1, const void *o2);
typedef struct S_List {
HeadList nodes;
// size_t elementSize; // probably not needed
size_t capacity;
size_t length;
compare_func *compare;
} T_List;
The user would need to specify a function appropriate to the element type. Examples of such functions might include:
int compare_strings(const void *o1, const void *o2) {
return strcmp((const char *) o1, (const char *) o2);
}
int compare_ints(const void *o1, const void *o2) {
int i1 = *(int *)o1;
int i2 = *(int *)o1;
if (i1 < i2) return -1;
else if (i1 > i2) return 1;
else return 0;
}
Functions such as your M_List_RemoveItemFromList()
that need to compare objects for equality in the sense appropriate for the list would use the specified function to do so. An equality comparison would then look like:
if (list->compare(value, currentNode->data) == 0) // ...
Upvotes: 1
Reputation: 1324
Okay, it's tricky, but I'm going to answer this after @akatz comment.
I defined a macro:
#define List_RemoveValueFromList(list, value) \
_Generic((value), \
int: M_List_RemoveIntFromList, \
char*: M_List_RemoveStringFromList) \
(list, value);
And then I created two functions with different comparison and a function that remove (in order to avoid duplication code):
void M_List_Remove(List list, NodeList lastNode, NodeList currentNode)
{
if (lastNode)
{
lastNode->next = currentNode->next;
}
else {
*(list->nodes) = currentNode->next;
}
currentNode->next = NULL;
--list->length;
}
int M_List_RemoveIntFromList(List list, int value)
{
NodeList lastNode = NULL;
NodeList currentNode = *(list->nodes);
while (currentNode)
{
if (value == *((int*)currentNode->data))
//if (value == currentNode->data)
{
M_List_Remove(list, lastNode, currentNode);
return 0;
}
lastNode = currentNode;
currentNode = currentNode->next;
}
return 1;
}
int M_List_RemoveStringFromList(List list, char* value)
{
NodeList lastNode = NULL;
NodeList currentNode = *(list->nodes);
while (currentNode)
{
if (strcmp(value, (char*)(currentNode->data)) == 0)
{
M_List_Remove(list, lastNode, currentNode);
return 0;
}
lastNode = currentNode;
currentNode = currentNode->next;
}
return 1;
}
Any improvement is absolutly accepted!
Upvotes: 0