ZetCode

F# sets

last modified May 17, 2025

In this article we will explore sets in F#. Sets are a fundamental data structure in functional programming, providing a way to store unique elements and perform set operations.

Sets in F# are immutable collections that contain no duplicate elements. They provide efficient lookup operations and support common set operations like union, intersection, and difference. F# sets are implemented as immutable balanced binary trees, which makes them efficient for functional programming patterns.

Since sets in F# are immutable, any operation that modifies a set—such as adding or removing elements—returns a new set instead of altering the original. This ensures that data integrity is preserved and allows for safe concurrent programming. Additionally, the underlying balanced tree structure ensures that set operations, such as membership tests and iteration, remain performant even with large data sets.

Creating sets

There are several ways to create sets in F#. You can create them from sequences, lists, arrays, or by using set comprehensions. The Set module provides functions for working with sets.

creating_sets.fsx
// Creating empty set
let emptySet = Set.empty
printfn "Empty set: %A" emptySet

// Using set literal
let uniqeueVals = set [ 1; 2; 3; 4; 5 ]
printfn "Unique values: %A" uniqeueVals

// Creating set from list
let numbers = Set.ofList [ 1; 2; 3; 2; 1 ]
printfn "From list: %A" numbers

// Creating set from array
let colors =
    Set.ofArray [| "red"
                   "green"
                   "blue"
                   "green" |]

printfn "From array: %A" colors

// Helper function to check if a number is prime
let isPrime n =
    if n < 2 then
        false
    else
        seq { 2 .. int (sqrt (float n)) }
        |> Seq.forall (fun x -> n % x <> 0)

// Creating set from sequence
let primes =
    Set.ofSeq (
        seq {
            for i in 1..10 do
                if isPrime i then yield i
        }
    )

printfn "Primes: %A" primes

// Set comprehension
let squares = Set.ofSeq (seq { for x in 1..5 -> x * x })
printfn "Squares: %A" squares

This example demonstrates different ways to create sets. Note how duplicates are automatically removed. The Set.empty function creates an empty set. The set function creates a set from a list, while the ofSeq function allows you to create sets from any sequence, including arrays and lists. The isPrime function is a helper function to check if a number is prime.

λ dotnet fsi creating_sets.fsx
Empty set: set []
Unique values: set [1; 2; 3; 4; 5]
From list: set [1; 2; 3]
From array: set ["blue"; "green"; "red"]
Primes: set [2; 3; 5; 7]
Squares: set [1; 4; 9; 16; 25]

Basic set operations

F# sets support fundamental operations like adding elements, checking for membership, and counting elements. Since sets are immutable, operations that modify a set return a new set rather than changing the original.

basic_operations.fsx
let mySet = set [1; 2; 3; 4; 5]

// Adding elements
let biggerSet = mySet.Add(6).Add(7)
printfn "After adding: %A" biggerSet

// Removing elements
let smallerSet = mySet.Remove(5).Remove(1)
printfn "After removing: %A" smallerSet

// Checking membership
printfn "Contains 3? %b" (mySet.Contains 3)
printfn "Contains 8? %b" (mySet.Contains 8)

// Counting elements
printfn "Count: %d" mySet.Count

// Minimum and maximum
printfn "Min: %d" (Set.minElement mySet)
printfn "Max: %d" (Set.maxElement mySet)

This code shows basic set operations. Add and Remove return new sets. Contains checks for membership, and Count returns the number of elements. minElement and maxElement return the smallest and largest elements.

λ dotnet fsi basic_operations.fsx
After adding: set [1; 2; 3; 4; 5; 6; 7]
After removing: set [2; 3; 4]
Contains 3? true
Contains 8? false
Count: 5
Min: 1
Max: 5

Set operations: union, intersection, difference

F# sets support mathematical set operations including union, intersection, and difference. These operations are performed using functions from the Set module or equivalent operators.

set_operations.fsx
let setA = set [1; 2; 3; 4; 5]
let setB = set [4; 5; 6; 7; 8]

// Union (elements in either set)
let unionAB = Set.union setA setB
printfn "Union: %A" unionAB

// Alternative union syntax
let unionAB2 = setA + setB
printfn "Union with +: %A" unionAB2

// Intersection (elements in both sets)
let intersectAB = Set.intersect setA setB
printfn "Intersection: %A" intersectAB

// Difference (elements in first set but not second)
let diffAB = Set.difference setA setB
printfn "Difference A-B: %A" diffAB

// Symmetric difference (elements in either set but not both)
let symDiffAB = (setA - setB) + (setB - setA)
printfn "Symmetric difference: %A" symDiffAB

This example demonstrates fundamental set operations. The + operator can be used for union, while - is used for difference. Note that symmetric difference isn't built-in but can be constructed from other operations.

λ dotnet fsi set_operations.fsx
Union: set [1; 2; 3; 4; 5; 6; 7; 8]
Union with +: set [1; 2; 3; 4; 5; 6; 7; 8]
Intersection: set [4; 5]
Difference A-B: set [1; 2; 3]
Symmetric difference: set [1; 2; 3; 6; 7; 8]

Set comparisons and subset operations

Sets can be compared for equality and subset relationships. F# provides functions to check if one set is a subset or superset of another, or if they contain exactly the same elements.

set_comparisons.fsx
let setX = set [1; 2; 3; 4]
let setY = set [3; 4; 5; 6]
let setZ = set [3; 4]

// Equality
printfn "X equals Y? %b" (setX = setY)
printfn "X equals X? %b" (setX = setX)

// Subset checks
printfn "Z subset of X? %b" (Set.isSubset setZ setX)
printfn "Z proper subset of X? %b" (Set.isProperSubset setZ setX)
printfn "Z subset of Y? %b" (Set.isSubset setZ setY)

// Superset checks
printfn "X superset of Z? %b" (Set.isSuperset setX setZ)
printfn "Y superset of Z? %b" (Set.isSuperset setY setZ)

The example shows how to compare sets. isSubset checks if all elements of one set are in another, while isProperSubset requires the sets to be different. isSuperset is the inverse of isSubset.

λ dotnet fsi set_comparisons.fsx
X equals Y? false
X equals X? true
Z subset of X? true
Z proper subset of X? true
Z subset of Y? true
X superset of Z? true
Y superset of Z? true

Set transformations and filtering

Sets can be transformed and filtered using higher-order functions like map and filter. These operations return new sets with the transformed or filtered elements.

set_transformations.fsx
let numbers = set [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]

// Mapping a function over a set
let squares = Set.map (fun x -> x * x) numbers
printfn "Squares: %A" squares

// Filtering elements
let evens = Set.filter (fun x -> x % 2 = 0) numbers
printfn "Evens: %A" evens

// Folding (reducing) a set
let sum = Set.fold (fun acc x -> acc + x) 0 numbers
printfn "Sum: %d" sum

// Iterating over a set
printf "Elements: "
Set.iter (fun x -> printf "%d " x) numbers
printfn ""

This code demonstrates functional transformations on sets. map applies a function to each element, filter selects elements matching a predicate, fold reduces the set to a single value, and iter performs an action for each element.

λ dotnet fsi set_transformations.fsx
Squares: set [1; 4; 9; 16; 25; 36; 49; 64; 81; 100]
Evens: set [2; 4; 6; 8; 10]
Sum: 55
Elements: 1 2 3 4 5 6 7 8 9 10 

Working with custom types

Sets can contain custom types, but the types must support comparison. For records and discriminated unions, F# automatically generates comparison implementations if all constituent types support comparison.

custom_types.fsx
type Person = {
    Name: string
    Age: int
}

let people = set [
    { Name = "Alice"; Age = 30 }
    { Name = "Bob"; Age = 25 }
    { Name = "Alice"; Age = 30 } // Duplicate
    { Name = "Charlie"; Age = 35 }
]

printfn "People set: %A" people
printfn "Count: %d" people.Count

type Shape =
    | Circle of radius: float
    | Rectangle of width: float * height: float

let shapes = set [
    Circle 2.5
    Rectangle (3.0, 4.0)
    Circle 2.5 // Duplicate
    Rectangle (1.0, 2.0)
]

printfn "Shapes set: %A" shapes
printfn "Contains Circle 2.5? %b" (shapes.Contains (Circle 2.5))

This example shows sets containing custom types. The Person record and Shape discriminated union automatically support comparison, so they can be used in sets. Duplicates are automatically removed based on the comparison.

λ dotnet fsi custom_types.fsx
People set: set [{ Name = "Alice"
 Age = 30; }; { Name = "Bob"
 Age = 25; }; { Name = "Charlie"
 Age = 35; }]
Count: 3
Shapes set: set [Circle 2.5; Rectangle (1.0, 2.0); Rectangle (3.0, 4.0)]
Contains Circle 2.5? true

F# sets are powerful immutable collections that provide efficient lookup operations and support mathematical set operations. Their immutable nature makes them ideal for functional programming patterns, and their implementation as balanced binary trees ensures good performance. Whether you need to work with unique collections of data or perform set-theoretic operations, F# sets offer a robust solution.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.