10 Arrays
Arrays are similar to the sequences we’ve encountered earlier, but with one important distinction: arrays are mutable, meaning their elements can be changed. Before we delve into algorithms that operate on arrays, let’s first revisit the differences between arrays and sequences.
10.1 Array vs Sequence
The (fixed) size of an array a is given by a.Length, whereas the (fixed) size of a sequence s is given by |s|.
The elements of arrays and sequences are accessed in the same way.
y := a[i]; y := s[i];
A sequence is entirely characterized by its length and its elements.
But that’s not true for arrays. Each array has an identity. In other words, an array is a reference to a sequence of elements. The follwoing assertion is true for sequences, but not necessarily for arrays.if |s| == |t| && forall k :: 0 <= k < |s| ==> s[k] == t[k] { assert s == t; }
if a.Length == b.Length && forall k :: 0 <= k < a.Length ==> a[k] == b[k] { assert a == b; }
One can construct a sequence from a range of array elements. The general form is a[i..j], which says to take the first j elements of the array and then drop the first i elements. If i is omitted, if defaults to 0, and if j is omitted, it defaults to a.Length. Omitting both, the expression a[..] is the sequence of all elements of the array.
var u: seq<X>; u := a[i..j];0..j]; u := a[assert u == a[..j]; u := a[i..a.Length];assert u == a[i..]; 0..a.Length]; u := a[assert u == a[..];
To create a new array, you must use a new assignment. Since this is a statement, it can only appear within method bodies—not function bodies. The elements of the new array are initialized using a specified function that maps indices to values, similar to how sequences are constructed. This initialization can be omitted if the array size is zero or if the element type is an auto-init type—one for which the compiler can automatically generate default values. Primitive types like int, real, bool, char, and bitvectors are generally auto-init types.
seq(100, i => x); u := var c: array<X> := new X[100](i => x); var a: array<int> := new int[100];
Unlike sequences, arrays can be multidimensional. Here is an example of a 2-dimensional array, or a matrix.
var m: array2<real> := new real[20, 30]; var z := m[12, 22]; assert m.Length0 == 20 && m.Length1 == 30;
10.2 Binary Search
Our first algorithm is a classic in computer science: binary search. It derives its name from the way it repeatedly halves the search space with each iteration. As a result, its running time is logarithmic relative to the length of the array. For binary search to work correctly, the input array must be sorted in advance.

Dafny
datatype Option<X> = None | Some(value: X) method BinarySearch(a: array<int>, key: int) returns (r: Option<int>) requires forall i, j :: 0 <= i < j < a.Length ==> a[i] <= a[j] ensures r.Some? ==> 0 <= r.value < a.Length && a[r.value] == key ensures r == None ==> key !in a[..] {var lo, hi := 0, a.Length; while lo < hi invariant 0 <= lo <= hi <= a.Length invariant key !in a[..lo] && key !in a[hi..] {var m := lo + (hi - lo) / 2; if key < a[m] { hi := m;else if a[m] < key { } 1; lo := m + else { } Some(m); return } }None; return }
10.3 Find Minimum
Our next program is a straightforward linear scan to find the minimum value of an array. We’ll gradually build up the specification and program. We will use a loop index a, which starts from 0 and goes up to a.Length.
We form the loop invariant by replacing the constant ‘a.Length‘ in the postcondition with the variable ‘n‘. This loop invariant speaks of "what we’ve computed so far", which is one of many common forms of loop invariants.
From the assignment ‘n := n + 1‘, we can "compute backwards" (as we’ve seen in the previous lectures) to get a strong hint of how to fill in the loop body.
Dafny
method FindMin(a: array<int>) returns (m: int) requires a.Length != 0 ensures forall i :: 0 <= i < a.Length ==> m <= a[i] ensures m in a[..] {0]; m := a[var n := 1; while n < a.Length invariant 1 <= n <= a.Length invariant forall i :: 0 <= i < n ==> m <= a[i] invariant m in a[..] {if a[n] < m { m := a[n]; }1; n := n + } }method TestFindMin(){ var a := new int[5]; 0], a[1], a[2], a[3], a[4] := 9, 4, 6, 3, 8 ; a[assert a[0] == 9 && a[1] == 4 && a[2] == 6 && a[3] == 3 && a[4] == 8; //var m := FindMin(a); assert m == 3; }
10.4 Find Zero
Dafny
method FindZero'(a: array<int>) returns (index: int) ensures index < 0 ==> forall i :: 0 <= i < a.Length ==> a[i] != 0 ensures 0 <= index ==> index < a.Length && a[index] == 0 {0; index := while index < a.Length invariant 0 <= index <= a.Length invariant forall k :: 0 <= k < index ==> a[k] != 0 {if a[index] == 0 { return; }1; index := index + }-1; index := } method TestFind0() { var a: array<int> := new int[5][9, 14, 6, 0, 8]; assert a[0] == 9 && a[1] == 14 && a[2] == 6 && a[3] == 0 && a[4] == 8; var r :=FindZero(a); assert forall i :: 0 <= i < 3 ==> a[i] != 0; assert r == 3; }
10.5 Updating Arrays
Dafny
method SetEndPoints(a: array<int>, left: int, right: int) requires a.Length != 0 modifies a ensures a.Length > 1 ==> a[0] == left ensures a[a.Length-1]==right {0] := left; a[assert a[0] == left; 1] := right; a[a.Length - assert a[a.Length-1]==right; }method TestSetEndPoints() { var a: array<int> := new int[5][9, 14, 6, 0, 8]; assert a[0] == 9 && a[1] == 14 && a[2] == 6 && a[3] == 0 && a[4] == 8; 0,100); SetEndPoints(a,assert a[0] == 0; assert a[4] == 100; }
Dafny
method Aliases(a: array<int>, b: array<int>) requires 100 <= a.Length modifies a {0] := 100; a[var c := a; if b == a { 10] := b[0] + 1; b[ }20] := a[14] + 2; c[ }
Dafny
method UpdateElements(a: array<int>) requires a.Length == 10 modifies a ensures old(a[4]) < a[4] ensures a[6] <= old(a[6]) ensures a[8] == old(a[8]) {4], a[8] := a[4] + 3, a[8] + 1; a[7], a[8] := 516, a[8] - 1; a[ }
Dafny
method OldVsParameters(a: array<int>, i: int) returns (y: int) requires 0 <= i < a.Length modifies a ensures old(a[i] + y) == 25 ensures old(a[i]) + y == 25 //
Dafny
method Increment(a: array<int>, i: int) requires 0 <= i < a.Length modifies a ensures a[i] == old(a)[i] + 1 ensures a[i] == old(a[i]) + 1 // {1; a[i] := a[i] + }
Dafny
method NewArray() returns (a: array<int>) ensures a.Length == 20 {new int[20]; a := var b := new int[30]; 6] := 216; a[7] := 343; b[ }
Dafny
method NewArray'() returns (a: array<int>) ensures a.Length == 20 ensures fresh(a) {new int[20]; a := var b := new int[30]; 6] := 216; a[7] := 343; b[ } method Caller() { var a := NewArray'(); 8] := 512; a[ }
Dafny
predicate IsZeroArray(a: array<int>, lo: int, hi: int) requires 0 <= lo <= hi <= a.Length reads a decreases hi - lo {0 && IsZeroArray(a, lo + 1, hi)) lo == hi || (a[lo] == }
Dafny
predicate IsZeroSeq(a: seq<int>, lo: int, hi: int) requires 0 <= lo <= hi <= |a| decreases hi - lo {0 && IsZeroSeq(a, lo + 1, hi)) lo == hi || (a[lo] == }
Dafny
method InitArray<T>(a: array<T>, d: T) modifies a ensures forall i :: 0 <= i < a.Length ==> a[i] == d {var n := 0; while n != a.Length invariant 0 <= n <= a.Length invariant forall i :: 0 <= i < n ==> a[i] == d { a[n] := d;1; n := n + } }
Dafny
method InitMatrix<T>(a: array2<T>, d: T) modifies a ensures forall i, j :: 0 <= i < a.Length0 && 0 <= j < a.Length1 ==> a[i, j] == d {var m := 0; while m != a.Length0 invariant 0 <= m <= a.Length0 invariant forall i, j :: 0 <= i < m && 0 <= j < a.Length1 ==> a[i, j] == d {var n := 0; while n != a.Length1 invariant 0 <= n <= a.Length1 invariant forall i, j :: 0 <= i < m && 0 <= j < a.Length1 ==> a[i, j] == dinvariant forall j :: 0 <= j < n ==> a[m, j] == d { a[m, n] := d;1; n := n + } 1; m := m + } }
Dafny
method IncrementArray(a: array<int>) modifies a ensures forall i :: 0 <= i < a.Length ==> a[i] == old(a[i]) + 1 {var n := 0; while n != a.Length invariant 0 <= n <= a.Length invariant forall i :: 0 <= i < n ==> a[i] == old(a[i]) + 1 invariant forall i :: n <= i < a.Length ==> a[i] == old(a[i]) { // The sequence of 'assert' commands here documents how we realize the needfor the final loop invariant. // assert a[n] + 1 == old(a[n]) + 1; 1; a[n] := a[n] + assert forall i :: 0 <= i < n ==> a[i] == old(a[i]) + 1; assert a[n] == old(a[n]) + 1; assert forall i :: 0 <= i < n + 1 ==> a[i] == old(a[i]) + 1; 1; n := n + assert forall i :: 0 <= i < n ==> a[i] == old(a[i]) + 1; } }
Dafny
method CopyArray<T>(src: array<T>, dst: array<T>) requires src.Length == dst.Length modifies dst ensures forall i :: 0 <= i < src.Length ==> dst[i] == old(src[i]) ensures dst[..] == old(src[..]) {var n := 0; while n != src.Length invariant 0 <= n <= src.Length invariant forall i :: 0 <= i < n ==> dst[i] == old(src[i]) invariant forall i :: 0 <= i < src.Length ==> src[i] == old(src[i]) { dst[n] := src[n];1; n := n + } } // Or using the aggregates:method CopyArray'<T>(src: array<T>, dst: array<T>) requires src.Length == dst.Length modifies dst ensures forall i :: 0 <= i < src.Length ==> dst[i] == old(src[i]) ensures dst[..] == old(src[..]) {forall i| 0 <= i <src.Length{ dst[i]:=src[i]; } }
Dafny
method MakeSomeArrays(F: int -> int, n: nat, d: int) {var a1 := new int[25](F); var a2 := new int[25](i => i * i); var a3 := new int[n](_ => 0); var a4 := new int[50, 50]((i, j) => if i == j then d else 0); var b := new int[a1.Length](i requires 0 <= i < a1.Length reads a1 => a1[i]); // Note how this last example needs precondition information on an anonymousfunction, to make an array access legal. // }
Dafny
method MakeASequence() {var s : seq<bool> := seq(500, i => i % 2 == 0); }
Dafny
method Aggregate1(a: array<int>) ensures forall i :: 0 <= i < a.Length ==> a[i] == old(a[i]) + i * i modifies a {forall i | 0 <= i < a.Length { a[i] := a[i] + i * i; } } method Aggregate2(a: array<int>) ensures forall i :: 0 <= i < a.Length ==> a[i] == if i % 2 == 0 then old(a[i]) + 1 else old(a[i]) modifies a {forall i | 0 <= i < a.Length && i % 2 == 0 { 1; a[i] := a[i] + } } method Aggregate3<T>(a: array2<T>, d: T) ensures forall i, j :: 0 <= i < a.Length0 && 0 <= j < a.Length1 ==> a[i, j] == d modifies a {forall i, j | 0 <= i < a.Length0 && 0 <= j < a.Length1 { a[i, j] := d; } }
10.6 Dutch national flag

Dafny
datatype Color = Red | White | Blue ghost predicate Below(c: Color, d: Color) { c == Red || c == d || d == Blue } method DutchFlag(a: array<Color>) modifies a array is sorted. // the final ensures forall i, j :: 0 <= i < j < a.Length ==> Below(a[i], a[j]) "after" state of the array is a permutation of "before" // ensures multiset(a[..]) == old(multiset(a[..])) {var r, w, b := 0, 0, a.Length; while w < b invariant 0 <= r <= w <= b <= a.Length array is divided into four regions // The invariant forall i :: 0 <= i < r ==> a[i] == Red // The first region is all red.invariant forall i :: r <= i < w ==> a[i] == White // The second region is all white.invariant forall i :: b <= i < a.Length ==> a[i] == Blue // The fourth region is all blue.in there. // We don't know what's in the third region. // When the loop begins, all cells are invariant multiset(a[..]) == old(multiset(a[..])) {and adjustment region boundaries // swap match a[w] case Red => a[r], a[w] := a[w], a[r];1, w + 1; r, w := r + case White => 1; w := w + case Blue => -1] := a[b-1], a[w]; a[w], a[b1; b := b - } }
10.7 Selection Sort
Dafny
method SelectionSort(a: array<int>) modifies a ensures forall i, j :: 0 <= i < j < a.Length ==> a[i] <= a[j] ensures multiset(a[..]) == old(multiset(a[..])) {var n := 0; while n != a.Length invariant 0 <= n <= a.Length invariant forall i, j :: 0 <= i < j < n ==> a[i] <= a[j] or equal to all // all elements already sorted are less than not sorted yet. // elements invariant forall i, j :: 0 <= i < n <= j < a.Length ==> a[i] <= a[j] invariant multiset(a[..]) == old(multiset(a[..])) {var mindex, m := n, n + 1; while m != a.Length invariant n <= mindex < m <= a.Length invariant forall i :: n <= i < m ==> a[mindex] <= a[i] {if a[m] < a[mindex] { mindex := m; }1; m := m + } a[n], a[mindex] := a[mindex], a[n];1; n := n + } }
Dafny
predicate Ordered(a: array<int>, left: nat, right: nat) reads a requires left <= right <= a.Length {forall i: nat :: 0 < left <= i < right ==> a[i-1] <= a[i] } method SelectionSort'(a: array<int>) modifies a ensures Ordered(a,0,a.Length) ensures multiset(a[..]) == old(multiset(a[..])) {for i := 0 to a.Length invariant 0 <= i <= a.Length invariant Ordered(a,0,i) invariant multiset(a[..]) == old(multiset(a[..])) {var minValue := a[i]; var mindex := i; for j:= i + 1 to a.Length invariant mindex < a.Length {if a[j] < minValue { mindex := j; } }if i != mindex { a[i], a[mindex] := a[mindex], a[i]; } } }
10.8 Optional Reading: Monotonicity Lemma
Dafny
lemma MonotoneBetween(c: seq<int>, i: int, j: int) // adjacent elements are sortedrequires forall t :: 0 <= t < |c| - 1 ==> c[t] <= c[t+1] requires 0 <= i < j < |c| ensures c[i] <= c[j] {if j == i + 1 { case: follows directly from the requires // base else { } for (i, j-1) and extend to (i, j) // inductive step: prove -1); MonotoneBetween(c, i, jassert c[i] <= c[j-1]; assert c[j-1] <= c[j]; // from requires assert c[i] <= c[j]; // chain } } // The general statementlemma monotonic(c: seq<int>) requires forall t :: 0 <= t < |c| - 1 ==> c[t] <= c[t+1] ensures forall i, j :: 0 <= i < j < |c| ==> c[i] <= c[j] {forall i, j | 0 <= i < j < |c| ensures c[i] <= c[j] { MonotoneBetween(c, i, j); } }
10.9 Optional Reading: Insertion Sort
Dafny
predicate sorted(a: array<int>, l: int, u: int) reads a requires 0 <=l <=u <= a.Length {forall i, j :: l <= i < j < u ==> a[i] <= a[j] }
Shuffling: The element being inserted is repeatedly swapped with its preceding element as long as that element is larger. This process continues until the new element reaches its proper position in the sorted portion. It requires multiple explicit swap operations.
Shifting: Each element in the sorted portion that is larger than the one being inserted is moved one position to the right, creating space for the new element. The new element is then placed in the correct position. This approach uses a temporary variable and avoids repeated swaps.
10.9.1 Shuffling
[3, 10, 8, 2, 7] →
[3, 8, 10, 2, 7] →
[3, 8, 2, 10, 7] →
[3, 2, 8, 10, 7] →
[2, 3, 8, 10, 7] →
[2, 3, 8, 7, 10]
multiset(a[..]) == multiset(old(a[..]))
Dafny
method insertion_sort_shuffle(a: array<int>) modifies a requires a.Length >= 2 ensures sorted(a, 0,a.Length) ensures multiset(a[..]) == multiset(old(a[..])) {var i:= 1; while i < a.Length invariant 1 <= i <= a.Length invariant sorted(a, 0, i) invariant multiset(a[..]) == multiset(old(a[..])) {var j := i; while j >= 1 && a[j-1] > a[j] invariant 0 <= j <= i invariant forall k,l:: 0 <= k < l <= i && j != l ==> a[k] <= a[l]invariant multiset(a[..]) == multiset(old(a[..])) {-1], a[j] := a[j], a[j-1]; a[j1; j := j - }1; i := i + } }
10.9.2 Shifting
2 into the correct position.
Let us insert item [3, 8, 10, 2, 7] →
= 2 //remember the current item
key [3, 8, 10, 10, 7] → //move everything that is greater than key
[3, 8, 8, 10, 7] →
[3, 3, 8, 10, 7] →
, swap key 2 with the current position
now[2, 3, 8, 10, 7]
multiset(a[..]) == multiset(old(a[..])) // does not hold, but
multiset(a[..][j:=key]) == multiset(old(a[..])) // holds.
Dafny
method insertion_sort_swap(a: array<int>) modifies a ensures sorted(a,0,a.Length) requires 0 < a.Length ensures multiset(a[..])==multiset(old(a[..]) ) requires forall k::0 <=k < a.Length ==> 1< a[k]< 15 // {var i := 1; while i < a.Length invariant 1 <= i <= a.Length invariant sorted(a,0,i) invariant multiset(a[..]) == multiset(old(a[..])) {var key := a[i]; var j := i; while j > 0 && a[j-1] > key invariant sorted(a,0,i) invariant forall k :: j <= k <= i ==> key <= a[k] invariant i == j || sorted(a,0,i+1) invariant multiset(a[..][j:=key]) == multiset(old(a[..])) {-1]; a[j] := a[j1; j := j - }assert sorted(a,0,i+1); a[j] := key;1; i := i + } }
Dafny
method Main() { var a := new int[5][9, 14, 6, 3, 8]; insertion_sort_swap(a);var k := 0; while(k < a.Length) { print a[k], ","; k := k+1;} print "\n"; var b:=new int[][1,4,8,3,2]; insertion_sort_shuffle(b);print (a[..]),"\n"; var c:=new int[][1,2,3,4,5]; var d:=multiset(c[..][2:=10]); // replace 3 with 10 print (d),"\n"; }
10.10 Optional Reading: Coincidence Count
Coincidence Count is the problem of computing how many elements are shared between two ordered arrays. Some variations of this problem assume that the arrays contain no duplicates. However, this implementation allows duplicate elements. For example, the Coincidence Count of the arrays [1, 1, 1, 2, 3, 3, 4] and [1, 1, 2, 2, 2, 4, 4] is 4, because they share four elements: two 1s, one 2, and one 4.
Dafny
lemma MultiSetInsertPrefix(a:array<int>, b:array<int>, nat, n:nat) m:requires m <a.Length && n < b.Length requires a[m] == b[n] ensures multiset(a[m..]) * multiset(b[n..]) == multiset{a[m]} + multiset(a[m+1..]) * multiset(b[n+1..]) {var E := multiset{a[m]}; calc{ multiset(a[m..]) * multiset(b[n..]); assert a[m..] == [a[m]] + a[m+1..] && {1..];} b[n..] == [b[n]] + b[n+multiset(a[m+1..])) * (E + multiset(b[n+1..])); (E + } }lemma MultisetIntersectionAdvance(a:array<int>, nat, b:multiset<int>) m:requires m < a.Length && a[m] !in b ensures multiset(a[m..]) * b == multiset(a[m+1..]) * b {var E := multiset{a[m]}; calc{ multiset(a[m..]) * b; assert a[m..] == [a[m]] + a[m+1..];} {multiset(a[m+1..])) * b; (E + multiset(a[m+1..]) * b; (E * b) + assert E * b == multiset{};} {multiset{} + multiset(a[m+1..]) * b; multiset(a[m+1..]) * b; } }method CoincidenceCount(a:array<int>, b:array<int>) returns (c:nat) requires forall i,j::0 <= i < j<a.Length ==> a[i]<=a[j] requires forall i,j::0 <= i < j<b.Length ==> b[i]<=b[j] ensures c == |multiset(a[..]) * multiset(b[..])| {var i,j:=0,0; 0; c := while i < a.Length && j < b.Length invariant 0 <= i <= a.Length invariant 0 <= j <= b.Length invariant c + |multiset(a[i..]) * multiset(b[j..])| == multiset(a[..]) * multiset(b[..])| | {var ai:= multiset(a[i..]); var bj := multiset(b[j..]); var ai2:=multiset(a[i+1..]); var bj2 := multiset(b[j+1..]); assert c + | ai * bj| == |multiset(a[..]) * multiset(b[..])|; if a[i] == b[j]{ assert c + |ai * bj| == |multiset(a[..]) * multiset(b[..])|; assert c + |multiset({a[i]})| + |ai2 * bj2 | multiset(a[..]) * multiset(b[..])| == | by {MultiSetInsertPrefix(a,b,i,j);}1; c:=c+1; i:=i + 1; j := j + else if a[i] < b[j]{ } MultisetIntersectionAdvance(a,i,bj);1; i := i +else if b[j] < a[i]{ }calc{ ai * bj; bj * ai; {MultisetIntersectionAdvance(b,j,ai);} bj2 * ai; ai * bj2; }1; j:= j+ } } }method TestCoincidenceCount(){ var a := new int[][1,1,1,2, 3,3,4]; var b := new int[][1,1,2,2,2,4,4]; var v:multiset<int> := multiset(a[..]) * multiset(b[..]); assert a[..] == [1,1,1,2,3,3,4]; assert b[..] == [1,1,2,2,2,4,4]; assert multiset(a[..]) * multiset(b[..]) == multiset([1,1,2,4]); var c:= CoincidenceCount(a,b); assert c == 4; }
decreases a.Length - m + b.Length - n
decreases a.Length - m, b.Length - n
decreases a.Length - m, if m < a.Length then b.Length - n else -1
Dafny
function InBoth(s: seq<int>, t: seq<int>): set<int> { set x | x in s && x in t }method CoincidenceCount1(a: array<int>, b: array<int>) returns (c: nat) requires forall i, j :: 0 <= i < j < a.Length ==> a[i] < a[j] requires forall i, j :: 0 <= i < j < b.Length ==> b[i] < b[j] ensures c == |InBoth(a[..], b[..])| {var m, n := 0, 0; 0; c := while m < a.Length && n < b.Length invariant 0 <= m <= a.Length && 0 <= n <= b.Length invariant |InBoth(a[..], b[..])| == c + |InBoth(a[m..], b[n..])| { if a[m] == b[n] { assert InBoth(a[m..], b[n..]) == 1..], b[n + 1..]); {a[m]} + InBoth(a[m + assert |InBoth(a[m..], b[n..])| == 1 + |InBoth(a[m + 1..], b[n + 1..])|; assert |InBoth(a[..], b[..])| == 1 + |InBoth(a[m + 1..], b[n + 1..])|; c + 1, m + 1, n + 1; c, m, n := c + assert |InBoth(a[..], b[..])| == c + |InBoth(a[m..], b[n..])|; else if a[m] < b[n] { } assert InBoth(a[m..], b[n..]) == InBoth(a[m + 1..], b[n..]); 1; m := m + else { } assert InBoth(a[m..], b[n..]) == InBoth(a[m..], b[n + 1..]); 1; n := n + } } }