I am implementing an undo/redo buffer with generic LinkedList.
In this state:
[Top]
state4 (undone)
state3 (undone)
state2 <-- current state
state1
[bottom]
When I do a Push, I would like to remove all states after the current one, and push the new one.
My current bypass is to do while (currentState != list.last), list.removeLast();
but it sucks
LinkedList just support Remove, RemoveFirst & removeLast...
I would like something like RemoveAllNodesAfter(LinkedListNode ...) ?
How can I code that nicely, without iterating throught all nodes ? Maybe with extensions ?...
I can't see anything in the standard LinkedList<T>
which lets you do this. You could look in PowerCollections and the C5 collections if you want - or just roll your own LinkedList
type. It's one of the simpler collections to implement, especially if you can add functionality in a "just in time" manner.
If I were to implement this myself, I would chose a different way to implement this.
Instead of the .RemoveAllNodesAfter(node)
method, I would opt to make a .SplitAfter(node)
method that returned a new linked list starting with the next node after node
. This would make a handier tool than just being able to chop off the tail. If you wanted your RemoveAllNodesAfter
method, it would just have to call the SplitAfter
method internally and discard the result.
Naive implementation:
public LinkedList<T> SplitAfter(Node node)
{
Node nextNode = node.Next;
// break the chain
node.Next = null;
nextNode.Previous = null;
return new LinkedList<T>(nextNode);
}
public void RemoveAllNodesAfter(Node node)
{
SplitAfter(node);
}
Linked List (especially the singly linked list) is one of the most basic fundamental collection structures. I'm certain that you could probably implement it (and add the behavior your need) with little effort.
In reality, you don't actually need a collection class to manage the list. You could manage the nodes without a collection class.
public class SingleLinkedListNode<T>
{
private readonly T value;
private SingleLinkedListNode<T> next;
public SingleLinkedListNode(T value, SingleLinkedListNode<T> next)
{
this.value = value;
}
public SingleLinkedListNode(T value, SingleLinkedListNode<T> next)
: this(value)
{
this.next = next;
}
public SingleLinkedListNode<T> Next
{
get { return next; }
set { next = value; }
}
public T Value
{
get { return value; }
}
}
If you're interested in a possible implementation, however, here's a somewhat simple SingleLinkedList implementation.
public class SingleLinkedList<T>
{
private SingleLinkedListNode<T> head;
private SingleLinkedListNode<T> tail;
public SingleLinkedListNode<T> Head
{
get { return head; }
set { head = value; }
}
public IEnumerable<SingleLinkedListNode<T>> Nodes
{
get
{
SingleLinkedListNode<T> current = head;
while (current != null)
{
yield return current;
current = current.Next;
}
}
}
public SingleLinkedListNode<T> AddToTail(T value)
{
if (head == null) return createNewHead(value);
if (tail == null) tail = findTail();
SingleLinkedListNode<T> newNode = new SingleLinkedListNode<T>(value, null);
tail.Next = newNode;
return newNode;
}
public SingleLinkedListNode<T> InsertAtHead(T value)
{
if (head == null) return createNewHead(value);
SingleLinkedListNode<T> oldHead = Head;
SingleLinkedListNode<T> newNode = new SingleLinkedListNode<T>(value, oldHead);
head = newNode;
return newNode;
}
public SingleLinkedListNode<T> InsertBefore(T value, SingleLinkedListNode<T> toInsertBefore)
{
if (head == null) throw new InvalidOperationException("you cannot insert on an empty list.");
if (head == toInsertBefore) return InsertAtHead(value);
SingleLinkedListNode<T> nodeBefore = findNodeBefore(toInsertBefore);
SingleLinkedListNode<T> toInsert = new SingleLinkedListNode<T>(value, toInsertBefore);
nodeBefore.Next = toInsert;
return toInsert;
}
public SingleLinkedListNode<T> AppendAfter(T value, SingleLinkedListNode<T> toAppendAfter)
{
SingleLinkedListNode<T> newNode = new SingleLinkedListNode<T>(value, toAppendAfter.Next);
toAppendAfter.Next = newNode;
return newNode;
}
public void TruncateBefore(SingleLinkedListNode<T> toTruncateBefore)
{
if (head == toTruncateBefore)
{
head = null;
tail = null;
return;
}
SingleLinkedListNode<T> nodeBefore = findNodeBefore(toTruncateBefore);
if (nodeBefore != null) nodeBefore.Next = null;
}
public void TruncateAfter(SingleLinkedListNode<T> toTruncateAfter)
{
toTruncateAfter.Next = null;
}
private SingleLinkedListNode<T> createNewHead(T value)
{
SingleLinkedListNode<T> newNode = new SingleLinkedListNode<T>(value, null);
head = newNode;
tail = newNode;
return newNode;
}
private SingleLinkedListNode<T> findTail()
{
if (head == null) return null;
SingleLinkedListNode<T> current = head;
while (current.Next != null)
{
current = current.Next;
}
return current;
}
private SingleLinkedListNode<T> findNodeBefore(SingleLinkedListNode<T> nodeToFindNodeBefore)
{
SingleLinkedListNode<T> current = head;
while (current != null)
{
if (current.Next != null && current.Next == nodeToFindNodeBefore) return current;
current = current.Next;
}
return null;
}
}
Now you can do this:
public static void Main(string[] args)
{
SingleLinkedList<string> list = new SingleLinkedList<string>();
list.InsertAtHead("state4");
list.AddToTail("state3");
list.AddToTail("state2");
list.AddToTail("state1");
SingleLinkedListNode<string> current = null;
foreach (SingleLinkedListNode<string> node in list.Nodes)
{
if (node.Value != "state2") continue;
current = node;
break;
}
if (current != null) list.TruncateAfter(current);
}
The thing is depending on your situation, it's not much better than this:
public static void Main(string[] args)
{
SingleLinkedListNode<string> first =
new SingleLinkedListNode<string>("state4");
first.Next = new SingleLinkedListNode<string>("state3");
SingleLinkedListNode<string> current = first.Next;
current.Next = new SingleLinkedListNode<string>("state2");
current = current.Next;
current.Next = new SingleLinkedListNode<string>("state1");
current = first;
while (current != null)
{
if (current.Value != "state2") continue;
current.Next = null;
current = current.Next;
break;
}
}
This eliminates the need for the collection class altogether.
Alternatively, you can do this:
while (currentNode.Next != null)
list.Remove(currentNode.Next);
Actually, a linked list is a fairly trivial data structure to implement especially in managed code (read: no memory management hassle).
Here's one I hacked up that support just enough functions (read: YAGNI) to support your undo/redo operations:
public class LinkedListNode<T>
{
public LinkedList<T> Parent { get; set; }
public T Value { get; set; }
public LinkedListNode<T> Next { get; set; }
public LinkedListNode<T> Previous { get; set; }
}
public class LinkedList<T> : IEnumerable<T>
{
public LinkedListNode<T> Last { get; private set; }
public LinkedListNode<T> AddLast(T value)
{
Last = (Last == null)
? new LinkedListNode<T> { Previous = null }
: Last.Next = new LinkedListNode<T> { Previous = Last };
Last.Parent = this;
Last.Value = value;
Last.Next = null;
return Last;
}
public void SevereAt(LinkedListNode<T> node)
{
if (node.Parent != this)
throw new ArgumentException("Can't severe node that isn't from the same parent list.");
node.Next.Previous = null;
node.Next = null;
Last = node;
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable<T>)this).GetEnumerator();
}
public IEnumerator<T> GetEnumerator()
{
var walk = Last;
while (walk != null) {
yield return walk.Value;
walk = walk.Previous;
}
}
}
Then you can use the SevereAt
method in your code to "cut" the linked list nice and simple.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With