Skip to content

Commit d24ece1

Browse files
authored
feat: String.toList_map (#11021)
This PR adds more theory about `Splits` for strings and deduces the first user-facing `String` lemma, `String.toList_map`.
1 parent faed852 commit d24ece1

File tree

9 files changed

+303
-18
lines changed

9 files changed

+303
-18
lines changed

doc/examples/palindromes.lean

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,8 @@ theorem List.palindrome_of_eq_reverse (h : as.reverse = as) : Palindrome as := b
9494
next => exact Palindrome.nil
9595
next a => exact Palindrome.single a
9696
next a b as ih =>
97-
have : a = b := by simp_all
98-
subst this
99-
have : as.reverse = as := by simp_all
100-
exact Palindrome.sandwich a (ih this)
97+
obtain ⟨rfl, h, -⟩ := by simpa using h
98+
exact Palindrome.sandwich b (ih h)
10199

102100
/-!
103101
We now define a function that returns `true` iff `as` is a palindrome.

src/Init/Data/Array/Lemmas.lean

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -245,26 +245,20 @@ theorem back_eq_of_push_eq {a b : α} {xs ys : Array α} (h : xs.push a = ys.pus
245245
replace h := List.append_inj_right' h (by simp)
246246
simpa using h
247247

248-
theorem pop_eq_of_push_eq {a b : α} {xs ys : Array α} (h : xs.push a = ys.push b) : xs = ys := by
248+
theorem push_eq_push {a b : α} {xs ys : Array α} : xs.push a = ys.push b ↔ a = b ∧ xs = ys := by
249249
cases xs
250250
cases ys
251-
simp at h
252-
replace h := List.append_inj_left' h (by simp)
253-
simp [h]
251+
simp [And.comm]
252+
253+
theorem pop_eq_of_push_eq {a b : α} {xs ys : Array α} (h : xs.push a = ys.push b) : xs = ys :=
254+
(push_eq_push.1 h).2
254255

255256
theorem push_inj_left {a : α} {xs ys : Array α} : xs.push a = ys.push a ↔ xs = ys :=
256257
⟨pop_eq_of_push_eq, fun h => by simp [h]⟩
257258

258259
theorem push_inj_right {a b : α} {xs : Array α} : xs.push a = xs.push b ↔ a = b :=
259260
⟨back_eq_of_push_eq, fun h => by simp [h]⟩
260261

261-
theorem push_eq_push {a b : α} {xs ys : Array α} : xs.push a = ys.push b ↔ a = b ∧ xs = ys := by
262-
constructor
263-
· intro h
264-
exact ⟨back_eq_of_push_eq h, pop_eq_of_push_eq h⟩
265-
· rintro ⟨rfl, rfl⟩
266-
rfl
267-
268262
theorem exists_push_of_ne_empty {xs : Array α} (h : xs ≠ #[]) :
269263
∃ (ys : Array α) (a : α), xs = ys.push a := by
270264
rcases xs with ⟨xs⟩

src/Init/Data/List/Lemmas.lean

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2494,6 +2494,9 @@ theorem flatten_reverse {L : List (List α)} :
24942494
by rw [length_reverse, length_replicate],
24952495
fun _ h => eq_of_mem_replicate (mem_reverse.1 h)⟩
24962496

2497+
@[simp]
2498+
theorem append_singleton_inj {as bs : List α} : as ++ [a] = bs ++ [b] ↔ as = bs ∧ a = b := by
2499+
rw [← List.reverse_inj, And.comm]; simp
24972500

24982501
/-! ### foldlM and foldrM -/
24992502

src/Init/Data/String/Basic.lean

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,7 @@ theorem utf8ByteSize_push {s : String} {c : Char} :
634634
(s.push c).utf8ByteSize = s.utf8ByteSize + c.utf8Size := by
635635
simp [← size_bytes, List.utf8Encode_singleton]
636636

637+
@[simp]
637638
theorem rawEndPos_push {s : String} {c : Char} : (s.push c).rawEndPos = s.rawEndPos + c := by
638639
simp [Pos.Raw.ext_iff]
639640

@@ -1130,6 +1131,11 @@ theorem Slice.Pos.get_eq_utf8DecodeChar {s : Slice} (pos : s.Pos) (h : pos ≠ s
11301131
pos.get h = s.str.bytes.utf8DecodeChar (s.startInclusive.offset.byteIdx + pos.offset.byteIdx)
11311132
((Pos.Raw.isValidForSlice_iff_isSome_utf8DecodeChar?.1 pos.isValidForSlice).elim (by simp_all [Pos.ext_iff]) (·.2)) := (rfl)
11321133

1134+
theorem Slice.Pos.utf8Encode_get_eq_extract {s : Slice} (pos : s.Pos) (h : pos ≠ s.endPos) :
1135+
List.utf8Encode [pos.get h] = s.str.bytes.extract (s.startInclusive.offset.byteIdx + pos.offset.byteIdx)
1136+
(s.startInclusive.offset.byteIdx + pos.offset.byteIdx + (pos.get h).utf8Size) := by
1137+
rw [get_eq_utf8DecodeChar pos h, List.utf8Encode_singleton, ByteArray.utf8EncodeChar_utf8DecodeChar]
1138+
11331139
/-- Returns the byte at the given position in the string, or `none` if the position is the end
11341140
position. -/
11351141
@[expose]
@@ -1495,6 +1501,14 @@ theorem Slice.Pos.lt_next {s : Slice} {pos : s.Pos} {h : pos ≠ s.endPos} :
14951501
pos < pos.next h := by
14961502
simp [Pos.lt_iff, Pos.Raw.lt_iff, Char.utf8Size_pos]
14971503

1504+
theorem Slice.Pos.copy_eq_copy_replaceEnd_append_get {s : Slice} {pos : s.Pos} (h : pos ≠ s.endPos) :
1505+
s.copy = (s.replaceEnd pos).copy ++ singleton (pos.get h) ++ (s.replaceStart (pos.next h)).copy := by
1506+
suffices (max (s.startInclusive.offset.byteIdx + (pos.offset.byteIdx + (pos.get h).utf8Size)) s.endExclusive.offset.byteIdx)
1507+
= s.endExclusive.offset.byteIdx by
1508+
simp [← bytes_inj, bytes_copy, utf8Encode_get_eq_extract, Nat.add_assoc, this]
1509+
rw [Nat.max_eq_right]
1510+
simpa [Pos.Raw.le_iff] using (pos.next h).offset_str_le_offset_endExclusive
1511+
14981512
@[inline, expose]
14991513
def Slice.Pos.prevAux {s : Slice} (pos : s.Pos) (h : pos ≠ s.startPos) : String.Pos.Raw :=
15001514
go (pos.offset.byteIdx - 1) (by
@@ -1861,10 +1875,13 @@ abbrev utf8SetAux (c' : Char) : List Char → Pos.Raw → Pos.Raw → List Char
18611875
Pos.Raw.utf8SetAux c'
18621876

18631877
@[simp]
1864-
theorem ValidPos.toSlice_get {s : String} {p : s.ValidPos} {h} :
1878+
theorem ValidPos.get_toSlice {s : String} {p : s.ValidPos} {h} :
18651879
p.toSlice.get h = p.get (ne_of_apply_ne (·.toSlice) (by simp_all)) := by
18661880
rfl
18671881

1882+
theorem ValidPos.get_eq_get_toSlice {s : String} {p : s.ValidPos} {h} :
1883+
p.get h = p.toSlice.get (ne_of_apply_ne Slice.Pos.ofSlice (by simp [h])) := rfl
1884+
18681885
@[simp]
18691886
theorem ValidPos.offset_next {s : String} (p : s.ValidPos) (h : p ≠ s.endValidPos) :
18701887
(p.next h).offset = p.offset + p.get h := by
@@ -1943,6 +1960,14 @@ theorem Pos.Raw.isValidForSlice_stringReplaceStart {s : String} {p : s.ValidPos}
19431960
rw [replaceStart, isValidForSlice_replaceStart, isValidForSlice_toSlice_iff,
19441961
ValidPos.offset_toSlice]
19451962

1963+
theorem ValidPos.utf8Encode_get_eq_extract {s : String} (pos : s.ValidPos) (h : pos ≠ s.endValidPos) :
1964+
List.utf8Encode [pos.get h] = s.bytes.extract pos.offset.byteIdx (pos.offset.byteIdx + (pos.get h).utf8Size) := by
1965+
rw [get_eq_get_toSlice, Slice.Pos.utf8Encode_get_eq_extract]
1966+
simp
1967+
1968+
theorem ValidPos.eq_copy_replaceEnd_append_get {s : String} {pos : s.ValidPos} (h : pos ≠ s.endValidPos) :
1969+
s = (s.replaceEnd pos).copy ++ singleton (pos.get h) ++ (s.replaceStart (pos.next h)).copy := by
1970+
simp [← bytes_inj, utf8Encode_get_eq_extract pos h, Slice.bytes_copy, ← size_bytes]
19461971

19471972
/--
19481973
Returns the next position in a string after position `p`. If `p` is not a valid position or

src/Init/Data/String/Lemmas.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module
77

88
prelude
99
public import Init.Data.String.Lemmas.Splits
10+
public import Init.Data.String.Lemmas.Modify
1011
public import Init.Data.Char.Order
1112
public import Init.Data.Char.Lemmas
1213
public import Init.Data.List.Lex
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/-
2+
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Author: Markus Himmel
5+
-/
6+
module
7+
8+
prelude
9+
public import Init.Data.String.Basic
10+
11+
/-!
12+
# Basic lemmas about strings
13+
14+
This file contains lemmas that could be in `Init.Data.String.Basic` but are not because they are
15+
not needed to define basic string operations.
16+
-/
17+
18+
public section
19+
20+
namespace String
21+
22+
@[simp]
23+
theorem singleton_append_inj : singleton c ++ s = singleton d ++ t ↔ c = d ∧ s = t := by
24+
simp [← toList_inj]
25+
26+
@[simp]
27+
theorem push_inj : push s c = push t d ↔ s = t ∧ c = d := by
28+
simp [← toList_inj]
29+
30+
@[simp]
31+
theorem append_eq_empty_iff {s t : String} : s ++ t = "" ↔ s = "" ∧ t = "" := by
32+
rw [← toList_inj]; simp
33+
34+
@[simp]
35+
theorem push_ne_empty : push s c ≠ "" := by
36+
rw [ne_eq, ← toList_inj]; simp
37+
38+
@[simp]
39+
theorem singleton_ne_empty {c : Char} : singleton c ≠ "" := by
40+
simp [singleton]
41+
42+
@[simp]
43+
theorem Slice.Pos.toCopy_inj {s : Slice} {p₁ p₂ : s.Pos} : p₁.toCopy = p₂.toCopy ↔ p₁ = p₂ := by
44+
simp [Pos.ext_iff, ValidPos.ext_iff]
45+
46+
end String
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/-
2+
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Author: Markus Himmel
5+
-/
6+
module
7+
8+
prelude
9+
public import Init.Data.String.Modify
10+
import all Init.Data.String.Modify
11+
import Init.Data.String.Lemmas.Basic
12+
13+
/-!
14+
# Lemmas for `Init.Data.String.Modify`
15+
16+
This file contains lemmas for the operations defined in `Init.Data.String.Modify`.
17+
18+
Note that `Init.Data.String.Modify` already proves a few lemmas which are needed immediately.
19+
-/
20+
21+
public section
22+
23+
namespace String
24+
25+
/-- You might want to invoke `ValidPos.Splits.exists_eq_singleton_append` to be able to apply this. -/
26+
theorem ValidPos.Splits.pastSet {s : String} {p : s.ValidPos} {t₁ t₂ : String}
27+
{c d : Char} (h : p.Splits t₁ (singleton c ++ t₂)) :
28+
(p.pastSet d h.ne_endValidPos_of_singleton).Splits (t₁ ++ singleton d) t₂ := by
29+
generalize h.ne_endValidPos_of_singleton = hp
30+
obtain ⟨rfl, rfl, rfl⟩ := by simpa using h.eq (p.splits_next_right hp)
31+
apply splits_pastSet
32+
33+
/-- You might want to invoke `ValidPos.Splits.exists_eq_singleton_append` to be able to apply this. -/
34+
theorem ValidPos.Splits.pastModify {s : String} {p : s.ValidPos} {t₁ t₂ : String}
35+
{c : Char} (h : p.Splits t₁ (singleton c ++ t₂)) :
36+
(p.pastModify f h.ne_endValidPos_of_singleton).Splits
37+
(t₁ ++ singleton (f (p.get h.ne_endValidPos_of_singleton))) t₂ :=
38+
h.pastSet
39+
40+
theorem toList_mapAux {f : Char → Char} {s : String} {p : s.ValidPos}
41+
(h : p.Splits t₁ t₂) : (mapAux f s p).toList = t₁.toList ++ t₂.toList.map f := by
42+
fun_induction mapAux generalizing t₁ t₂ with
43+
| case1 s => simp_all
44+
| case2 s p hp ih =>
45+
obtain ⟨c, rfl⟩ := h.exists_eq_singleton_append hp
46+
simp [ih h.pastModify]
47+
48+
@[simp]
49+
theorem toList_map {f : Char → Char} {s : String} : (s.map f).toList = s.toList.map f := by
50+
simp [map, toList_mapAux s.splits_startValidPos]
51+
52+
@[simp]
53+
theorem length_map {f : Char → Char} {s : String} : (s.map f).length = s.length := by
54+
simp [← length_toList]
55+
56+
@[simp]
57+
theorem map_eq_empty {f : Char → Char} {s : String} : s.map f = "" ↔ s = "" := by
58+
simp [← toList_eq_nil_iff]
59+
60+
end String

0 commit comments

Comments
 (0)