module Data.Nat.Range where
private variable
: Nat x y
Ranges of natural numbers🔗
This module contains various tools for working with contiguous ranges of natural numbers. We are mainly interested in ranges as something to iterate over when performing other constructions. For example, the factorial function
can be expressed as taking the product of all numbers in the range
We will focus our attention on half-open ranges of numbers Ranges of this form have a couple of nice properties:
- If is a list, then gives us all valid indices into If we used a closed range, then we’d have to subtract 1 from the length of to get the same list.
- If we have two half-open ranges and with then we can directly concatenate them to get the range
- All the ranges and can be expressed as a half-open range using only successors. In contrast, deriving from any of the other range types requires us to use predecessors, which are much more annoying to reason about.
Unfortunately, ranges are a bit annoying to construct directly via structural recursion on and The recurrence relation that defines is:
Note that
is increasing and
is constant in this recurrence relation, so Agda’s termination checker
will reject this definition. Instead, the termination metric is
which decreases by 1 every step. We could use well-founded induction to define
but luckily there is a simpler solution: we can define an auxiliary
function count-up
that computes the
range
and then define
as count-up x (y - x)
.
: Nat → Nat → List Nat
count-up = []
count-up x zero (suc n) = x ∷ count-up (suc x) n
count-up x
: Nat → Nat → List Nat
range = count-up x (y - x) range x y
We can prove that range
satisfies our recurrence relation via some elementary results about
monus.
: ∀ {x y} → .(y ≤ x) → range x y ≡ []
range-≥-empty {x} {y} y≤x =
range-≥-empty (y - x) ≡⟨ ap (count-up x) (monus-≤-zero y x y≤x) ⟩
count-up x 0 ≡⟨⟩
count-up x
[] ∎
: .(x < y) → range x y ≡ x ∷ range (suc x) y
range-<-∷ {x} {suc y} x<y =
range-<-∷ (suc y - x) ≡⟨ ap (count-up x) (monus-≤-suc x y (≤-peel x<y)) ⟩
count-up x (suc (y - x)) ≡⟨⟩
count-up x (suc x) (y - x) ∎ x ∷ count-up
Properties of half-open ranges🔗
The length of is
: ∀ (x n : Nat) → length (count-up x n) ≡ n
length-count-up = refl
length-count-up x zero (suc n) = ap suc (length-count-up (suc x) n)
length-count-up x
: ∀ (x y : Nat) → length (range x y) ≡ y - x
length-range = length-count-up x (y - x) length-range x y
The range is the singleton list
: ∀ (x : Nat) → range x (suc x) ≡ x ∷ []
range-single =
range-single x (suc x) ≡⟨ range-<-∷ ≤-refl ⟩
range x (suc x) (suc x) ≡⟨ ap (x ∷_) (range-≥-empty ≤-refl) ⟩
x ∷ range x ∷ [] ∎
If and then
First, an auxiliary lemma: if we count m
numbers up from
x
, and then count n
numbers up from
x + m
, then this is the same as counting m + n
numbers up from x
.
: ∀ (x m n : Nat) → count-up x m ++ count-up (x + m) n ≡ count-up x (m + n)
count-up-++ =
count-up-++ x zero n 0 ⌝ n ≡⟨ ap! (+-zeror x) ⟩
count-up ⌜ x +
count-up x n ∎(suc m) n =
count-up-++ x (count-up (suc x) m ++ count-up ⌜ x + suc m ⌝ n) ≡⟨ ap! (+-sucr x m) ⟩
x ∷ (count-up (suc x) m ++ count-up (suc x + m) n) ≡⟨ ap (x ∷_) (count-up-++ (suc x) m n) ⟩
x ∷ (suc m + n) ∎ count-up x
Our result on ranges follows immediately from this count-up-index-of after we do some monus munging.
: ∀ {x z : Nat} → (y : Nat) → x ≤ y → y ≤ z → range x y ++ range y z ≡ range x z
range-++ {x = x} {z = z} y x≤y y≤z =
range-++ (y - x) ++ count-up ⌜ y ⌝ (z - y) ≡⟨ ap! (sym (monus-+l-inverse x y x≤y)) ⟩
count-up x (y - x) ++ count-up (x + (y - x)) (z - y) ≡⟨ count-up-++ x (y - x) (z - y) ⟩
count-up x (y - x) + (z - y) ⌝ ≡⟨ ap! (monus-cancel-outer x y z x≤y y≤z) ⟩
count-up x ⌜ (z - x) ∎ count-up x
: x ≤ y → range x (suc y) ≡ range x y ∷r y
range-∷r {x = x} {y = y} x≤y =
range-∷r (suc y) ≡˘⟨ range-++ y x≤y ≤-ascend ⟩
range x (suc y) ⌝ ≡⟨ ap! (range-single y) ⟩
range x y ++ ⌜ range y range x y ∷r y ∎
If is nonempty, then
: ∀ {x y} → is-nonempty (range x y) → x < y
nonempty-range→< {x = x} {y = y} ne with holds? (x < y)
nonempty-range→< ... | yes x<y = x<y
... | no ¬x<y =
(is-nonempty→not-empty ne (range-≥-empty (≤-from-not-< x y ¬x<y))) absurd
Membership in half-open ranges🔗
If then
count-up-lower: ∀ {x n i}
→ i ∈ count-up x n
→ x ≤ i
{n = suc n} (here i=x) = ≤-reflᵢ (symᵢ i=x)
count-up-lower {n = suc n} (there i∈xn) = <-weaken (count-up-lower i∈xn)
count-up-lower
range-lower: ∀ {x y i : Nat}
→ i ∈ range x y
→ x ≤ i
= count-up-lower x∈ij range-lower x∈ij
Likewise, if
then
The argument here is a bit trickier than the previous one due
to some annoying monus manipulation. A short inductive argument shows us
that i ∈ count-up x n
implies that
i < n + x
. To transfer this to range x y
,
we need to show that
which is only true when
We can discharge this side condition by observing that if
then
must be nonempty, and thus
count-up-upper: ∀ {x n i : Nat}
→ i ∈ count-up x n
→ i < n + x
{x = x} {n = suc n} {i = i} (here i=x) =
count-up-upper (≤-trans (≤-reflᵢ i=x) (+-≤r n x))
s≤s {x = x} {n = suc n} {i = i} (there i∈xy) =
count-up-upper (count-up-upper i∈xy) (≤-refl' (+-sucr n x))
≤-trans
range-upper: ∀ {x y i : Nat}
→ i ∈ range x y
→ i < y
{x = x} {y = y} {i = i} i∈xy =
range-upper (count-up-upper i∈xy) $ ≤-refl' $ᵢ
≤-trans
monus-+r-inverse y x $ᵢ(nonempty-range→< (has-member→nonempty i∈xy)) <-weaken
Conversely, if then
count-up-∈: ∀ {x n i}
→ .(x ≤ i) → .(i < n + x)
→ i ∈ count-up x n
{x = x} {n = zero} {i = i} x≤i i<n+x =
count-up-∈ (<-irrefl refl (≤-trans i<n+x x≤i))
absurd {x = x} {n = suc n} {i = i} x≤i i<n+x with ≤-strengthen x≤i
count-up-∈ ... | inl x=i = here (Equiv.from Id≃path (sym x=i))
... | inr x<i = there (count-up-∈ x<i (≤-trans i<n+x (≤-refl' (sym (+-sucr n x)))))
range-∈: ∀ {x y i}
→ .(x ≤ i) → .(i < y)
→ i ∈ range x y
{x = x} {y = y} {i = i} x≤i i<y =
range-∈
count-up-∈ x≤i $ᵢ ≤-trans i<y $ ≤-refl' $ᵢ sym $(≤-trans x≤i (<-weaken i<y)) monus-+r-inverse y x
Next, observe that if then the index of in is
count-up-index-of: ∀ {x n i : Nat}
→ (i∈xn : i ∈ count-up x n)
→ index-of i∈xn ≡ i - x
{x = x} {n = suc n} {i = i} (here i=x) =
count-up-index-of 0 ≡˘⟨ monus-≤-zero i x (≤-reflᵢ i=x) ⟩
i - x ∎{x = x} {n = suc n} {i = i} (there i∈xn) =
count-up-index-of (index-of i∈xn) ≡⟨ ap suc (count-up-index-of i∈xn) ⟩
suc 1 + (i - (1 + x)) ≡˘⟨ monus-pres-+l 1 i (1 + x) (count-up-lower i∈xn) ⟩
(1 + i) - (1 + x) ≡⟨ monus-cancell 1 i x ⟩
i - x ∎
range-index-of: ∀ {x y i : Nat}
→ (i∈xn : i ∈ range x y)
→ index-of i∈xn ≡ i - x
= count-up-index-of range-index-of
This means that is a proposition, as any two possible indices of must both be equal to
range-∈-is-prop: ∀ (x y i : Nat)
→ is-prop (i ∈ range x y)
=
range-∈-is-prop x y i i∈xy i∈xy' .injective member≃lookup $ Σ-prop-path! $ fin-ap $
Equiv
index-of i∈xy ≡⟨ range-index-of i∈xy ⟩
i - x ≡˘⟨ range-index-of i∈xy' ⟩ index-of i∈xy' ∎
This is the last ingredient we need to prove that is equivalent to
range-∈≃bounded: ∀ (x y i : Nat)
→ (i ∈ range x y) ≃ (x ≤ i × i < y)
=
range-∈≃bounded x y i (range-∈-is-prop x y i) (hlevel 1)
prop-ext (λ i∈xy → range-lower i∈xy , range-upper i∈xy)
(λ x≤i<y → range-∈ (x≤i<y .fst) (x≤i<y .snd))