Queues and dealing with options
Contents:
# Implementing queues as a list
Lets start by defining our queue as a list
But we ofcourse want to wrap this around a module:
=
Is the more efficient way of adding an element at the end of the list is to reverse it first?
rev ;;
100% not! Because firstly, we’re traversig the list twice, so its twice as bad as q @ [val]. Secondly, They are equivalent in terms of time complexity O(n). We have a much sleeker solution. Let’s look at it.
- We’ll represent the queue as two lists, front and the back of the queue
(*queue is a record with an arbitrary dividing line in between*)
(**E.g. front = [1;2;3] and back = [0;9;8;7] represents the queue = [1;2;3;7;8;9;0]
so, we'll be having the `back` of the queue in the reversed order*)
- Implement the
peek
let empty =
(*If we think about it, if the front is empty, then back must also be empty
This is a design decision we're making. This means I don't have to match anything
for the back*)
None
Some a
- Implement the
enqueue
(*Pretty darn interesting. So we're saying if the queue is non empty, then we return
q but we cons the value to the back. Since that will later get reversed anyway, we're
good to go
Do note the syntax here
*)
- Implement the
dequeue
None
Some
Note that this implementation is not quite complete, because I haven’t handled my own requirement that when the front (b) is empty then the back must also be empty.
None
(*Now there might still be some elements in the back, we just need to move them
to the front of the queue*)
(*Now this is constant time except in that rare case where we hit the empty tail*)
Very neat!
# Exceptions vs options
Options as you should recall are Some and None. The good thing baout exceptions is that we don’t have to worry about options! As in we can simply chain together multiple operations without have to “optionally” get a response from a function.
Because if we’re optionally getting a response we’ll have to handle the complementary as well. For example:
empty_queue |> enqueue 1 |> enqueue 2 |> dequeue |> peek
This gives me a type error saying that it expected an optional queue but it got a real queue. So what if we could make a pipeline operator that handles this for us?
Recall how the original pipeline operator was defined as:
f x
Takes an argument and a function and applies that function to the argument (passes in the argument to the function). Now we can have the argument as an option, so let’s do this:
match opt None
Some
This works (also available in the standard library as Option.map), so now we can write this:
empty_queue |> enqueue 1 |> enqueue 2 |> dequeue >>| peek
but what if I want to dequeue again? I can’t do that because the new operator gives out an option response but the dequeue accepts only normal queues! And the normal pipeline also won’t work because that will raise a type error.
So, we’ll have to come up with a new operator, that will let me return a non-option too:
match opt None
f x
using this towards the end, we’ll be golden. This operator is present as Option.bind.
empty_queue |> enqueue 1 |> enqueue 2 |> dequeue >>| enqueue 42 >>= dequeue >>| peek