Fisher–Yates shuffle
DefinitionFisher–Yates shuffle with supplied draws#✓fully proved
Given a vector \(x = (x_0, \dots, x_{n-1})\) and a draw history \(d\), which assigns to each position \(i < n\) a number \(d_i\) with \(0 \le d_i < n - i\), the Fisher–Yates computation processes the positions \(i = 0, 1, \dots, n - 1\) in increasing order and at position \(i\) swaps the entries at positions \(i\) and \(i + d_i\) of the current vector. It returns the final vector together with a cost pair in \(\mathbb{N} \times \mathbb{N}\): each retrieved draw adds \((1, 0)\) and each swap adds \((0, 1)\).
public def randomlyPermute {α : Type u} {n : Nat} (xs : Vector α n)
(draws : (i : Fin n) → Fin (n - i.val)) :
Cslib.Algorithms.Lean.TimeM (Nat × Nat) (Vector α n) :=
scan draws 0 xs
@[no_expose] private def scan {α : Type u} {n : Nat}
(draws : (i : Fin n) → Fin (n - i.val)) (i : Nat) (xs : Vector α n) :
Cslib.Algorithms.Lean.TimeM (Nat × Nat) (Vector α n) :=
if hi : i < n then do
TimeM.tick (1, 0)
let draw := draws ⟨i, hi⟩
let j := i + draw.val
TimeM.tick (0, 1)
let saved := xs.swap i j hi (by
dsimp [j]
have hd : draw.val < n - i := draw.isLt
omega)
⋯
CSLibExt/Algorithms/Lean/Random/RandomlyPermute/Basic.lean:34
Used by 8
TheoremCost of the Fisher–Yates shuffle#✓fully proved
For every vector \(x\) of length \(n\) and every draw history \(d\), which assigns to each position \(i < n\) a number \(d_i\) with \(0 \le d_i < n - i\), the Fisher–Yates computation on \(x\) and \(d\) has cost pair \((n, n)\): it retrieves exactly \(n\) draws and performs exactly \(n\) swaps.
When \(n \ge 1\), this count includes the final draw \(d_{n-1} = 0\) and the self-swap at position \(n - 1\).
public theorem randomlyPermute_time {α : Type u} {n : Nat} (xs : Vector α n)
(draws : (i : Fin n) → Fin (n - i.val)) :
(randomlyPermute xs draws).time = (n, n)
public def randomlyPermute {α : Type u} {n : Nat} (xs : Vector α n)
(draws : (i : Fin n) → Fin (n - i.val)) :
Cslib.Algorithms.Lean.TimeM (Nat × Nat) (Vector α n) :=
scan draws 0 xs
CSLibExt/Algorithms/Lean/Random/RandomlyPermute/Basic.lean:53
Uses 1 · Used by 3
TheoremFisher–Yates output is a rearrangement of its input#✓fully proved
For every vector \(x\) of length \(n\) and every draw history \(d\), which assigns to each position \(i < n\) a number \(d_i\) with \(0 \le d_i < n - i\), the list of entries of the vector returned by the Fisher–Yates computation on \(x\) and \(d\) is a permutation of the list of entries of \(x\). In particular every value occurs equally often in both, even when values of \(x\) repeat.
public theorem randomlyPermute_perm {α : Type u} {n : Nat} (xs : Vector α n)
(draws : (i : Fin n) → Fin (n - i.val)) :
(randomlyPermute xs draws).ret.toList.Perm xs.toList
public def randomlyPermute {α : Type u} {n : Nat} (xs : Vector α n)
(draws : (i : Fin n) → Fin (n - i.val)) :
Cslib.Algorithms.Lean.TimeM (Nat × Nat) (Vector α n) :=
scan draws 0 xs
CSLibExt/Algorithms/Lean/Random/RandomlyPermute/Basic.lean:53
Uses 1
TheoremFisher–Yates on the empty vector#✓fully proved
For the empty vector \(x\) (of length \(0\)) and its empty draw history, the Fisher–Yates computation returns \(x\) with cost pair \((0, 0)\): it retrieves no draws and performs no swaps.
public theorem randomlyPermute_empty {α : Type u} (xs : Vector α 0)
(draws : (i : Fin 0) → Fin (0 - i.val)) :
randomlyPermute xs draws = ⟨xs, (0, 0)⟩
public def randomlyPermute {α : Type u} {n : Nat} (xs : Vector α n)
(draws : (i : Fin n) → Fin (n - i.val)) :
Cslib.Algorithms.Lean.TimeM (Nat × Nat) (Vector α n) :=
scan draws 0 xs
CSLibExt/Algorithms/Lean/Random/RandomlyPermute/Basic.lean:53
Uses 1
TheoremFisher–Yates on a one-element vector#✓fully proved
For every vector \(x\) of length \(1\) and every draw history \(d\), the Fisher–Yates computation returns \(x\) unchanged with cost pair \((1, 1)\): it still retrieves the single draw \(d_0 = 0\) and performs the self-swap at position \(0\).
public theorem randomlyPermute_singleton {α : Type u} (xs : Vector α 1)
(draws : (i : Fin 1) → Fin (1 - i.val)) :
randomlyPermute xs draws = ⟨xs, (1, 1)⟩
public def randomlyPermute {α : Type u} {n : Nat} (xs : Vector α n)
(draws : (i : Fin n) → Fin (n - i.val)) :
Cslib.Algorithms.Lean.TimeM (Nat × Nat) (Vector α n) :=
scan draws 0 xs
CSLibExt/Algorithms/Lean/Random/RandomlyPermute/Basic.lean:53
Uses 2
DefinitionOutput distribution of the Fisher–Yates shuffle#✓fully proved
For a vector \(x\) of length \(n\), this is the probability distribution on outcomes (a vector of length \(n\) together with a cost pair in \(\mathbb{N} \times \mathbb{N}\)) obtained by choosing a draw history \(d\) uniformly at random among all assignments of a number \(d_i\) with \(0 \le d_i < n - i\) to each position \(i < n\), and returning the result of the Fisher–Yates computation on \(x\) and \(d\).
This models an ideal source of random draws, not an executable pseudorandom generator, and does not assume that the output permutation is uniform.
public def randomlyPermuteDistribution {α : Type u} {n : Nat}
(xs : Vector α n) : PMF (Cslib.Algorithms.Lean.TimeM (Nat × Nat) (Vector α n)) := by
letI : Nonempty ((i : Fin n) → Fin (n - i.val)) := ⟨fun i => ⟨0, by omega⟩⟩
exact (Cslib.Probability.PMF.uniformOfFintype ((i : Fin n) → Fin (n - i.val))).map
(randomlyPermute xs)
Uses 1 · Used by 2
TheoremProbability of a Fisher–Yates outcome#✓fully proved
Let \(x\) be a vector of length \(n\), and consider the draw histories \(d\), which assign to each position \(i < n\) a number \(d_i\) with \(0 \le d_i < n - i\). For every outcome \(o\) (a vector of length \(n\) together with a cost pair), the probability that the Fisher–Yates output distribution of \(x\) assigns to \(o\) equals the sum, over all draw histories \(d\) for which the Fisher–Yates computation on \(x\) and \(d\) yields exactly \(o\), of \(\prod_{i=0}^{n-1} \frac{1}{n - i}\).
Distinct histories that yield the same outcome (for example when values of \(x\) repeat) add up, and an outcome that no history yields has probability \(0\).
public theorem randomlyPermuteDistribution_apply {α : Type u} {n : Nat} (xs : Vector α n)
(out : Cslib.Algorithms.Lean.TimeM (Nat × Nat) (Vector α n)) :
randomlyPermuteDistribution xs out =
∑ draws : (i : Fin n) → Fin (n - i.val),
if out = randomlyPermute xs draws
then ∏ i : Fin n, ((n - i.val : Nat) : ENNReal)⁻¹ else 0
public def randomlyPermuteDistribution {α : Type u} {n : Nat}
(xs : Vector α n) : PMF (Cslib.Algorithms.Lean.TimeM (Nat × Nat) (Vector α n)) := by
letI : Nonempty ((i : Fin n) → Fin (n - i.val)) := ⟨fun i => ⟨0, by omega⟩⟩
exact (Cslib.Probability.PMF.uniformOfFintype ((i : Fin n) → Fin (n - i.val))).map
(randomlyPermute xs)
CSLibExt/Algorithms/Lean/Random/RandomlyPermute/Probability.lean:233
public def randomlyPermute {α : Type u} {n : Nat} (xs : Vector α n)
(draws : (i : Fin n) → Fin (n - i.val)) :
Cslib.Algorithms.Lean.TimeM (Nat × Nat) (Vector α n) :=
scan draws 0 xs
CSLibExt/Algorithms/Lean/Random/RandomlyPermute/Basic.lean:53
Uses 2
TheoremFisher–Yates produces a uniformly random permutation#✓fully proved
Let \(x = (x_0, \dots, x_{n-1})\) be a vector and let \(\sigma\) be a permutation of \(\{0, \dots, n - 1\}\) chosen uniformly at random. Then the Fisher–Yates output distribution of \(x\) equals the distribution of the outcome consisting of the vector \((x_{\sigma(0)}, \dots, x_{\sigma(n-1)})\) with cost pair \((n, n)\).
public theorem randomlyPermuteDistribution_eq_map_uniform {α : Type u} {n : Nat}
(xs : Vector α n) :
randomlyPermuteDistribution xs =
(Cslib.Probability.PMF.uniformOfFintype (Equiv.Perm (Fin n))).map
(fun σ => (⟨Vector.ofFn (fun p => xs[(σ p).val]), (n, n)⟩ :
Cslib.Algorithms.Lean.TimeM (Nat × Nat) (Vector α n)))
public def randomlyPermuteDistribution {α : Type u} {n : Nat}
(xs : Vector α n) : PMF (Cslib.Algorithms.Lean.TimeM (Nat × Nat) (Vector α n)) := by
letI : Nonempty ((i : Fin n) → Fin (n - i.val)) := ⟨fun i => ⟨0, by omega⟩⟩
exact (Cslib.Probability.PMF.uniformOfFintype ((i : Fin n) → Fin (n - i.val))).map
(randomlyPermute xs)
CSLibExt/Algorithms/Lean/Random/RandomlyPermute/Probability.lean:233
Uses 3
TheoremPrefix probabilities of the Fisher–Yates shuffle#✓fully proved
Let \(k \le n\) and let \(a_0, \dots, a_{k-1}\) be distinct elements of \(\{0, \dots, n - 1\}\). Run the Fisher–Yates computation on the vector \((0, 1, \dots, n - 1)\) with every draw history \(d\), which assigns to each position \(i < n\) a number \(d_i\) with \(0 \le d_i < n - i\), and give each history weight \(1/n!\). Then the total weight of the histories whose output vector has entry \(a_p\) at position \(p\) for every \(p < k\) equals \((n - k)!/n!\).
There are exactly \(n!\) draw histories, so this is the probability of the prefix under a uniformly random history. The cases \(k = 0\) and \(k = n\) are included.
public theorem randomlyPermute_prefix_probability {n k : Nat} (hkn : k ≤ n)
(a : Fin k → Fin n) (hinj : Function.Injective a) :
(∑ draws : (i : Fin n) → Fin (n - i.val),
if ∀ p : Fin k,
(randomlyPermute (Vector.ofFn fun q : Fin n => q) draws).ret[(Fin.castLE hkn p).val] = a p
then (n.factorial : ENNReal)⁻¹ else 0) =
((n - k).factorial : ENNReal) / n.factorial
public def randomlyPermute {α : Type u} {n : Nat} (xs : Vector α n)
(draws : (i : Fin n) → Fin (n - i.val)) :
Cslib.Algorithms.Lean.TimeM (Nat × Nat) (Vector α n) :=
scan draws 0 xs
CSLibExt/Algorithms/Lean/Random/RandomlyPermute/Basic.lean:53
Uses 2
Loading dependency explorer…
The dependency explorer needs JavaScript and its graph data.