Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# - LinkedList - How to remove all nodes after specified node?

Tags:

c#

linked-list

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 ?...

like image 303
rockeye Avatar asked Feb 24 '09 15:02

rockeye


4 Answers

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.

like image 97
Jon Skeet Avatar answered Sep 19 '22 02:09

Jon Skeet


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);
}
like image 20
Lasse V. Karlsen Avatar answered Sep 23 '22 02:09

Lasse V. Karlsen


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.

like image 24
Michael Meadows Avatar answered Sep 23 '22 02:09

Michael Meadows


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.

like image 30
chakrit Avatar answered Sep 23 '22 02:09

chakrit