Continuing from last time, let’s look at how one goes from imperative pseudocode to pure functional using Gale-Shapely as an example.
Overall, to convert an algorithm from imperative to functional is a fairly simple process once you understand how to convert from a while loop to recursion with accumulators. This post is just a more advanced version of what I talked about in my From Imperative to Computation Expressions post. In fact, if you haven’t already I suggest you read it first. I’ll be assuming you understand how to perform that conversion in this article. If you’re still having trouble, another good article which is a bit easier is The Ted Neward F# Folding Challenge.
The conversion of an algorithm is a two stage process: first identify algorithmic cases, implied mutable data structures, and alternate immutable data structures. Then convert the given loops into recursion while leveraging pattern matching. I know it may seem difficult at first, but after reading this I think you’ll have a good idea of how to get there.
So, let’s identify the cases.
function stableMatching {
Initialize all m ? M and w ? W to free
// Loop Termination Case on
// check for “free m with unproposed w” and
// implicit ranking of W for each m in M
while ? free man m who still has a
woman w to propose to {
w = m’s highest ranked such woman
who he has not proposed to yet
// She’s Single Case
if w is free
(m, w) become engaged
// She’s Engaged Case
else some pair (m’, w) already exists
// She Picks m Sub-Case
if w prefers m to m’
(m, w) become engaged
m’ becomes free
// She Picks m’ Sub-Case
else
(m’, w) remain engaged
}
}
Starting from the top, we can just ignore the imperative initialization. However, you’ll notice that a great deal is going on in that while clause.
First, it defines our loop termination by looking for a “free m who still has a w to propose to”. This is interesting because it seems to be begging for some kind of data structure lookup. Second it defines a implicit ranking of each member of W by each member of M. Note that this is not mentioned in the initialization phase.
The rest of this algorithm reads like a simple list of cases. To make this plain, let’s list them out along with state changes.
- no single m in M with unproposed w
— execution terminates - m proposes to unengaged w
— w is no longer in m proposals
— m is no longer single
— m and w are engaged - m proposes to w engaged to m’
a. She switches to m
— w is no longer in m proposals
— m is no longer single
— m and w are engaged
— m’ is single
b. She stays with m’
— w is no longer in m proposals
— m’ and w stay engaged
— m remains single
The mechanics of what exactly we do in these cases depends on our data structures. So we can’t get much further without figuring that out. To identify what data structures are needed we’ll need to identify what exactly it is that needs to be kept track of. Only once we know that can we pick our immutable data structures appropriately.
Looking above, it appears that we’ll need to keep track of:
- Given a woman, is she engaged and, if so, who is she engaged to? (need to look it up)
- Which men are single? (just need one at a time, order doesn’t matter)
- For each man, which is his next most desirable match that he has not yet proposed to? (just need one at a time, order matters)
Note that we don’t have to keep track of which men are engaged to which women, the question is never asked. This is a minimal set of requirements here. No extra stuff allowed. I’m serious.
Moving on, in an imperative mutable version you might use these data structures:
- An array indexed by w to the m index engaged to (-1 if single)
- An array indexed by m to the w index engaged to (-1 if single)
- An array indexed by m where each element is an ordered linked list of unproposed w indices.
However, what would satisfy these same requirements but be immutable? Well, if we step out of the mutation mindset it becomes obvious pretty quickly:
- An engagement map of women to men
- An immutable linked list of single men
- Define a man to include his index as well as an ordered unproposed woman immutable linked list
Instead of scanning arrays, or querying mutable structures we simply decompose our data structures as much as needed and recompose them into the form we need on the next call. This may sound difficult, but it’s really not. The fact that we have pattern matching and our cases can be defined in terms of the shape of our data structures actually makes this very simple to do. That is, once you get the hang of it.
To see this in action, let’s break up the code in the last article and discuss each chunk.
1: open System 2: 3: // a Bachelor is an identity index and an 4: // ordered list of women indicies to approach. 5: type Bachelor = int * int list
Here we see the definition of a bachelor, it’s a tuple of an integer and a list of integers. This is effectively a pair containing an index and a list of woman indices. Note that in this example all men are of type Bachelor.
7: // Some notation: 8: // wi = woman index (int) 9: // mi = man index (int) 10: // mi' = woman's current partner index (int) 11: // m = man with index and unapproached women indices (Bachelor) 12: // mSingle = men that are single (Bachelor list) 13: // wEngaged = engagements from women to men (int, Bachelor)
We’ll keep this notation around just so you have easy reference.
15: let funGS (comp: _ -> _ -> float) (M: _ array) (W: _ array) = 16: let Windices = [ 0 .. W.Length - 1 ] 17: // List of men with women in order of desire 18: let Munproposed = 19: List.init M.Length 20: (fun mi -> 21: let sortFun wi = 1.0 - (comp M.[mi] W.[wi]) 22: mi, Windices |> List.sortBy sortFun)
Windices is just a list of numbers from 0 to the number of woman tokens. Doing this first makes the calculation of Munproposed less expensive.
Munproposed is our initial set of all single men. Here List.init is used to call the given lambda to generate each element. As you can see by the last line within that lambda, each element of the list is a tuple containing the index of that man and a list of women indices sorted by the given desirability function, sortFun.
23: // Recursively solve stable marriages 24: let rec findMarriages mSingle wEngaged = 25: match mSingle with 26: // No single guys left with desired women, we're done 27: | [] -> wEngaged 28: // Guy is out of luck, remove from singles 29: | (mi, []) :: bachelors -> findMarriages bachelors wEngaged
These are our first two cases, if the mSingle list pattern matches with [], we know it’s empty and so we are done.
In the second case we are pattern matching on the structure of singles list as well as it’s first element. The syntax (mi, []) matches on a tuple of an index and an empty inner list. This list is the list of yet unproposed to women and so if it’s empty we know that this guy is out of options and so it’s ok to drop him from the list of bachelors. We do this by simply recursing on bachelors, which is the tail of the mSingle list.
30: // He's got options! 31: | (mi, wi :: rest) :: bachelors -> 32: let m = mi, rest
This is our general purpose match, and is the structure we expect to see in most cases. Here the head is fully decomposed into a man index (mi), his next first choice woman index (wi), the rest of the women (rest) and the rest of the single men (bachelors).
Immediately afterward we define m to be that given man index (mi) and the rest of the women tokens (rest). As two tokens are only ever compared once, m should no longer contain wi in his ordered list of proposals. This is the form he will take until the algorithm ends or he is evaluated again after being placed in the mSingle pool.
33: match wEngaged |> Map.tryFind wi with 34: // She's single! m is now engaged! 35: | None -> findMarriages bachelors (wEngaged |> Map.add wi m) 36: // She's already engaged, let the best man win! 37: | Some (m') –>
38: let mi', _ = m'
Now that we have fully matched out everything masculine, it’s time to turn to the feminine.
Here we look in the wEngaged map to find out if wi is single. This is done by a using Map.tryFind which returns None if the given key is not in the map and Some(value) if it is.
If the Map.tryFind call returns None we know she is single and so recurse with the rest of our single men (bachelors) and our wEngaged map is extended via Map.add to contain a new mapping from the woman index (wi) to the given man (m).
If she’s engaged our pattern match automatically decomposes the given option type and we know exactly who she is engaged to (m’).
39: if comp W.[wi] M.[mi] > comp W.[wi] M.[mi'] then 40: // Congrats mi, he is now engaged to wi 41: // The previous suitor (mi') is bested 42: findMarriages 43: (m' :: bachelors) 44: (wEngaged |> Map.add wi m) 45: else 46: // The current bachelor (mi) lost, better luck next time 47: findMarriages 48: (m :: bachelors) 49: wEngaged
This is the core comparison logic. Here we determine which bachelor gets her hand. This is pretty straightforward from an imperative perspective as we’re using the oh-so familiar if statement.
In the first case the new contender (m) wins. We append the loser (m’) back on to the head of what will be mSingle next time around with the syntax (m’ :: bachelors). Similar to what happens if she’s single, we add a mapping from the woman index (wi) to the man instance (m) via the call to Map.add. Note that this will override the former mapping from wi to m’.
If the current bachelor loses we simply append him back to the head of the bachelors list and try again next time around. You’ll get her next time bro.
50: findMarriages Munproposed Map.empty 51: // Before returning, remove unproposed lists from man instances 52: |> Map.map (fun wi m -> let mi, _ = m in mi)
The final section is just how we set up our recursion. We start with the full list of single men and their ranked lady lists (Munproposed) and an empty map of relationships (Map.empty). When the algorithm exits we make one last pass through our returned wEngaged map in order to strip out all of the lists of unproposed to women. It’s just baggage now anyway.
Well that’s how you get from imperative pseudocode to a pure functional implementation. All of the code here, as well as a really nasty imperative version to compare it to is available on my GitHub page.
If you liked this post, have any questions, or feel like I missed something important I hope you’ll comment here on it. We’ll all benefit from the knowledge shared.
Full name: Snippet.Bachelor
val int : ‘T -> int (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.int
——————–
type int<‘Measure> = int
Full name: Microsoft.FSharp.Core.int<_>
type: int<‘Measure>
implements: IComparable
implements: IConvertible
implements: IFormattable
implements: IComparable<int<‘Measure>>
implements: IEquatable<int<‘Measure>>
inherits: ValueType
——————–
type int = int32
Full name: Microsoft.FSharp.Core.int
type: int
implements: IComparable
implements: IFormattable
implements: IConvertible
implements: IComparable<int>
implements: IEquatable<int>
inherits: ValueType
Full name: Microsoft.FSharp.Collections.list<_>
type: ‘T list
implements: Collections.IStructuralEquatable
implements: IComparable<List<‘T>>
implements: IComparable
implements: Collections.IStructuralComparable
implements: Collections.Generic.IEnumerable<‘T>
implements: Collections.IEnumerable
Full name: Snippet.funGS
val float : ‘T -> float (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.float
——————–
type float<‘Measure> = float
Full name: Microsoft.FSharp.Core.float<_>
type: float<‘Measure>
implements: IComparable
implements: IConvertible
implements: IFormattable
implements: IComparable<float<‘Measure>>
implements: IEquatable<float<‘Measure>>
inherits: ValueType
——————–
type float = Double
Full name: Microsoft.FSharp.Core.float
type: float
implements: IComparable
implements: IFormattable
implements: IConvertible
implements: IComparable<float>
implements: IEquatable<float>
inherits: ValueType
val M : ‘a array
type: ‘a array
implements: ICloneable
implements: Collections.IList
implements: Collections.ICollection
implements: Collections.IStructuralComparable
implements: Collections.IStructuralEquatable
implements: Collections.Generic.IList<‘a>
implements: Collections.Generic.ICollection<‘a>
implements: seq<‘a>
implements: Collections.IEnumerable
inherits: Array
——————–
val M : ‘a array
type: ‘a array
implements: ICloneable
implements: Collections.IList
implements: Collections.ICollection
implements: Collections.IStructuralComparable
implements: Collections.IStructuralEquatable
implements: Collections.Generic.IList<‘a>
implements: Collections.Generic.ICollection<‘a>
implements: seq<‘a>
implements: Collections.IEnumerable
inherits: Array
Full name: Microsoft.FSharp.Core.array<_>
type: ‘T array
implements: ICloneable
implements: Collections.IList
implements: Collections.ICollection
implements: Collections.IStructuralComparable
implements: Collections.IStructuralEquatable
implements: Collections.Generic.IList<‘T>
implements: Collections.Generic.ICollection<‘T>
implements: seq<‘T>
implements: Collections.IEnumerable
inherits: Array
val W : ‘a array
type: ‘a array
implements: ICloneable
implements: Collections.IList
implements: Collections.ICollection
implements: Collections.IStructuralComparable
implements: Collections.IStructuralEquatable
implements: Collections.Generic.IList<‘a>
implements: Collections.Generic.ICollection<‘a>
implements: seq<‘a>
implements: Collections.IEnumerable
inherits: Array
——————–
val W : ‘a array
type: ‘a array
implements: ICloneable
implements: Collections.IList
implements: Collections.ICollection
implements: Collections.IStructuralComparable
implements: Collections.IStructuralEquatable
implements: Collections.Generic.IList<‘a>
implements: Collections.Generic.ICollection<‘a>
implements: seq<‘a>
implements: Collections.IEnumerable
inherits: Array
type: int list
implements: Collections.IStructuralEquatable
implements: IComparable<List<int>>
implements: IComparable
implements: Collections.IStructuralComparable
implements: Collections.Generic.IEnumerable<int>
implements: Collections.IEnumerable
type: ‘a array
implements: ICloneable
implements: Collections.IList
implements: Collections.ICollection
implements: Collections.IStructuralComparable
implements: Collections.IStructuralEquatable
implements: Collections.Generic.IList<‘a>
implements: Collections.Generic.ICollection<‘a>
implements: seq<‘a>
implements: Collections.IEnumerable
inherits: Array
type: (int * int list) list
implements: Collections.IStructuralEquatable
implements: IComparable<List<int * int list>>
implements: IComparable
implements: Collections.IStructuralComparable
implements: Collections.Generic.IEnumerable<int * int list>
implements: Collections.IEnumerable
module List
from Microsoft.FSharp.Collections
——————–
type List<‘T> =
| ( [] )
| ( :: ) of ‘T * ‘T list
with
interface Collections.IEnumerable
interface Collections.Generic.IEnumerable<‘T>
member Head : ‘T
member IsEmpty : bool
member Item : index:int -> ‘T with get
member Length : int
member Tail : ‘T list
static member Cons : head:’T * tail:’T list -> ‘T list
static member Empty : ‘T list
end
Full name: Microsoft.FSharp.Collections.List<_>
type: List<‘T>
implements: Collections.IStructuralEquatable
implements: IComparable<List<‘T>>
implements: IComparable
implements: Collections.IStructuralComparable
implements: Collections.Generic.IEnumerable<‘T>
implements: Collections.IEnumerable
Full name: Microsoft.FSharp.Collections.List.init
type: ‘a array
implements: ICloneable
implements: Collections.IList
implements: Collections.ICollection
implements: Collections.IStructuralComparable
implements: Collections.IStructuralEquatable
implements: Collections.Generic.IList<‘a>
implements: Collections.Generic.ICollection<‘a>
implements: seq<‘a>
implements: Collections.IEnumerable
inherits: Array
type: int
implements: IComparable
implements: IFormattable
implements: IConvertible
implements: IComparable<int>
implements: IEquatable<int>
inherits: ValueType
type: int
implements: IComparable
implements: IFormattable
implements: IConvertible
implements: IComparable<int>
implements: IEquatable<int>
inherits: ValueType
Full name: Microsoft.FSharp.Collections.List.sortBy
type: (int * int list) list
implements: Collections.IStructuralEquatable
implements: IComparable<List<int * int list>>
implements: IComparable
implements: Collections.IStructuralComparable
implements: Collections.Generic.IEnumerable<int * int list>
implements: Collections.IEnumerable
type: Map<int,(int * int list)>
implements: IComparable
implements: Collections.Generic.IDictionary<int,(int * int list)>
implements: Collections.Generic.ICollection<Collections.Generic.KeyValuePair<int,(int * int list)>>
implements: seq<Collections.Generic.KeyValuePair<int,(int * int list)>>
implements: Collections.IEnumerable
type: (int * int list) list
implements: Collections.IStructuralEquatable
implements: IComparable<List<int * int list>>
implements: IComparable
implements: Collections.IStructuralComparable
implements: Collections.Generic.IEnumerable<int * int list>
implements: Collections.IEnumerable
type: int list
implements: Collections.IStructuralEquatable
implements: IComparable<List<int>>
implements: IComparable
implements: Collections.IStructuralComparable
implements: Collections.Generic.IEnumerable<int>
implements: Collections.IEnumerable
module Map
from Microsoft.FSharp.Collections
——————–
type Map<‘Key,’Value (requires comparison)> =
class
interface Collections.IEnumerable
interface IComparable
interface Collections.Generic.IEnumerable<Collections.Generic.KeyValuePair<‘Key,’Value>>
interface Collections.Generic.ICollection<Collections.Generic.KeyValuePair<‘Key,’Value>>
interface Collections.Generic.IDictionary<‘Key,’Value>
new : elements:seq<‘Key * ‘Value> -> Map<‘Key,’Value>
member Add : key:’Key * value:’Value -> Map<‘Key,’Value>
member ContainsKey : key:’Key -> bool
override Equals : obj -> bool
member Remove : key:’Key -> Map<‘Key,’Value>
member TryFind : key:’Key -> ‘Value option
member Count : int
member IsEmpty : bool
member Item : key:’Key -> ‘Value with get
end
Full name: Microsoft.FSharp.Collections.Map<_,_>
type: Map<‘Key,’Value>
implements: IComparable
implements: Collections.Generic.IDictionary<‘Key,’Value>
implements: Collections.Generic.ICollection<Collections.Generic.KeyValuePair<‘Key,’Value>>
implements: seq<Collections.Generic.KeyValuePair<‘Key,’Value>>
implements: Collections.IEnumerable
Full name: Microsoft.FSharp.Collections.Map.tryFind
Full name: Microsoft.FSharp.Collections.Map.add
type: int
implements: IComparable
implements: IFormattable
implements: IConvertible
implements: IComparable<int>
implements: IEquatable<int>
inherits: ValueType
Full name: Microsoft.FSharp.Collections.Map.empty
Full name: Microsoft.FSharp.Collections.Map.map
Full name: Snippet.alignJaroWinkler
Enjoy this post? Continue the conversation with me on twitter.
Tags: Abstraction, Algorithms, F#, fsharp, Functional Programming, Gale-Shapely, Immutable Data Structures, Looping, Record Linkage, Recursion