You are previewing Programming F#.

Programming F#

Cover of Programming F# by Chris Smith Published by O'Reilly Media, Inc.
O'Reilly logo

Mutable Collection Types

Often you will need to store data, but you won’t know how many items you will have ahead of time. For example, you might be loading records from a database. If you use an array, you run the risk of wasting memory by allocating too many elements, or even worse, not enough.

Mutable collection types allow you to dynamically add and remove elements over time. While mutable collection types can be problematic when doing parallel and asynchronous programming, they are very simple to work with.

List<'T>

The List type in the System.Collections.Generic namespace—not to be confused with the F# list type—is a wrapper on top of an array that allows you to dynamically adjust the size of the array when adding and removing elements. Because List is built on top of standard arrays, retrieving and adding values is typically very fast. But once the internal array is filled up a new, larger one will need to be created and the List’s contents copied over.

Example 4-10 shows basic usage of a List. Again, we’re denying poor Pluto the title of planet.

Example 4-10. Using the List type

> // Create a List<_> of planets
open System.Collections.Generic
let planets = new List<string>();;

val planets : Collections.Generic.List<string>

> // Add individual planets
planets.Add("Mercury")
planets.Add("Venus")
planets.Add("Earth")
planets.Add("Mars");;
> planets.Count;;
val it : int = 4
> // Add a collection of values at once
planets.AddRange( [| "Jupiter"; "Saturn"; "Uranus"; "Neptune"; "Pluto" |] );;
val it : unit = ()
> planets.Count;;
val it : int = 9
> // Sorry bro
planets.Remove("Pluto");;
val it : bool = true
> planets.Count;;
val it : int = 8

Table 4-2 shows common methods on the List type.

Table 4-2. Methods and properties on the List<'T> type

Function and type

Description

Add:

'a -> unit

Adds an element to the end of the list.

Clear:

unit -> unit

Removes all elements from the list.

Contains:

'a -> bool

Returns whether or not the item can be found in the list.

Count:

int

Property for the number of items in the list.

IndexOf:

'a -> int

Returns a zero-based index of the given item in the list. If it is not present, returns −1.

Insert:

int * 'a -> unit

Inserts the given item at the specified index into the list.

Remove:

'a -> bool

Removes the given item if present from the list.

RemoveAt:

int -> unit

Removes the item at the specified index.

Dictionary<'K,'V>

The Dictionary type in the System.Collections.Generic namespace that contains key-value pairs. Typically, you would use a dictionary when you want to store data and require a friendly way to look it up, rather than an index, such as using a name to look up someone’s phone number.

Example 4-11 shows using a dictionary to map element symbols to a custom Atom type. It uses a language feature called units of measure to annotate the weight of each atom in atomic mass units. We will cover the units of measure feature in Chapter 7.

Example 4-11. Using a dictionary

// Atomic Mass Units
[<Measure>]
type amu

type Atom = { Name : string; Weight : float<amu> }

open System.Collections.Generic
let periodicTable = new Dictionary<string, Atom>()

periodicTable.Add( "H", { Name = "Hydrogen";  Weight = 1.0079<amu> })
periodicTable.Add("He", { Name = "Helium";    Weight = 4.0026<amu> })
periodicTable.Add("Li", { Name = "Lithium";   Weight = 6.9410<amu> })
periodicTable.Add("Be", { Name = "Beryllium"; Weight = 9.0122<amu> })
periodicTable.Add( "B", { Name = "Boron ";    Weight = 10.811<amu> })
// ...

// Lookup an element
let printElement name =

    if periodicTable.ContainsKey(name) then
        let atom = periodicTable.[name]
        printfn
            "Atom with symbol with '%s' has weight %A."
            atom.Name atom.Weight
    else
        printfn "Error. No atom with name '%s' found." name

// Alternate syntax to get a value. Return a tuple of 'success * result'
let printElement2 name =

    let (found, atom) = periodicTable.TryGetValue(name)
    if found then
        printfn
            "Atom with symbol with '%s' has weight %A."
            atom.Name atom.Weight
    else
        printfn "Error. No atom with name '%s' found." Name

Table 4-3 shows common methods on the Dictionary<'k,'v> type.

Table 4-3. Methods and properties on the Dictionary<'k,'v> type

Function and type

Description

Add:

'k * 'v -> unit

Adds a new key-value pair to the dictionary.

Clear:

unit -> unit

Removes all items from the dictionary.

ContainsKey:

'k -> bool

Checks if the given key is present in the dictionary.

ContainsValue:

'v -> bool

Checks if the given value is present in the dictionary.

Count:

int

Returns the number of items in the dictionary.

Remove:

'k -> unit

Removes a key-value pair from the dictionary with the given key.

HashSet<'T>

A HashSet, also defined in the System.Collections.Generic namespace, is an efficient collection for storing an unordered set of items. Let’s say you were writing an application to crawl web pages. You would need to keep track of which pages you have visited before so you didn’t get stuck in an infinite loop; however, if you stored the page URLs you had already visited in a List, it would be very slow to loop through each element to check whether a page had been visited before. A HashSet stores a collection of unique values based on their hash code, so checking if an element exists in the set can be done rather quickly.

Hash codes will be better explained in the next chapter; for now you can just think of them as a way to uniquely identify an object. They are the reason why looking up elements in a HashSet and a Dictionary is fast.

Example 4-12 shows using a HashSet to check whether a movie has won the Oscar for Best Picture.

Example 4-12. Using the HashSet type

Open System.Collections.Generic

let bestPicture = new HashSet<string>()
bestPicture.Add("No Country for Old Men")
bestPicture.Add("The Departed")
bestPicture.Add("Crash")
bestPicture.Add("Million Dollar Baby")
// ...

// Check if it was a best picture
if bestPicture.Contains("Manos: The Hands of Fate") then
    printfn "Sweet..."
    // ...

Table 4-4 shows common methods on the HashSet<'T> type.

Table 4-4. Methods and properties on the HashSet<'T> type

Function and type

Description

Add:

'a -> unit

Adds a new item to the HashSet.

Clear:

unit -> unit

Removes all items from the HashSet.

Count:

int

Returns the number of items in the HashSet.

IntersectWith:

seq<'a> -> unit

Modifies the HashSet to contain only elements that intersect with the given sequence.

IsSubsetOf:

seq<'a > -> bool

Returns whether the HashSet is a subset of the sequence; that is, every element in the HashSet is found in the sequence.

IsSupersetOf:

seq<'a > -> bool

Returns whether the HashSet is a superset of the sequence; that is, every element in the sequence is contained in the HashSet.

Remove:

'a -> unit

Removes the given item from the HashSet.

UnionWith:

seq<'a> -> unit

Modifies the HashSet to contain at least every element in the given sequence.

The best content for your career. Discover unlimited learning on demand for around $1/day.