Reputation: 15213
Haskell supports some basic operations for recursing through lists, like head
, tail
, init
and last
. I'm wondering, internally, how Haskell is representing its list data? If it's a singly-linked list, then init
and last
operations could become costly as the list grows. If it's a doubly-linked list, all four operations could be made O(1)
quite easily, albeit at the expense of some memory. Either way, it's important for me to know, so I can write appropriate code. (although, the ethos of functional programming seems to be one of "ask what it does, not how it does it").
Upvotes: 19
Views: 2334
Reputation: 137997
Lists are represented as ... singly linked lists. The definition is given by:
data [] a = [] | a : [a]
which you could write as:
data List a = Empty | Cons a (List a)
The memory layout is entirely defined by this.
So you end up with something like this:
So head
is O(1) on this structure, while last
or (++)
is O(n)
There is no magic to data structures in Haskell - their straight-forward definition makes entirely clear what the complexity will be (modulo laziness). If you need different complexity, use a different structure (such as IntMap, Sequence, HashMap, Vector etc)...
Upvotes: 28
Reputation: 47402
Haskell lists are singly linked, so cons
, head
and tail
are O(1) while init
and last
are O(n).
If you need better performance, consider using the Seq
type from Data.Sequence, which provides O(1) access to both ends of the list. Internally it uses 2-3 finger trees.
Upvotes: 14