From cfcda8c0bed0313024ccf43c166903ad2fd223d2 Mon Sep 17 00:00:00 2001 From: KCN-judu Date: Tue, 24 Dec 2024 01:28:36 +0800 Subject: [PATCH 1/5] Docs: add docs for string iter behavior --- string/string.mbt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/string/string.mbt b/string/string.mbt index cced44b37..45dc16727 100644 --- a/string/string.mbt +++ b/string/string.mbt @@ -116,6 +116,16 @@ pub fn to_array(self : String) -> Array[Char] { } ///| +/// Returns an iterator over the Unicode characters in the string. +/// +/// Note: This iterator yields Unicode characters, not Utf16 code units. +/// As a result, the count of characters returned by `iter().count()` may not be equal to the length of the string returned by `length()`. +/// +/// ``` +/// let s = "Hello, World!🤣"; +/// assert_eq!(s.iter().count(), 14); // Unicode characters +/// assert_eq!(s.length(), 15); // Utf16 code units +/// ``` pub fn iter(self : String) -> Iter[Char] { Iter::new(fn(yield_) { let len = self.length() @@ -162,6 +172,8 @@ pub fn iter2(self : String) -> Iter2[Int, Char] { } ///| +/// Note: This method operates on Unicode characters, not Utf16 code units. +/// As a result, the count of characters passed to the folding function may not be equal to the length of the string. pub fn fold[A](self : String, init~ : A, f : (A, Char) -> A) -> A { let mut rv = init for c in self { From 96d5bbe8775ce7c4beeaf5527cacfd73166493fa Mon Sep 17 00:00:00 2001 From: rami3l Date: Mon, 23 Dec 2024 17:19:38 +0800 Subject: [PATCH 2/5] perf(deque): optimize `@deque.iter()` --- deque/deque.mbt | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/deque/deque.mbt b/deque/deque.mbt index 24d8d0ef9..93755fd1f 100644 --- a/deque/deque.mbt +++ b/deque/deque.mbt @@ -542,12 +542,26 @@ pub fn shrink_to_fit[A](self : T[A]) -> Unit { ///| pub fn iter[A](self : T[A]) -> Iter[A] { Iter::new(fn(yield_) { - for i in 0.. break x } + guard not(self.is_empty()) else { IterContinue } + let { head, buf, len, .. } = self + let cap = buf.length() + let head_len = cap - head + if head_len >= len { + for i in head..<(head + len) { + guard let IterContinue = yield_(buf[i]) else { x => return x } + } } else { - IterContinue + for i in head.. return x } + + } + for i in 0..<(len - head_len) { + guard let IterContinue = yield_(buf[i]) else { x => return x } + + } } + IterContinue }) } From 880b9141f8002d26a856f3dbc627cb4986be624e Mon Sep 17 00:00:00 2001 From: rami3l Date: Mon, 23 Dec 2024 17:19:58 +0800 Subject: [PATCH 3/5] feat(deque): add `@deque.iter2()` --- deque/deque.mbt | 27 +++++++++++++++++++++++++++ deque/deque.mbti | 1 + 2 files changed, 28 insertions(+) diff --git a/deque/deque.mbt b/deque/deque.mbt index 93755fd1f..c41a14040 100644 --- a/deque/deque.mbt +++ b/deque/deque.mbt @@ -565,6 +565,33 @@ pub fn iter[A](self : T[A]) -> Iter[A] { }) } +///| +pub fn iter2[A](self : T[A]) -> Iter2[Int, A] { + Iter2::new(fn(yield_) { + guard not(self.is_empty()) else { IterContinue } + let { head, buf, len, .. } = self + let cap = buf.length() + let head_len = cap - head + let mut j = 0 + if head_len >= len { + for i in head..<(head + len) { + guard let IterContinue = yield_(j, buf[i]) else { x => return x } + j += 1 + } + } else { + for i in head.. return x } + j += 1 + } + for i in 0..<(len - head_len) { + guard let IterContinue = yield_(j, buf[i]) else { x => return x } + j += 1 + } + } + IterContinue + }) +} + ///| pub fn T::from_iter[A](iter : Iter[A]) -> T[A] { let dq = T::new() diff --git a/deque/deque.mbti b/deque/deque.mbti index 0210006c9..045d35414 100644 --- a/deque/deque.mbti +++ b/deque/deque.mbti @@ -16,6 +16,7 @@ impl T { front[A](Self[A]) -> A? is_empty[A](Self[A]) -> Bool iter[A](Self[A]) -> Iter[A] + iter2[A](Self[A]) -> Iter2[Int, A] length[A](Self[A]) -> Int map[A, U](Self[A], (A) -> U) -> Self[U] mapi[A, U](Self[A], (Int, A) -> U) -> Self[U] From 30359eaed3bf2d7ce805b839497541a36e9d87a0 Mon Sep 17 00:00:00 2001 From: rami3l Date: Mon, 23 Dec 2024 16:48:19 +0800 Subject: [PATCH 4/5] feat(deque): add `@deque.copy()` --- deque/deque.mbt | 16 ++++++++++++++++ deque/deque.mbti | 1 + deque/deque_test.mbt | 9 +++++++++ 3 files changed, 26 insertions(+) diff --git a/deque/deque.mbt b/deque/deque.mbt index c41a14040..b8272b501 100644 --- a/deque/deque.mbt +++ b/deque/deque.mbt @@ -38,6 +38,22 @@ pub fn T::from_array[A](arr : Array[A]) -> T[A] { deq } +///| +/// Creates a new copy of this deque. +pub fn T::copy[A](self : T[A]) -> T[A] { + let len = self.len + let deq = T::{ + buf: UninitializedArray::make(len), + len, + head: 0, + tail: len - 1, + } + for i, x in self { + deq.buf[i] = x + } + deq +} + ///| pub fn T::of[A](arr : FixedArray[A]) -> T[A] { let deq = T::{ diff --git a/deque/deque.mbti b/deque/deque.mbti index 045d35414..16a67419a 100644 --- a/deque/deque.mbti +++ b/deque/deque.mbti @@ -9,6 +9,7 @@ impl T { capacity[A](Self[A]) -> Int clear[A](Self[A]) -> Unit contains[A : Eq](Self[A], A) -> Bool + copy[A](Self[A]) -> Self[A] each[A](Self[A], (A) -> Unit) -> Unit eachi[A](Self[A], (Int, A) -> Unit) -> Unit from_array[A](Array[A]) -> Self[A] diff --git a/deque/deque_test.mbt b/deque/deque_test.mbt index e84493e73..60a9a752a 100644 --- a/deque/deque_test.mbt +++ b/deque/deque_test.mbt @@ -203,6 +203,15 @@ test "from_array" { inspect!(@deque.of([1, 2, 3]), content="@deque.of([1, 2, 3])") } +test "copy" { + inspect!((@deque.new() : @deque.T[Int]).copy(), content="@deque.of([])") + let deq = @deque.of([1, 2, 3]) + let deq1 = deq.copy() + deq1[0] = 111 + inspect!(deq, content="@deque.of([1, 2, 3])") + inspect!(deq1, content="@deque.of([111, 2, 3])") +} + test "op_set" { let dv = @deque.of([1, 2, 3, 4, 5]) dv[1] = 3 From 86d7a264609378fa4711bd3de8916d54159b7bf5 Mon Sep 17 00:00:00 2001 From: rami3l Date: Mon, 23 Dec 2024 17:33:39 +0800 Subject: [PATCH 5/5] test: improve coverage for `deque/deque.mbt` --- deque/deque_test.mbt | 72 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/deque/deque_test.mbt b/deque/deque_test.mbt index 60a9a752a..131bff77a 100644 --- a/deque/deque_test.mbt +++ b/deque/deque_test.mbt @@ -374,3 +374,75 @@ test "from_iter empty iter" { let dq : @deque.T[Int] = @deque.T::from_iter(Iter::empty()) inspect!(dq, content="@deque.of([])") } + +test "from_array with single element" { + let arr = [42] + let deque = @deque.T::from_array(arr) + assert_eq!(deque.length(), 1) + assert_eq!(deque.front(), Some(42)) + assert_eq!(deque.back(), Some(42)) +} + +test "unsafe_pop_front after many push_front" { + let dq = @deque.new() + // First fill some elements to create a buffer with some capacity + for i = 0; i < 10; i = i + 1 { + dq.push_front(i) + } + // Pop all elements except one + for i = 0; i < 9; i = i + 1 { + dq.unsafe_pop_front() + } + // Now head should be at the end of buffer + // When we pop again, head should wrap around to 0 + inspect!(dq.front(), content="Some(0)") + dq.unsafe_pop_front() + inspect!(dq.is_empty(), content="true") +} + +test "unsafe_pop_back when tail needs to wrap around" { + let dq = @deque.new(capacity=2) + dq.push_back(1) // tail = 0 + dq.push_back(2) // tail = 1 + dq.unsafe_pop_front() // head = 1 + dq.push_back(3) // tail = 0 (wraps around) + dq.unsafe_pop_back() + inspect!(dq.back(), content="Some(2)") +} + +test "deque op_set wrapping case" { + let dq = @deque.new(capacity=4) + // Add elements until the deque wraps around + dq.push_back(1) // head=0, tail=0 + dq.push_back(2) // head=0, tail=1 + dq.push_back(3) // head=0, tail=2 + dq.push_back(4) // head=0, tail=3 + dq.push_front(0) // head=3, tail=3 + + // Now the deque is [0,1,2,3,4] with head=3, tail=3 + // Setting dq[2] should trigger the uncovered branch + dq[2] = 10 + + // Verify the change + inspect!(dq, content="@deque.of([0, 10, 2, 3, 4])") +} + +test "iter when tail needs to wrap around" { + let dq = @deque.new(capacity=2) + dq.push_back(1) // tail = 0 + dq.push_back(2) // tail = 1 + dq.unsafe_pop_front() // head = 1 + dq.push_back(3) // tail = 0 (wraps around) + inspect!(dq.iter().to_array(), content="[2, 3]") + inspect!(dq.iter().filter(fn(x) { x % 2 == 0 }).to_array(), content="[2]") + inspect!(dq.iter().filter(fn(x) { x % 2 != 0 }).to_array(), content="[3]") +} + +test "iter2 when tail needs to wrap around" { + let dq = @deque.new(capacity=2) + dq.push_back(1) // tail = 0 + dq.push_back(2) // tail = 1 + dq.unsafe_pop_front() // head = 1 + dq.push_back(3) // tail = 0 (wraps around) + inspect!(dq.iter2().to_array(), content="[(0, 2), (1, 3)]") +}