13

Looking at this question, where the questioner is interested in the first and last instances of some element in a List, it seems a more efficient solution would be to use a DoubleLinkedList that could search backwards from the end of the list. However there is only one implementation in the collections API and it's mutable.

Why is there no immutable version?

Community
  • 1
  • 1
Luigi Plinge
  • 50,650
  • 20
  • 113
  • 180
  • 1
    Off the top of my head, I can't think of any way any operation on a doubly-linked list could avoid copying *all* elements (whereas singly-linked lists can share the tail - e.g. O(1) `cons`). That sounds pretty inefficent in general to me. Coming to think of it, I'm not aware of *any* library with an immutable doubly linked list... –  Nov 07 '11 at 20:48
  • 1
    In Haskell, the data structure you are looking for would be [Data.Sequence](http://hackage.haskell.org/packages/archive/containers/latest/doc/html/Data-Sequence.html) (which is slightly more complicated than a doubly-lined list, but has many performance improvements while maintaining immutability). I'd be surprised if Scala didn't have an equivalent immutable structure; if it doesn't, then let's get in contact with the devs and ask for it. – Dan Burton Nov 07 '11 at 22:32
  • @Dan I believe the equivalent in Scala would be [Vector](http://www.scala-lang.org/api/current/index.html#package). – Kipton Barros Nov 08 '11 at 02:41

5 Answers5

15

Because you would have to copy the whole list each time you want to make a change. With a normal linked list, you can at least prepend to the list without having to copy everything. And if you do want to copy everything on every change, you don't need a linked list for that. You can just use an immutable array.

Kim Stebel
  • 41,826
  • 12
  • 125
  • 142
10

There are many impediments to such a structure, but one is very pressing: a doubly linked list cannot be persistent.

The logic behind this is pretty simple: from any node on the list, you can reach any other node. So, if I added an element X to this list DL, and tried to use a part of DL, I'd face this contradiction: from the node pointing to X one can reach every element in part(DL), but, by the properties of the doubly linked list, that means from any element of part(DL) I can reach the node pointing to X. Since part(DL) is supposed to be immutable and part of DL, and since DL did not include the node pointing to X, that just cannot be.

Non-persistent immutable data structures might have some uses, but they are generally bad for most operations, since they need to be recreated whenever a derivative is produced.

Now, there's the minor matter of creating mutually referencing strict objects, but this is surmountable. One can use by-name parameters and lazy vals, or one can do like Scala's List: actually create a mutable collection, and then "freeze" it in immutable state (see ListBuffer and it's toList method).

Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
  • 5
    Your conclusion about a "contradiction" is incorrect. You can not reach anything from the elements of the list, where "elements" are the objects you add to the list. You go forward and backward from the list object (and for a persistent list these traversal operations would return a new list object representing the new position). One simple persistent implementation is to have the list represent the forward and backward links between nodes as two (persistent) hash tables from a node to its successor and predecessor, where each node holds one element. – Per Mildner Aug 27 '12 at 20:37
  • 3
    @PerMildner By "element" I meant the node pointing to the element. I made the correct to this regard. Remember that "persistent" means you are not allowed to create a new list, just a new node. – Daniel C. Sobral Aug 28 '12 at 02:57
6

Because it is logically impossible to create a mutually (circular) referential data-structure with strict immutability.

You cannot create two nodes that point to each other due to simple existential ordering priority, in that at least one of the nodes will not exist when the other is created.

It is possible to get this circularity with tricks involving laziness (which is implemented with mutation), but the real question then becomes why you would want this thing in the first place?

Jed Wesley-Smith
  • 4,686
  • 18
  • 20
4

As others have noted, there is no persistent implementation of a double-linked list. You will need some kind of tree to get close to the characteristics you want.

In particular, you may want to look at finger trees, which provide O(1) access to the front and back, amortized O(1) insertion to the front and back, and O(log n) insertion elsewhere. (That's in contrast to most other commonly-used trees which have O(log n) access and insertion everywhere.)

See also:

avh4
  • 2,635
  • 1
  • 22
  • 25
3

As a supplemental to the answer of @KimStebel I like to add:
If you are searching for a data structure suitable for the question that motivated you to ask this question, then you might have a look at Extreme Cleverness: Functional Data Structures in Scala by @DanielSpiewak.

Community
  • 1
  • 1
Peter Schmitz
  • 5,824
  • 4
  • 26
  • 48