I am trying to implement a singly linked list. What is the most optimal way of implementing a RemoveBefore(ListNode)
method?
I have done this:
- Check if node being removed is the head. If so, return.
- Check if there are two nodes and node being removed is the tail. Then set head and tail as the same. Old tail will be automatically disposed.
- If there are more than 2 nodes and the above 2 conditions are not satisfied track current node, child node and grandchild node.
- If grandchild node is the requested node the make current node point to grandchild node. Child node will be disposed.
namespace DataStructures.Lists
{
public interface ISinglyLinkedNode<T> where T : IComparable<T>
{
T Data { get; set; }
ISinglyLinkedNode<T> Next { get; set; }
}
public interface ISinglyLinkedList<T> where T : IComparable<T>
{
ISinglyLinkedNode<T> Head { get; }
ISinglyLinkedNode<T> Tail { get; }
void Add(ISinglyLinkedNode<T> Data);
void AddHead(ISinglyLinkedNode<T> Data);
void AddTail(ISinglyLinkedNode<T> Data);
void RemoveHead();
void RemoveTail();
void AddAt(int Index, ISinglyLinkedNode<T> Data);
void InsertAfter(ISinglyLinkedNode<T> Node, T Data);
void InsertBefore(ISinglyLinkedNode<T> Node, T Data);
ISinglyLinkedNode<T> GetAt(int Index);
void RemoveAt(int Index);
void RemoveAfter(ISinglyLinkedNode<T> Node);
void RemoveBefore(ISinglyLinkedNode<T> Node);
void Remove(ISinglyLinkedNode<T> Node);
void ReverseList();
}
public interface IPrintLinkedList
{
void PrintLinkedList();
}
/// <summary>
/// Definition of a Singly linked node. This node has a data slot and a single pointer slot pointing to the next
/// singly linked node.
/// </summary>
/// <typeparam name="T"></typeparam>
public class SinglyLinkedNode<T> : IDisposable, IComparable<SinglyLinkedNode<T>>, ISinglyLinkedNode<T> where T : IComparable<T>
{
private T _data;
private ISinglyLinkedNode<T> _next;
public SinglyLinkedNode()
{
_data = default(T);
_next = null;
}
public SinglyLinkedNode(T data)
{
_data = data;
_next = null;
}
public T Data { get { return _data; } set { _data = value; } }
public ISinglyLinkedNode<T> Next { get { return _next; } set { _next = value; } }
int IComparable<SinglyLinkedNode<T>>.CompareTo(SinglyLinkedNode<T> other)
{
if (other == null)
return -1;
return this.Data.CompareTo(other.Data);
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
IDisposable disposable = _data as IDisposable;
if (disposable != null)
disposable.Dispose();
}
disposedValue = true;
}
}
public void Dispose()
{
Dispose(true);
}
#endregion
}
/// <summary>
/// Implements ISinglyLinkedList and defines a singly linked list
/// </summary>
/// <typeparam name="T">Template type</typeparam>
public class SinglyLinkedList<T> : ISinglyLinkedList<T>, IPrintLinkedList, IEnumerable<T> where T : IComparable<T>
{
private ISinglyLinkedNode<T> _head;
private ISinglyLinkedNode<T> _tail;
public SinglyLinkedList()
{
_head = null;
_tail = null;
}
#region ISinglyLinkedList implementation
ISinglyLinkedNode<T> ISinglyLinkedList<T>.Head
{
get
{
return _head;
}
}
ISinglyLinkedNode<T> ISinglyLinkedList<T>.Tail
{
get
{
return _tail;
}
}
void ISinglyLinkedList<T>.Add(ISinglyLinkedNode<T> Data)
{
(this as ISinglyLinkedList<T>).AddHead(Data);
}
void ISinglyLinkedList<T>.AddAt(int Index, ISinglyLinkedNode<T> Data)
{
throw new NotImplementedException();
}
void ISinglyLinkedList<T>.AddHead(ISinglyLinkedNode<T> Data)
{
Data.Next = _head;
_head = Data;
}
void ISinglyLinkedList<T>.AddTail(ISinglyLinkedNode<T> Data)
{
_tail.Next = Data;
_tail = Data;
}
ISinglyLinkedNode<T> ISinglyLinkedList<T>.GetAt(int Index)
{
if (Index < 0)
return new SinglyLinkedNode<T>();
ISinglyLinkedNode<T> Temp = _head;
for (int i = 0; i < Index - 1; i++)
{
Temp = Temp.Next;
}
return Temp;
}
void ISinglyLinkedList<T>.Remove(ISinglyLinkedNode<T> Node)
{
//empty linked list
if (_head == null)
return;
for (ISinglyLinkedNode<T> current = _head, prev = null; current != null; prev = current, current = current.Next)
{
if (object.Equals(current.Data, Node.Data))
{
prev.Next = current.Next;
break;
}
}
}
void ISinglyLinkedList<T>.RemoveAfter(ISinglyLinkedNode<T> Node)
{
Node.Next = Node.Next.Next;
}
void ISinglyLinkedList<T>.RemoveAt(int Index)
{
(this as ISinglyLinkedList<T>).Remove((this as ISinglyLinkedList<T>).GetAt(Index));
}
void ISinglyLinkedList<T>.RemoveBefore(ISinglyLinkedNode<T> Node)
{
//cannot remove before head
if (object.Equals(Node.Data, _head.Data))
return;
//if we need to remove the node before tail then make head and tail the same
if (object.Equals(Node.Data, _tail.Data))
{
_head = _tail;
(_tail as IDisposable).Dispose();
}
for (ISinglyLinkedNode<T> currentnode = _head, child = null, grandchild = null; currentnode != null; currentnode = currentnode.Next)
{
child = currentnode.Next;
grandchild = child.Next;
if (object.Equals(Node.Data, grandchild.Data))
{
currentnode.Next = grandchild;
break;
}
}
}
void ISinglyLinkedList<T>.RemoveHead()
{
lock (_head)
{
lock (_head.Next)
{
_head = _head.Next;
}
}
}
void ISinglyLinkedList<T>.RemoveTail()
{
ISinglyLinkedNode<T> currentnode = _head;
//check if there is only one node. Remove head then
if (_head.Next == null)
{
_head = null;
return;
}
//if there are only two nodes set heads next to null to remove tail
if (_head.Next != null && _head.Next.Next == null)
{
_head.Next = null;
return;
}
for (ISinglyLinkedNode<T> current = _head, child = current.Next, grandchild = child.Next; current != null; current = current.Next)
{
}
}
#endregion
#region IEnumerable implementation
IEnumerator IEnumerable.GetEnumerator()
{
return new SinglyLinkedListEnumerator<T>();
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return new SinglyLinkedListEnumerator<T>();
}
#endregion
void IPrintLinkedList.PrintLinkedList()
{
PrintNode(_head);
}
void PrintNode(ISinglyLinkedNode<T> Node)
{
if (Node == null)
{
Debug.Write(" | " + "NULL" + " | " + "\n");
return;
}
Debug.Write(" | " + Node.Data + " | " + "-->");
PrintNode(Node.Next);
}
void ISinglyLinkedList<T>.InsertAfter(ISinglyLinkedNode<T> Node, T Data)
{
throw new NotImplementedException();
}
void ISinglyLinkedList<T>.InsertBefore(ISinglyLinkedNode<T> Node, T Data)
{
throw new NotImplementedException();
}
void ISinglyLinkedList<T>.ReverseList()
{
// < __ < __ < __ __: reversedPart: head
// (__)__ __ __
//head : current: > > >
ISinglyLinkedNode<T> reversedPart = null;
ISinglyLinkedNode<T> current = _head;
while (current != null)
{
ISinglyLinkedNode<T> next = current.Next;
current.Next = reversedPart;
reversedPart = current;
current = next;
}
_head = reversedPart;
}
internal class SinglyLinkedListEnumerator<T> : IEnumerator<T>
{
public T Current
{
get
{
throw new NotImplementedException();
}
}
object IEnumerator.Current
{
get
{
throw new NotImplementedException();
}
}
public void Dispose()
{
throw new NotImplementedException();
}
public bool MoveNext()
{
throw new NotImplementedException();
}
public void Reset()
{
throw new NotImplementedException();
}
}
}
}
-
\$\begingroup\$ Don't you like the one that .net provides? \$\endgroup\$t3chb0t– t3chb0t2016年11月17日 17:22:41 +00:00Commented Nov 17, 2016 at 17:22
-
\$\begingroup\$ @t3chb0t , I am trying to create my own C# singly linked list implementation as an academic exercise. \$\endgroup\$Pradeepl– Pradeepl2016年11月17日 17:31:20 +00:00Commented Nov 17, 2016 at 17:31
-
\$\begingroup\$ I think it would be better if you posted the complete code, not just a single method. \$\endgroup\$t3chb0t– t3chb0t2016年11月17日 17:34:21 +00:00Commented Nov 17, 2016 at 17:34
-
\$\begingroup\$ Sure the complete code is at github.com/PradeepLoganathan/EngineeringCore/blob/master/… \$\endgroup\$Pradeepl– Pradeepl2016年11月17日 17:38:26 +00:00Commented Nov 17, 2016 at 17:38
-
1\$\begingroup\$ You like new lines, don't you? There are so many of them. \$\endgroup\$t3chb0t– t3chb0t2016年11月17日 17:57:36 +00:00Commented Nov 17, 2016 at 17:57
2 Answers 2
Simplifying RemoveBefore
Don't over-complicate things with unnecessary special cases. You already have a loop for looping through all the nodes. Just use that, with a few changes:
Instead of keeping track of decendents (child
and grandchild
) of the current node (currentnode
), keep track of its ancestors (parent
and grandparent
). Then when currentnode
matches the input node (Node
):
parent
is the node to be removedgrandparent
needs to have itsnext
reference updatedcurrentnode
is whatgrandparent.next
should be set to
There are a couple special cases:
parent
isnull
=>Node
has no parent (is_head
) => nothing needs to be removedgrandparent
isnull
=>parent
is_head
=>currentnode
becomes the new_head
- We fall out of the loop without finding
Node
=> nothing needs to be done
Here's how this translates to code:
void ISinglyLinkedList<T>.RemoveBefore(ISinglyLinkedNode<T> Node) {
for (ISinglyLinkedNode<T> currentnode = _head, parent = null, grandparent = null; currentnode != null; currentnode = currentnode.Next) {
if (object.Equals(Node.Data, grandchild.Data)) {
if(parent == null) {
// currentnode is _head => do nothing
break;
}
else if(grandparent == null) {
// parent is _head => currentnode becomes new _head
_head = currentnode
break;
}
else {
// We are removing parent => grandparent.Next becomes currentnode
grandparent.Next = currentnode
break;
}
}
// Update parent and grandparent for next iteration
grandparent = parent;
parent = currentnode;
}
}
This is a good start, but the logic is still a little more complex than it needs to be. For one thing, since every block ends with a break
, we can move those outside. But we also don't really need to do anything when parent
is null
. Here's how I would probably write the logic:
// If parent is null, we don't need to do anything
if(parent != null) {
if(grandparent == null) {
// parent is _head => currentnode becomes _head
_head = currentnode;
}
else {
// We are removing parent => grandparent.Next becomes currentnode
grandparent.Next = currentnode;
}
}
// In any case, we're done!
break;
Other Suggestions
Here are a few other suggestions:
- Consider having
RemoveBefore
return itself at the end instead of being avoid
function. This allows users to chain method invocations together in a "fluid" pattern. For example:list.RemoveBefore(a).RemoveBefore(b)
. The same goes for other methods as well. You are comparing nodes for equality based on their
Data
properties, butRemoveBefore
takes a node as a parameter. This could be confusing, because the user when the user passes in one node, the code could match an entirely different node (i.e., if the same data is stored in two different locations in the list). I suggest doing one of the following:- Compare nodes based on identity (i.e., pointer equality). This will allow users to remove nodes at precise locations, regardless of the data stored in the list.
- Instead of
RemoveBefore
taking a node parameter, have it take the data stored in the node instead. This way, it's clear that comparison is based on the data, not the position of a particular node.
Of course, you could implement both ideas in separate methods. The same also goes for other methods as well.
Reinventing the wheel is a useful learning exercise. However, it's worth considering what has gone before you and the environment that you're performing the exercise in. .Net provides some common interfaces that generic collection classes can implement to help them fit into the existing landscape. Consider implementing some of ICollection<T>, IEnumerable<T>, IEnumerable, ICollection, IReadOnlyCollection<T>
.
This will help your list to fit in with the existing framework (so that for example collection extension methods and linq support are more straightforward). It will also help you decide on some of the methods that your class should consider implementing.
I think @Nathan has covered most points around your RemoveBefore
method, which is what you were after the review for, however I'd consider throwing an exception if the provided item isn't in the list. There's also quite a lot that could be said about the rest of your code if you're looking for a full review (such as the mismatch between the locking in RemoveHead
and the rest of the code).
Explore related questions
See similar questions with these tags.