Efficient implementation of immutable (dual) LinkedList

Having read this question Invariable or not invariable? and after reading the answers to my previous questions about immutability, I'm still a little puzzled by the efficient implementation of a simple LinkedList that is immutable. From the point of view of the array, it seems easy: copy the array and return a new structure based on this copy.

Presumably, we have a generic Node class:

class Node{
    private Object value;
    private Node next;
}

And the LinkedList class based on the above allows the user to add, delete, etc. Now, how would we guarantee immutability? Should we recursively copy all the links to the list when we insert an element?

I am also interested to know the answers in Immutable or not immutable? that mention cerain optimization leading to log (n) of time and space using a binary tree. Also, I read somewhere that adding an ale to the front is 0 (1). This is very puzzling to me, as if we are not providing a copy of the links, but in fact we are modifying the same data structures in two different sources, which violates the immutability ...

Will any of your answers work on doubly linked lists? I look forward to any answers / directions to any other questions / solutions. Thanks in advance for your help.

+5
source share
3 answers

Node LinkedList , , .. , ?

, readonly , , readonly, . , , , !

, ?

. , , - . . , .

:

abstract class ImmutableList
{
    public static readonly ImmutableList Empty = new EmptyList();
    private ImmutableList() {}
    public abstract int Head { get; }
    public abstract ImmutableList Tail { get; }
    public abstract bool IsEmpty { get; }
    public abstract ImmutableList Add(int head);
    private sealed class EmptyList : ImmutableList
    {
        public override int Head { get {  throw new Exception(); } }
        public override ImmutableList Tail { get { throw new Exception(); } }
        public override bool IsEmpty { get { return true; } }
        public override ImmutableList Add(int head)
        {
            return new List(head, this);
        }
    }

    private sealed class List : ImmutableList
    {
        private readonly int head;
        private readonly ImmutableList tail;
        public override int Head { get { return head; } }
        public override ImmutableList Tail { get { return tail; } }
        public override bool IsEmpty { get { return false; } }
        public override ImmutableList Add(int head)
        {
            return new List(head, this);
        }
    }
}
...
ImmutableList list1 = ImmutableList.Empty;
ImmutableList list2 = list1.Add(100);
ImmutableList list3 = list2.Add(400);

. , , IEnumerable<int>. . , , ; list3 2, , list2 .

- ?

, , , , ; .

, . , , - . , " ?" : " , ?" , , , .

, , , , , - deque, . :

http://blogs.msdn.com/b/ericlippert/archive/2008/02/12/immutability-in-c-part-eleven-a-working-double-ended-queue.aspx

, deque, ; , , .

UPDATE:

. , w - , , ... , ?

. , (10, 20, 30, 40), 25 2? (10, 20, 25, 30, 40).

? (20, 30, 40), (30, 40) (40). , (30, 40).

. :

10 ----> 20 ----> 30 -----> 40 -----> Empty

10 ----> 20 ----> 25 -----> 30 -----> 40 -----> Empty

| 10 ----> 20 --------------> 30 -----> 40 -----> Empty
|                        /
| 10 ----> 20 ----> 25 -/ 

(30, 40), .

UPDATE:

?

:

ImmutableList InsertAt(int value, int position)
{
    if (position < 0) 
        throw new Exception();
    else if (position == 0) 
         return this.Add(value);
    else 
        return tail.InsertAt(value, position - 1).Add(head);
}

, ?

, , DeleteAt.

, , InsertAt DeleteAt. , , !

+17

, ?

, .

, - O (n). (As ( ) ).

( ).

- "", , O (1).

, . Haskell. , :

data List a = Empty | Node a (List a)

"cons" ( ) :

cons a xs = Node a xs

, O (1). - . () node () .

:

:

  • .

, , .

0

"": .

( Scala):

case class ListItem(s:String, id:UUID, nextID: UUID)

ListItems , UUID: type MyList = Map[UUID, ListItem]

ListItem val list : MyList:

def insertAfter(l:MyList, e:ListItem)={ 
     val beforeE=l.getElementBefore(e)
     val afterE=l.getElementAfter(e)
     val eToInsert=e.copy(nextID=afterE.nextID)
     val beforeE_new=beforeE.copy(nextID=e.nextID)
     val l_tmp=l.update(beforeE.id,beforeE_new)
     return l_tmp.add(eToInsert)
}

Where add, update, getis constant while using Map: http://docs.scala-lang.org/overviews/collections/performance-characteristics

The implementation of a double linked list is similar.

0
source

All Articles