Notes on working with finite sorted lists

by Colin Ross on January 11, 2009

In a previous post, I wrote some information on ways to work with sorted infinite lists.  Since then I have investigated some more and realise that the code I gave could be considerably improved.  Particularly if you are interested in sorted finite lists.

The improvement

I made the remark that while

elem n primes

would not return for the infinite list primes, the alternative

(head $ dropWhile (<n) primes) == n

would work.

This is true, but it raises a couple of issues that need to be addressed before it can be used more widely.

The first problem is that it actually doesn’t work for finite lists, which is rather annoying but can be resolved by adding an appropriate test before taking the head:

contains n ns = (not $ null rest) && (head rest == n)
   where
   rest = dropWhile (<n) ns

The other issue with finite lists is that this is actually pretty inefficient and a better way to test for membership is to use sets. A much faster approach is this:

contains n ns = Set.member n $ Set.fromList ns

Even though this would appear to be doing a lot more work, it does appear to be considerably faster.  However, this does not work for infinite lists, because the call to Set.fromList never terminates.

I have been making increased use of both of these methods to test for membership as I continue to work through Project Euler probems.  Now at level 2!

Bookmark and Share

Related posts:

  1. The fast, the right and the smart Working my
  2. Finding a member of an infinite list Haskell, a

{ 4 comments… read them below or add one }

Felipe January 12, 2009 at 9:50 pm

Set.fromList is O(n log n) and has to traverse the whole list, while elem is O(n) and traverses only half of the list on average, there’s a clear winner here for me.

*But* Set.member is O(log n), so if you construct the Set only once and use it multiple times, then that’s a win. But then you should at least write

contains ns = let s = Set.fromList ns in flip Set.member ns

and hope that GHC does a nice optimizing job.

Reply

Colin Ross January 12, 2009 at 10:14 pm

@Felipe

I took the liberty of editing your comment to use your most recent code snippet.

That all makes sense. One thing I struggle to understand is how much of a computation is kept around after it is evaluated once. From what you write, does the “let” force the evaluation and keep the result around? Or does that depend on the optimisation performed by the compiler?

I still have a lot to learn.

Reply

Felipe January 14, 2009 at 12:05 am

Thanks for the edit =). I’d like to have a preview button here.

I can’t get you a definite answer, but I think that GHC will share the Set on every optimization options, from -O0 to -O3, when using the definition I gave. However I don’t think it will share the Set on the post’s definition because that may create a space leak and the compiler can’t tell that the optimization is benign only by looking at the code.

Reply

Colin Ross January 14, 2009 at 8:15 am

Interesting. I guess as I write more Haskell, these things will become second nature – roll on that day…

I have added a plugin to allow the preview of comments – hope it works ok!

Reply

Leave a Comment

Previous post: The fast, the right and the smart

Next post: Finding a member of an infinite list