The built-in implementation of the LinkedList is a doubly-linked circular list.
I see the following implementation of the Clear method:
public void Clear() {
LinkedListNode<T> current = head;
while (current != null ) {
LinkedListNode<T> temp = current;
current = current.Next;
temp.Invalidate();
}
head = null;
count = 0;
version++;
}
So, this clearing method apparently works for O(N).
public sealed class LinkedListNode<T> {
internal LinkedList<T> list;
internal LinkedListNode<T> next;
internal LinkedListNode<T> prev;
internal T item;
public LinkedListNode( T value) {
this.item = value;
}
internal LinkedListNode(LinkedList<T> list, T value) {
this.list = list;
this.item = value;
}
public LinkedList<T> List {
get { return list;}
}
public LinkedListNode<T> Next {
get { return next == null || next == list.head? null: next;}
}
public LinkedListNode<T> Previous {
get { return prev == null || this == list.head? null: prev;}
}
public T Value {
get { return item;}
set { item = value;}
}
internal void Invalidate() {
list = null;
next = null;
prev = null;
}
}
I wonder why can't we just assign null to head instead of nulling all the references? As far as I can see, assigning null to the head will result in breaking the circle and all the nodes will be collected by GC pretty soon. Or am I missing something?
This is done so that once the linked list is cleared, anything holding references to its old nodes will throw errors if it tries to navigate the list.
If the old links weren't set to null, the old linked list would remain accessible if anything had a reference to any of its nodes, which would (a) hide the problem because the code would continue to apparently work and (b) keep alive the node objects in memory.
Related
The source code of Enumerator is:
public struct Enumerator : IEnumerator<T>, System.Collections.IEnumerator {
private List<T> list;
private int index;
private int version;
private T current;
...
public bool MoveNext() {
List<T> localList = list; <--------------Q1
if (version == localList._version && ((uint)index < (uint)localList._size)) {
current = localList._items[index];
index++;
return true;
}
return MoveNextRare();
}
private bool MoveNextRare() {
if (version != list._version) {
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
index = list._size + 1; <-----------------Q2
current = default(T);
return false;
}
void System.Collections.IEnumerator.Reset() {
if (version != list._version) {
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
index = 0;
current = default(T);
}
...
}
I have some questions on this iterator pattern:
Q1-Why MoveNext method need to define a localList, can't it just use the private field list directly since List<T> is already a reference type, why need to create an alias for it?
Q2- MoveNextRare method will invoke when the index is out of range of the last element in the list, so what's the point to increment it, why not just leave it untouched, because when Reset() calls, index will be set to 0 anyway?
For the first question I don't have any answer, maybe it just an relic from some previous implementation, maybe it somehow improves performance (though I would wonder how and why, so my bet is on the first guess). Also in the Core implementation list field is marked as readonly.
As for the second one - it has nothing to do with Reset, but with System.Collections.IEnumerator.Current implementation:
Object System.Collections.IEnumerator.Current {
get {
if( index == 0 || index == list._size + 1) { // check second comparasion
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
}
return Current;
}
My issue is basically in order of the functions/property called. I have a custom linked list, circular one. So I made a custom enumenator and all. The issue is, the foreach cycle actually calls the MoveNext() method of enumerator first, therefore moving from the actual first Node of the cycle to the second node, which is kind of bad if you want to have your items in actual order.
Question is, am I doing something wrong, and if not, how to compensate for this?
Code of Enumerator is simple as can be. This, basically:
class EnumeratorLinkedList : IEnumerator<Node>
{
private Node current;
private Node first;
private bool didWeMove;
public EnumeratorSpojovySeznam(Node current)
{
this.current = current;
this.first = current;
didWeMove = false;
}
public Node Current => current;
object System.Collections.IEnumerator.Current => Current;
public bool MoveNext()
{
if ((didWeMove == true && current == first)) return false;
current = current.Next;
didWeMove = true;
return true;
}
public void Dispose()
{
}
public void Reset()
{
throw new NotImplementedException();
}
}
Do you consider using C# iterators to implement enumerator for your circular LinkedList? If you do, it is very simple. Iterators provide convenient and easy way to implement enumerators.
Here is how enumerator can be implemented for your circular LinkedList:
class LinkedList : IEnumerable<Node>
{
private Node first;
public IEnumerator<Node> GetEnumerator()
{
// Check if LinkedList is empty.
// If it is empty we immediately break enumeration.
if (first == null)
yield break;
// Here goes logic for enumerating circular LinkedList.
Node current = first;
do
{
yield return current;
current = current.Next;
} while (current != first);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
As you can see an implementation using iterator is more straightforward and intuitive.
Here is complete sample that demonstrates usage of iterators.
I realize that in most cases, it would be beneficial to have a homogeneous, type-safe stack, but I'm curious if this is possible, as I was asked to create a Stack for an exercise which accepts all types.
So I currently have a generic linked-list Stack class, and I'm wondering if there's any way to make the Node class generic so that the Next node can be of a different type. I know I can make the stack accept all types by just making the all the type parameters object instead of using a generic, but I was warned of the performance implications of boxing & unboxing of value types / upcasting all of it's elements to the base object class.
So I went ahead an implemented it as Generic, but was unable to find a solution that allowed different types for each Node. If I wanted to avoid the object approach, is there a possible solution? Even if it takes a different approach altogether, it doesn't necessarily have to fit this approach.
I hope this makes sense! At any rate, here's the implementation I currently have. Please let me know what you think.
Node.cs
namespace DataStructures
{
class Node<T>
{
public T Value { get; private set; }
public Node<T> Next { get; set; }
public Node(T value)
{
Value = value;
}
}
}
Stack.cs
namespace DataStructures
{
class Stack<T>
{
private Node<T> root;
public int Size { get; private set; } = 0;
public Stack()
{
}
public Stack(params T[] values)
{
foreach (var value in values)
Push(value);
}
public void Push(T value)
{
if (value == null)
throw new ArgumentNullException("value", "You cannot push a null value onto the stack");
var newNode = new Node<T>(value);
if (root != null)
newNode.Next = root;
root = newNode;
Size++;
}
public dynamic Peek()
{
if (root == null)
return null;
return root.Value;
}
public T Pop()
{
if (root == null)
throw new InvalidOperationException("The stack is empty, nothing to pop");
Size--;
var popped = root.Value;
root = root.Next;
return popped;
}
public bool TryPop(out dynamic popped)
{
if (root == null)
{
popped = null;
return false;
}
else
{
popped = Pop();
return true;
}
}
public bool IsEmpty() => root == null ? true : false;
public void Clear()
{
root = null;
}
}
}
I tried to implement my linked list below, It just that when I used my linked list, it seems that it's inserting a null entry in the middle. Anyone could take a look? The problem could be in the add end method.
LinkedList<T>
public class LinkedList<T> : IEnumerable<T>
{
public Node<T> Head { get; set; }
public int Count { get; set; }
public LinkedList()
{
Head = new Node<T>();
}
public void AddStart(T data)
{
if (Head == null)
{
Head = new Node<T> {Value = data};
}
else
{
var newNode = new Node<T> {Value = data, Next = Head};
Head = newNode;
}
Count++;
}
public void AddEnd(T data)
{
var newNode = new Node<T> { Value = data, Next = null};
var current = Head;
if (Head == null)
{
Head = newNode;
}
else
{
while (current.Next != null)
{
current = current.Next;
}
current.Next = newNode;
}
}
public IEnumerator<T> GetEnumerator()
{
Node<T> current = Head;
while (current != null)
{
yield return current.Value;
current = current.Next;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Usage:
LinkedList<string> strings = new LinkedList<string>();
strings.AddStart("a");
strings.AddStart("b");
strings.AddStart("c");
strings.AddEnd("a");
strings.AddEnd("e");
strings.AddEnd("d");
Result: (Notice the null in the middle)
c
b
a
a
e
d
Usage:
LinkedList<string> strings = new LinkedList<string>();
strings.AddStart("a");
strings.AddStart("b");
strings.AddStart("c");
strings.AddEnd("a");
strings.AddEnd("e");
strings.AddEnd("d");
strings.AddStart("a");
strings.AddEnd("b");
strings.AddStart("a");
strings.AddStart("b");
strings.AddStart("c");
strings.AddEnd("a");
strings.AddEnd("e");
strings.AddEnd("d");
Result: (Notice the null in the middle)
c
b
a
a
c
b
a
a
e
d
b
a
e
d
You are creating an empty Node, when you initialize your LinkedList:
public LinkedList()
{
Head = new Node<T>();
}
At this point you essentially have a node without a value. This is displayed as an empty value.
Everytime you call AddStart, you insert items before hand. When calling AddEnd you insert items at the end. Since you call both methods equally, it appears that the initial node is in the middle.
You can resolve this by changing your constructor to this:
public LinkedList()
{
Head = null;
}
Or by removing the constructor alltogether. In both methods you are checking for a Head node with a value, so you do not need to initialize it without a value.
if (Head == null)
{
Head = new Node<T> {Value = data};
}
In your current state, you are checking if Head is null. Head is of Type Node, which, if it is a class, cannot be null, if you initialize it in your constrcutor (Head = new Node<T>(); //not null any longer). So you need to make up your mind on how you want to solve it.
Personally I'd just write a unit test to assert my logic and implementation is correct. If you assume that Head is null after initialisation, test for it:
[Fact]
public void AssertHeadIsNull()
{
var list = new LinkedList<int>();
Assert.Null(list.Head);
}
In your case, the test would have failed, and you could have backtracked the issue to your constructor.
class ListNode
{
public object Data { get; private set; }
public ListNode Next { get; set; }
public ListNode(object Element)
{
Data = Element;
}
public ListNode(object Element, ListNode NextNode)
{
Data = Element;
Next = NextNode;
}
public ListNode()
{
}
}
class LinkedList
{
ListNode first;
ListNode last;
public LinkedList()
{
first = null;
last = null;
}
public ListNode Find(object After)
{
ListNode current = new ListNode();
current= first;
while (current.Data != After)
current = current.Next;
return current;
}
public void Add(object newItem, object After)
{
ListNode current=new ListNode();
ListNode newNode=new ListNode();
current = Find(After);
newNode.Next = current.Next;
current.Next = newNode;
}
public void InsertAtFront(object Element)
{
if (IsEmpty())
{
first = last = new ListNode(Element);
}
else
{
first = new ListNode(Element,first);
}
}
bool IsEmpty()
{
return first == null;
}
public void Display()
{
ListNode current = first;
while (current!=null)
{
Console.WriteLine(current.Data);
current = current.Next;
}
}
}
I implement Find method for Add After specific element, but when I debug it showing me the object reference not set to an instance of an object exception. Please point out my mistake in Find method or with Add After method. thanks
current= first;
while (current.Data != After)
can result in a potential null reference issue. first can still be set to null from the constructor initialisation which would mean that current = null, which'd then result in null.Data which would throw a null reference exception.
This would fix the null reference issue in Find()
while (current != null && current.Data != After)
Fixing this would result in null being returned which would still result in issues in Add
current = Find(After);
newNode.Next = current.Next;
current.Next = newNode;
In this context, LinkedList is first initialised, current = Find(After) would mean current = null, causing another null reference exceptions on the next two lines.
public void Add(object newItem, object After)
{
if (IsEmpty())
{
InsertAtFront(newItem);
return;
}
ListNode newNode = new ListNode();
ListNode current = Find(After);
newNode.Next = current.Next;
current.Next = newNode;
}
This will fix both the Add and the Find methods to be usable in the form of:
LinkedList list = new LinkedList();
list.InsertAtFront("test");
list.Find(list.first.Data);
list.Add("test2", ll.first.Data);
This will make it workable, but I would however highly recommend reading into the implementation of linked lists or using one of the system collections as this implementation has quite a few potential issues.
Problem is here
while (current.Data != After)
current = current.Next;
When there is no After in your list you will eventually get current.Next equal to null
You need to check if current.Next is not null
while (current.Next != null && current.Data != After)
current = current.Next;
you should also fix your add logic (if you want to add elements into empty list)
public void Add(object newItem, object After)
{
if(IsEmpty())
{
InsertAtFront(newItem);
return;
}
ListNode newNode=new ListNode();
newNode.Data = newItem;
ListNode current = Find(After);
newNode.Next = current.Next;
current.Next = newNode;
}