From 453569cd41fdd170b458d39c61252d991c3de105 Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Thu, 26 Dec 2024 16:14:33 +0800 Subject: [PATCH 01/31] force to remove wrong commit user --- immut/array/array.mbt | 128 ++++-------- immut/array/operation.mbt | 38 ++++ immut/array/tree.mbt | 424 ++++++++++++++++++++++++++++++++++++-- immut/array/types.mbt | 8 +- 4 files changed, 496 insertions(+), 102 deletions(-) diff --git a/immut/array/array.mbt b/immut/array/array.mbt index 44fb4a7c9..4728b7ab6 100644 --- a/immut/array/array.mbt +++ b/immut/array/array.mbt @@ -49,24 +49,34 @@ pub fn iter[A](self : T[A]) -> Iter[A] { }) } +///| + ///| pub fn T::from_iter[A](iter : Iter[A]) -> T[A] { iter.fold(init=new(), fn(arr, e) { arr.push(e) }) } ///| +/// pub fn length[A](self : T[A]) -> Int { self.size } ///| +/// pub fn copy[A](self : T[A]) -> T[A] { fn copy(t : Tree[A]) -> Tree[A] { match t { Leaf(l) => Leaf(l.copy()) Empty => Empty - Node(node) => - Node(FixedArray::makei(node.length(), fn(i) { copy(node[i]) })) + Node(node, sizes) => + Node( + FixedArray::makei(node.length(), fn(i) { copy(node[i]) }), + match sizes { + Some(sizes) => Some(FixedArray::copy(sizes)) + None => None + }, + ) } } @@ -74,6 +84,7 @@ pub fn copy[A](self : T[A]) -> T[A] { } ///| +/// /// Get a value at the given index. /// /// # Examples @@ -92,6 +103,7 @@ pub fn op_get[A](self : T[A], index : Int) -> A { } ///| +/// /// Set a value at the given index (immutable). /// /// # Example @@ -100,25 +112,15 @@ pub fn op_get[A](self : T[A], index : Int) -> A { /// assert_eq!(v.set(1, 10), @array.of([1, 10, 3, 4, 5])) /// ``` pub fn set[A](self : T[A], index : Int, value : A) -> T[A] { - fn set(i : Int, e, s, t : Tree[A]) -> Tree[A] { - match t { - Leaf(l) => Leaf(immutable_set(l, i & bitmask, e)) - Node(node) => { - let idx = shr_as_uint(i, s) & bitmask - Node(immutable_set(node, idx, set(i, e, s - num_bits, node[idx]))) - } - Empty => abort("Index out of bounds") - } - } - { - tree: set(index, value, self.shift, self.tree), + tree: self.tree.set(index, self.shift, value), size: self.size, shift: self.shift, } } ///| +/// /// Push a value to the end of the array. /// /// # Example @@ -127,22 +129,12 @@ pub fn set[A](self : T[A], index : Int, value : A) -> T[A] { /// assert_eq!(v.push(4), @array.of([1, 2, 3, 4])) /// ``` pub fn push[A](self : T[A], value : A) -> T[A] { - if self.size == (branching_factor << self.shift) { - { - tree: Node([self.tree, new_branch([value], self.shift)]), - size: self.size + 1, - shift: self.shift + num_bits, - } - } else { - { - tree: self.tree.add(self.size, self.shift, value), - size: self.size + 1, - shift: self.shift, - } - } + let (tree, shift) = self.tree.push_end(self.shift, value) + { tree, size: self.size + 1, shift } } ///| +/// /// Create a persistent array from an array. /// /// # Example @@ -155,6 +147,7 @@ pub fn T::from_array[A](arr : Array[A]) -> T[A] { } ///| +/// /// Iterate over the array. /// /// # Example @@ -165,18 +158,11 @@ pub fn T::from_array[A](arr : Array[A]) -> T[A] { /// assert_eq!(arr, [1, 2, 3, 4, 5]) /// ``` pub fn each[A](self : T[A], f : (A) -> Unit) -> Unit { - fn go(t : Tree[A]) -> Unit { - match t { - Empty => () - Leaf(l) => l.each(f) - Node(n) => n.each(fn(t) { go(t) }) - } - } - - go(self.tree) + self.tree.each(f) } ///| +/// /// Iterate over the array with index. /// /// # Example @@ -187,33 +173,17 @@ pub fn each[A](self : T[A], f : (A) -> Unit) -> Unit { /// assert_eq!(arr, [0, 2, 6, 12, 20]) /// ``` pub fn eachi[A](self : T[A], f : (Int, A) -> Unit) -> Unit { - fn go(t : Tree[A], shift : Int, start : Int) -> Unit { - match t { - Empty => () - Leaf(l) => - for i = 0; i < l.length(); i = i + 1 { - f(start + i, l[i]) - } - Node(n) => { - let child_shift = shift - num_bits - let mut start = start - for i = 0; i < n.length(); i = i + 1 { - go(n[i], child_shift, start) - start += 1 << shift - } - } - } - } - - go(self.tree, self.shift, 0) + self.tree.eachi(f, self.shift, 0) } ///| +/// pub impl[A : Eq] Eq for T[A] with op_equal(self, other) { self.size == other.size && self.tree == other.tree } ///| +/// /// Fold the array. /// /// # Example @@ -222,18 +192,11 @@ pub impl[A : Eq] Eq for T[A] with op_equal(self, other) { /// assert_eq!(v.fold(fn(a, b) { a + b }, init=0), 15) /// ``` pub fn fold[A, B](self : T[A], init~ : B, f : (B, A) -> B) -> B { - fn go(t : Tree[A], acc : B) -> B { - match t { - Empty => acc - Leaf(l) => l.fold(f, init=acc) - Node(n) => n.fold(fn(t, acc) { go(acc, t) }, init=acc) - } - } - - go(self.tree, init) + self.tree.fold(init, f) } ///| +/// /// Fold the array in reverse order. /// /// # Example @@ -242,18 +205,11 @@ pub fn fold[A, B](self : T[A], init~ : B, f : (B, A) -> B) -> B { /// assert_eq!(v.rev_fold(fn(a, b) { a + b }, init=0), 15) /// ``` pub fn rev_fold[A, B](self : T[A], init~ : B, f : (B, A) -> B) -> B { - fn go(t : Tree[A], acc : B) -> B { - match t { - Empty => acc - Leaf(l) => l.rev_fold(f, init=acc) - Node(n) => n.rev_fold(fn(t, acc) { go(acc, t) }, init=acc) - } - } - - go(self.tree, init) + self.tree.rev_fold(init, f) } ///| +/// /// Fold the array from left to right. /// /// # Example @@ -267,6 +223,7 @@ pub fn fold_left[A](self : T[A], f : (A, A) -> A, init~ : A) -> A { } ///| +/// /// Fold the array from right to left. /// /// # Example @@ -280,6 +237,7 @@ pub fn fold_right[A](self : T[A], f : (A, A) -> A, init~ : A) -> A { } ///| +/// /// Map a function over the array. /// /// # Example @@ -288,18 +246,11 @@ pub fn fold_right[A](self : T[A], f : (A, A) -> A, init~ : A) -> A { /// assert_eq!(v.map(fn(e) { e * 2 }), @array.of([2, 4, 6, 8, 10])) /// ``` pub fn map[A, B](self : T[A], f : (A) -> B) -> T[B] { - fn go(t : Tree[A]) -> Tree[B] { - match t { - Empty => Empty - Leaf(l) => Leaf(l.map(f)) - Node(n) => Node(FixedArray::makei(n.length(), fn(i) { go(n[i]) })) - } - } - - { tree: go(self.tree), size: self.size, shift: self.shift } + { tree: self.tree.map(f), size: self.size, shift: self.shift } } ///| +/// fn new_by_leaves[A](len : Int, gen_leaf : (Int, Int) -> FixedArray[A]) -> T[A] { fn tree(cap, len, s) -> Tree[A] { if cap == branching_factor { @@ -317,7 +268,9 @@ fn new_by_leaves[A](len : Int, gen_leaf : (Int, Int) -> FixedArray[A]) -> T[A] { tree(cap / branching_factor, len, i) } - Node(FixedArray::makei(child_count, child)) + // Use None here because the implementation of `new_by_leaves` ensures that the tree is full + // and we can use radix indexing. + Node(FixedArray::makei(child_count, child), None) } } @@ -353,23 +306,27 @@ test "new_by_leaves" { } ///| +/// /// Create a persistent array with a given length and value. pub fn T::make[A](len : Int, value : A) -> T[A] { new_by_leaves(len, fn(_s, l) { FixedArray::make(l, value) }) } ///| +/// /// Create a persistent array with a given length and a function to generate values. pub fn T::makei[A](len : Int, f : (Int) -> A) -> T[A] { new_by_leaves(len, fn(s, l) { FixedArray::makei(l, fn(i) { f(s + i) }) }) } ///| +/// pub fn T::of[A](arr : FixedArray[A]) -> T[A] { makei(arr.length(), fn(i) { arr[i] }) } ///| +/// pub impl[X : @quickcheck.Arbitrary] @quickcheck.Arbitrary for T[X] with arbitrary( size, rs @@ -378,6 +335,7 @@ pub impl[X : @quickcheck.Arbitrary] @quickcheck.Arbitrary for T[X] with arbitrar } ///| +/// pub impl[A : Hash] Hash for T[A] with hash_combine(self, hasher) { for e in self { hasher.combine(e) @@ -404,8 +362,8 @@ test "mix" { v2.each(fn(e) { ct = ct + e }) inspect!(ct, content="14850") v2 = v2.map(fn(e) { e * 2 }) - let ct1 = fold(v2, fn(a, b) { a + b }, init=0) - let ct2 = rev_fold(v2, fn(a, b) { a + b }, init=0) + let ct1 = v2.fold(fn(a, b) { a + b }, init=0) + let ct2 = v2.rev_fold(fn(a, b) { a + b }, init=0) inspect!(ct1, content="19800") inspect!(ct2, content="19800") inspect!(v.tree.is_empty_tree(), content="false") diff --git a/immut/array/operation.mbt b/immut/array/operation.mbt index d61d360a1..9cf85783b 100644 --- a/immut/array/operation.mbt +++ b/immut/array/operation.mbt @@ -13,6 +13,7 @@ // limitations under the License. ///| +/// Set the value at the given index. This operation is O(n). fn immutable_set[T](arr : FixedArray[T], i : Int, v : T) -> FixedArray[T] { let arr = arr.copy() arr[i] = v @@ -20,6 +21,7 @@ fn immutable_set[T](arr : FixedArray[T], i : Int, v : T) -> FixedArray[T] { } ///| +/// Add an element to the end of the array. This operation is O(n). fn immutable_push[T](arr : FixedArray[T], val : T) -> FixedArray[T] { let len = arr.length() let new_arr = FixedArray::make(len + 1, val) @@ -32,3 +34,39 @@ fn immutable_push[T](arr : FixedArray[T], val : T) -> FixedArray[T] { fn shr_as_uint(x : Int, y : Int) -> Int { (x.reinterpret_as_uint() >> y).reinterpret_as_int() } + +///| +/// Given an index and a shift, return the index of the branch that contains the given index. +fn radix_indexing(index : Int, shift : Int) -> Int { + shr_as_uint(index, shift) & bitmask +} + +///| +/// +/// Get the index of the branch that contains the given index. +/// For example, if the sizes are [0, 3, 6, 10] and the index is 5, the function should return 2. +fn get_branch_index(sizes : FixedArray[Int], index : Int) -> Int { + let mut lo = 0 + let mut hi = sizes.length() + while LINEAR_THRESHOLD < hi - lo { + let mid = (lo + hi) / 2 + if sizes[mid] <= index { + lo = mid + } else { + hi = mid + } + } + while sizes[lo] <= index { + lo += 1 + } + lo +} + +///| +/// Copy the sizes array. +fn copy_sizes(sizes : FixedArray[Int]?) -> FixedArray[Int]? { + match sizes { + Some(sizes) => Some(sizes.copy()) + None => None + } +} diff --git a/immut/array/tree.mbt b/immut/array/tree.mbt index 7301ca4c0..f1be860bc 100644 --- a/immut/array/tree.mbt +++ b/immut/array/tree.mbt @@ -16,11 +16,16 @@ let num_bits = 5 ///| +/// Invariant: `branching_factor` is a power of 2. let branching_factor : Int = 1 << num_bits ///| let bitmask : Int = branching_factor - 1 +///| +/// The threshold for switching to a linear search. +const LINEAR_THRESHOLD : Int = 4 + ///| fn Tree::empty[T]() -> Tree[T] { Tree::Empty @@ -30,7 +35,7 @@ fn Tree::empty[T]() -> Tree[T] { fn get_first[T](self : Tree[T]) -> T { match self { Leaf(leaf) => leaf[0] - Node(node) => get_first(node[0]) + Node(node, _) => get_first(node[0]) // TODO: prove that this should always be non-zero Empty => abort("Index out of bounds") } } @@ -39,17 +44,73 @@ fn get_first[T](self : Tree[T]) -> T { fn get_last[T](self : Tree[T]) -> T { match self { Leaf(leaf) => leaf[leaf.length() - 1] - Node(node) => get_last(node[node.length() - 1]) + Node(node, _) => get_last(node[node.length() - 1]) // TODO: prove that this should always be non-zero Empty => abort("Index out of bounds") } } ///| fn get[T](self : Tree[T], index : Int, shift : Int) -> T { + fn get_radix(node : Tree[T], shift : Int) -> T { + match node { + Leaf(leaf) => leaf[index & bitmask] + Node(node, None) => + get_radix(node[radix_indexing(index, shift)], shift - num_bits) + Node(_, Some(_)) => + abort("Unreachable: Node should not have sizes in get_radix") + Empty => abort("Index out of bounds") + } + } + + match self { + Leaf(leaf) => leaf[index] + Node(children, Some(sizes)) => { + let branch_index = get_branch_index(sizes, index) + let sub_index = index - sizes[branch_index] + get(children[branch_index], sub_index, shift - num_bits) + } + Node(_, None) => get_radix(self, shift) + Empty => abort("Index out of bounds") + } +} + +///| +fn set[T](self : Tree[T], index : Int, shift : Int, value : T) -> Tree[T] { + fn set_radix(node : Tree[T], shift : Int) -> Tree[T] { + match node { + Leaf(leaf) => Leaf(immutable_set(leaf, index & bitmask, value)) + Node(node, None) => { + let sub_idx = radix_indexing(index, shift) + Node( + immutable_set( + node, + sub_idx, + set_radix(node[radix_indexing(index, shift)], shift - num_bits), + ), + None, + ) + } + Node(_, Some(_)) => + abort("Unreachable: Node should not have sizes in set_radix") + Empty => abort("Index out of bounds") + } + } + match self { - Leaf(leaf) => leaf[index & bitmask] - Node(node) => - get(node[shr_as_uint(index, shift) & bitmask], index, shift - num_bits) + Leaf(leaf) => Leaf(immutable_set(leaf, index & bitmask, value)) + Node(children, Some(sizes)) => { + let branch_index = get_branch_index(sizes, index) + let sub_index = index - sizes[branch_index] + Node( + immutable_set( + children, + branch_index, + children[branch_index].set(sub_index, shift - num_bits, value), + ), + Some(sizes.copy()), + ) + } + Node(_children, None) => set_radix(self, shift) Empty => abort("Index out of bounds") } } @@ -63,25 +124,356 @@ fn is_empty_tree[T](self : Tree[T]) -> Bool { } ///| -fn new_branch[T](leaf : FixedArray[T], shift : Int) -> Tree[T] { +/// Create a new tree with a single leaf. Note that the resulting tree is a left-skewed tree. +fn new_branch_left[T](leaf : FixedArray[T], shift : Int) -> Tree[T] { match shift { 0 => Leaf(leaf) - s => Node([new_branch(leaf, s - num_bits)]) + s => Node([new_branch_left(leaf, s - num_bits)], None) // size is None because we can use radix indexing + } +} + +///| +/// Push a value to the end of the tree. +/// Precondition: +/// - The height of `self` = `shift` / `num_bits` (the height starts from 0). +/// - `length` is the number of elements in the tree. +fn push_end[T](self : Tree[T], shift : Int, value : T) -> (Tree[T], Int) { + fn update_sizes_last(sizes : FixedArray[Int]?) -> FixedArray[Int]? { + match sizes { + Some(sizes) => { + let new_sizes = sizes.copy() + new_sizes[new_sizes.length() - 1] += 1 + Some(new_sizes) + } + None => None + } + } + + fn push_sizes_last(sizes : FixedArray[Int]?) -> FixedArray[Int]? { + match sizes { + Some(sizes) => Some(immutable_push(sizes, 1)) + None => None + } + } + + fn worker(node : Tree[T], shift : Int) -> Tree[T]? { + match node { + Leaf(leaf) => { + if shift != 0 { + abort( + "Unreachable: Leaf should not have a non-zero shift, which means we have not reached the bottom of the tree", + ) + } + if leaf.length() < branching_factor { + Some(Leaf(immutable_push(leaf, value))) + } else { + None + } + } + Node(nodes, sizes) => { + let len = nodes.length() + match worker(nodes[len - 1], shift - num_bits) { + // We have successfully pushed the value, now duplicate its ancestor nodes. + Some(new_node) => { + let new_nodes = nodes.copy() + new_nodes[len - 1] = new_node + Some(Node(new_nodes, update_sizes_last(sizes))) + } + // We need to create a new node to push the value. + None => + if len < branching_factor { + Some( + Node( + immutable_push( + nodes, + new_branch_left([value], shift - num_bits), + ), + push_sizes_last(sizes), + ), + ) + } else { + None + } + } + } + Empty => Some(Leaf([value])) + } + } + + match worker(self, shift) { + Some(new_tree) => (new_tree, shift) + None => { + let new_branch = new_branch_left([value], shift) + ( + match self { + Leaf(_leaf) => Node([self, new_branch], None) + Node(_nodes, Some(sizes)) => + Node([self, new_branch], Some([sizes[sizes.length() - 1], 1])) + Node(_nodes, None) => Node([self, new_branch], None) + Empty => + abort( + "Unreachable: Empty tree should have fallen into the Some(new_tree) branch", + ) + }, + shift + num_bits, + ) + } } } ///| -fn add[T](self : Tree[T], index : Int, shift : Int, value : T) -> Tree[T] { +/// +/// For each element in the tree, apply the function `f`. +fn each[A](self : Tree[A], f : (A) -> Unit) -> Unit { match self { - Leaf(l) => Leaf(immutable_push(l, value)) - Node(n) => { - let idx = shr_as_uint(index, shift) & bitmask - if idx < n.length() { - Node(immutable_set(n, idx, n[idx].add(index, shift - num_bits, value))) - } else { - Node(immutable_push(n, new_branch([value], shift - num_bits))) + Empty => () + Leaf(l) => l.each(f) + Node(ns, _) => ns.each(fn(t) { t.each(f) }) + } +} + +///| +/// +/// For each element in the tree, apply the function `f` with the index of the element. +fn eachi[A]( + self : Tree[A], + f : (Int, A) -> Unit, + shift : Int, + start : Int +) -> Unit { + match self { + Empty => () + Leaf(l) => + for i = 0; i < l.length(); i = i + 1 { + f(start + i, l[i]) + } + Node(ns, None) => { + let child_shift = shift - num_bits + let mut start = start + for i = 0; i < ns.length(); i = i + 1 { + ns[i].eachi(f, child_shift, start) + start += 1 << shift + } + } + Node(ns, Some(sizes)) => { + let child_shift = shift - num_bits + let mut start = start + for i = 0; i < ns.length(); i = i + 1 { + ns[i].eachi(f, child_shift, start) + start += sizes[i] } } - Empty => Leaf([value]) + } +} + +///| +/// Fold the tree. +fn fold[A, B](self : Tree[A], acc : B, f : (B, A) -> B) -> B { + match self { + Empty => acc + Leaf(l) => l.fold(f, init=acc) + Node(n, _) => n.fold(fn(acc, t) { t.fold(acc, f) }, init=acc) + } +} + +///| +/// Fold the tree in reverse order. +fn rev_fold[A, B](self : Tree[A], acc : B, f : (B, A) -> B) -> B { + match self { + Empty => acc + Leaf(l) => l.rev_fold(f, init=acc) + Node(n, _) => n.rev_fold(fn(acc, t) { t.rev_fold(acc, f) }, init=acc) + } +} + +///| +/// Map the tree. +fn map[A, B](self : Tree[A], f : (A) -> B) -> Tree[B] { + match self { + Empty => Empty + Leaf(l) => Leaf(l.map(f)) + Node(n, szs) => + Node( + FixedArray::makei(n.length(), fn(i) { n[i].map(f) }), + copy_sizes(szs), + ) + } +} + +///| +fn concat[A]( + left : Tree[A], + left_shift : Int, + right : Tree[A], + right_shift : Int, + top : Bool +) -> (Tree[A], Int) { + if left_shift > right_shift { + let (c, c_shift) = concat( + left.right_child(), + left_shift - num_bits, + right, + right_shift, + false, + ) + return rebalance(left, left_shift, c, c_shift, Empty, 0, false), + } else if right_shift > left_shift { + let (c, c_shift) = concat( + left, + left_shift, + right.left_child(), + right_shift - num_bits, + false, + ) + return rebalance(Empty, 0, c, c_shift, right, right_shift, false) + } + else { + + if left_shift == 0 { + let left_elems = left.leaf_elements() + let right_elems = right.leaf_elements() + let left_len = left_elems.length() + let right_len = right_elems.length() + let len = left_len+right_len + let children = { + if top && len <= branching_factor{ + [Leaf(FixedArray::makei(len, fn (i: Int) { if i < left_len {left_elems[i]} else {right_elems[i-left_len]}}))] + } + else { + [left, right] + } + } + return Node(children, None) + } else { + let c = concat(left.right_child(), left_child - num_bits, right.left_child(), right_child - num_bits, false) + return rebalance() + } + } +} + +///| +/// Given three `Node`s of the same height, rebalance them into two. +fn rebalance[A]( + left : Tree[A], + center : Tree[A], + right : Tree[A], + shift : Int, + top : Bool +) -> Tree[A] { + let t = merge(left, center, right) + +} + +///| +/// Given three trees of the same height (if not `Empty`), merge them into one. +/// `left` and `right` might be `Node` or `Empty`. +/// `center` is always a `Node`. +/// The resulting array might be longer than `branching_factor`, +/// which will be handled by `rebalance` later. +fn merge[A]( + left : Tree[A], + center : Tree[A], + right : Tree[A] +) -> FixedArray[Tree[A]] { + if left.is_leaf() || not(center.is_node()) || right.is_leaf() { + abort("Unreachable: input to merge is invalid") + } + fn get_children(self : Tree[A]) -> FixedArray[Tree[A]] { + match self { + Node(children, _) => children + Empty => [] + Leaf(_) => abort("Unreachable") + } + } + + let left_children = get_children(left) + let center_children = get_children(center) + let right_children = get_children(right) + let left_len = left_children.length() + let center_len = center_children.length() + let right_len = right_children.length() + FixedArray::makei(left_len + center_len + right_len, fn(i) { + if i < left_len { + left_children[i] + } else if i < left_len + center_len { + center_children[i - left_len] + } else { + right_children[i - left_len - center_len] + } + }) +} + +///| +fn concat_plan[A](t : FixedArray[Tree[A]]) -> (FixedArray[Int], Int) { + let c = FixedArray::make(t.length(), fn(i : Int) { t[i].num_children() }) + let S = c.fold(init=0, fn(acc, x) { acc + x }) + // round up to the nearest integer of S/branching_factor + let opt_len = (S + branching_factor - 1) / branching_factor + let mut n = t.length() + let mut i = 0 + + +} + +///| +fn is_node[A](self : Tree[A]) -> Bool { + match self { + Node(_, _) => true + _ => false + } +} + +///| +fn is_leaf[A](self : Tree[A]) -> Bool { + match self { + Leaf(_) => true + _ => false + } +} + +///| +fn is_empty[A](self : Tree[A]) -> Bool { + match self { + Empty => true + _ => false + } +} + +///| +/// Get the rightmost child of a tree node. Abort if +/// it is not a `Node`. +fn right_child[A](self : Tree[A]) -> Tree[A] { + match self { + Node(children, _) => children[-1] + Leaf(_) | Empty => abort("Should not get children on non-`Node`s") + } +} + +///| +/// Get the leftmost child of a tree node. Abort if +/// it is not a `Node`. +fn left_child[A](self : Tree[A]) -> Tree[A] { + match self { + Node(children, _) => children[0] + Leaf(_) | Empty => abort("Should not get children on non-`Node`s") + } +} + +///| +/// Get the leaf contents. Abort if it is not a `Leaf`. +fn leaf_elements[A](self : Tree[A]) -> FixedArray[A] { + match self { + Leaf(children) => children + _ => abort("Should not call `get_leaf_elements` on non-leaf nodes") + } +} + +///| +/// Get the length of the current node, not the total number of elements in the tree. +fn num_children[A](self : Tree[A]) -> Int { + match self { + Empty => 0 + Leaf(l) => l.length() + Node(children, _) => children.length() } } diff --git a/immut/array/types.mbt b/immut/array/types.mbt index 8e9a9cb1d..e9688c7d4 100644 --- a/immut/array/types.mbt +++ b/immut/array/types.mbt @@ -13,6 +13,10 @@ // limitations under the License. ///| +/// Invariants: +/// - `shift` = tree height * `num_bits`. When it is 0, we are at the leaf level. +/// - `size` = the number of elements in the tree. +/// - `shift` is not used when `tree` is `Empty`. struct T[A] { tree : Tree[A] size : Int @@ -20,8 +24,10 @@ struct T[A] { } ///| +/// Invariants: +/// - For `Node`, the sizes array is `None` if the tree is full, i.e., we can use radix indexing. priv enum Tree[A] { Empty - Node(FixedArray[Tree[A]]) + Node(FixedArray[Tree[A]], FixedArray[Int]?) // (Subtrees, Sizes of subtrees) Leaf(FixedArray[A]) } derive(Eq, Show) From c6b5a25e757bbe420d47edd0c2bc27b0b8a29223 Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Thu, 26 Dec 2024 17:31:22 +0800 Subject: [PATCH 02/31] sync --- immut/array/tree.mbt | 48 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/immut/array/tree.mbt b/immut/array/tree.mbt index f1be860bc..1770bd044 100644 --- a/immut/array/tree.mbt +++ b/immut/array/tree.mbt @@ -26,6 +26,14 @@ let bitmask : Int = branching_factor - 1 /// The threshold for switching to a linear search. const LINEAR_THRESHOLD : Int = 4 +///| +/// The $e_{max}$ parameter of the search step invariant. +const E_MAX : Int = 2 + +///| +/// $e_{max} / 2$. +let e_max_2 : Int = E_MAX / 2 + ///| fn Tree::empty[T]() -> Tree[T] { Tree::Empty @@ -405,14 +413,35 @@ fn merge[A]( ///| fn concat_plan[A](t : FixedArray[Tree[A]]) -> (FixedArray[Int], Int) { - let c = FixedArray::make(t.length(), fn(i : Int) { t[i].num_children() }) - let S = c.fold(init=0, fn(acc, x) { acc + x }) + let mut node_counts = FixedArray::makei(t.length(), fn { + i => t[i].num_children() + }) + let total_nodes = node_counts.fold(init=0, fn { acc, x => acc + x }) // round up to the nearest integer of S/branching_factor - let opt_len = (S + branching_factor - 1) / branching_factor - let mut n = t.length() + let opt_len = (total_nodes + branching_factor - 1) / branching_factor + let mut new_len = t.length() let mut i = 0 - + while opt_len + e_max_2 < new_len { + // Skip over all nodes satisfying the invariant. + while node_counts[i] > branching_factor - e_max_2 { + i += 1 + } + // Found short node, so redistribute over the next nodes + let mut remaining_nodes = node_counts[i] + while remaining_nodes > 0 { + let min_size = min(remaining_nodes + node_counts[i + 1], branching_factor) + node_counts[i] = min_size + remaining_nodes = remaining_nodes + node_counts[i + 1] - min_size + i += 1 + } + for j = i; j < new_len - 1; j = j + 1 { + node_counts[j] = node_counts[j + 1] + } + new_len -= 1 + i -= 1 + } + return (node_counts, new_len) } ///| @@ -477,3 +506,12 @@ fn num_children[A](self : Tree[A]) -> Int { Node(children, _) => children.length() } } + +///| +fn min(a : Int, b : Int) -> Int { + if a < b { + a + } else { + b + } +} From 127ec888858f5afd1eeef57279daf68c2803f430 Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Fri, 27 Dec 2024 14:23:15 +0800 Subject: [PATCH 03/31] sync --- immut/array/moon.pkg.json | 3 +- immut/array/tree.mbt | 117 +++++++++++++++++++++++++++++++++++++- 2 files changed, 116 insertions(+), 4 deletions(-) diff --git a/immut/array/moon.pkg.json b/immut/array/moon.pkg.json index d8a1f022b..687357ba9 100644 --- a/immut/array/moon.pkg.json +++ b/immut/array/moon.pkg.json @@ -5,7 +5,8 @@ { "path": "moonbitlang/core/array", "alias": "_core/array" - } + }, + "moonbitlang/core/bool" ], "targets": { "panic_test.mbt": ["not", "native"], diff --git a/immut/array/tree.mbt b/immut/array/tree.mbt index 1770bd044..006e3cfa1 100644 --- a/immut/array/tree.mbt +++ b/immut/array/tree.mbt @@ -412,9 +412,10 @@ fn merge[A]( } ///| -fn concat_plan[A](t : FixedArray[Tree[A]]) -> (FixedArray[Int], Int) { +/// Create a redistribution plan for the tree. +fn redis_plan[A](t : FixedArray[Tree[A]]) -> (FixedArray[Int], Int) { let mut node_counts = FixedArray::makei(t.length(), fn { - i => t[i].num_children() + i => t[i].size() }) let total_nodes = node_counts.fold(init=0, fn { acc, x => acc + x }) // round up to the nearest integer of S/branching_factor @@ -444,6 +445,107 @@ fn concat_plan[A](t : FixedArray[Tree[A]]) -> (FixedArray[Int], Int) { return (node_counts, new_len) } +///| +/// This function redistributes the nodes in `old_t` according to the plan in `node_counts`. +/// +/// Preconditions: +/// - forall i in 0..node_nums, old_t[i] != Empty. +/// - `old_t` contains a list of trees, each of (`shift` / `num_bits`) height. +/// - `node_counts` contains the number of children of each node in `new_t` (the redistributed version of `old_t`). +/// - `node_nums` is the length of `node_counts`. +fn redis[A]( + old_t : FixedArray[Tree[A]], + node_counts : FixedArray[Int], + node_nums : Int, + shift : Int +) -> FixedArray[Tree[A]] { + let old_len = old_t.length() + let new_t = FixedArray::make(node_nums, Empty) + let mut old_offset = 0 + let mut j = 0 // the index of in the old tree + if shift == 0 { + // Handle Leaf case + for i = 0; i < node_nums; i = i + 1 { + // t[j] is the next to be redistributed + // old_offset is the index of the next node to be redistributed in old_t[j] + // old_offset == 0 means all nodes in old_t[j] are to be redistributed + // new_t[i] is the current node to be filled with redistributed nodes + let old_leaf_elems = old_t[i].leaf_elements() + let old_leaf_len = old_leaf_elems.length() + if old_offset == 0 && old_leaf_len == node_counts[i] { + // Perfect, we just point to the old leaf + new_t[i] = old_t[i] + } else { + let mut new_offset = 0 // the accumulated number of elements in the new leaf + let new_leaf_len = node_counts[i] + let new_elems = FixedArray::make(new_leaf_len, old_leaf_elems[0]) + while new_offset < node_counts[i] { + guard j < old_len // This shouldn't be triggered if the plan was correctly generated + let remaining = min( + node_counts[i] - new_offset, + old_leaf_len - old_offset, + ) + FixedArray::unsafe_blit( + new_elems, new_offset, old_leaf_elems, old_offset, remaining, + ) + new_offset += remaining + old_offset += remaining + if old_offset == old_leaf_len { + j += 1 + old_offset = 0 + } + } + new_t[i] = Leaf(new_elems) + } + } + } else { + // Handle Node case, pretty much the same as the Leaf case + for i = 0; i < node_nums; i = i + 1 { + let old_node_chldrn = old_t[i].node_children() + let old_node_len = old_node_chldrn.length() + if old_offset == 0 && old_node_len == node_counts[i] { + new_t[i] = old_t[i] + } else { + let mut new_offset = 0 // the accumulated number of elements in the new leaf + let new_node_len = node_counts[i] + let new_node_chldrn = FixedArray::make(new_node_len, old_node_chldrn[0]) + while new_offset < node_counts[i] { + guard j < old_len // This shouldn't be triggered if the plan was correctly generated + let remaining = min( + node_counts[i] - new_offset, + old_node_len - old_offset, + ) + FixedArray::unsafe_blit( + new_node_chldrn, new_offset, old_node_chldrn, old_offset, remaining, + ) + new_offset += remaining + old_offset += remaining + if old_offset == old_node_len { + j += 1 + old_offset = 0 + } + } + new_t[i] = Node(new_node_chldrn, compute_sizes(new_node_chldrn)) + } + + } + } + new_t +} + +fn compute_sizes[A](children : FixedArray[Tree[A]]) -> FixedArray[Int] { + let mut sizes = FixedArray::make(children.length(), 0) + let mut sum = 0 + let mut flag = true + for i = 0; i < children.length(); i = i + 1 { + let sz = children[i].size() + flag = flag & (sz == branching_factor) + sum += sz + sizes[i] = sum + } + sizes +} + ///| fn is_node[A](self : Tree[A]) -> Bool { match self { @@ -497,9 +599,18 @@ fn leaf_elements[A](self : Tree[A]) -> FixedArray[A] { } } +///| +/// Get the children of a `Node`. Abort if it is not a `Node`. +fn node_children[A](self : Tree[A]) -> FixedArray[Tree[A]] { + match self { + Node(children, _) => children + _ => abort("Should not call `node_children` on non-`Node`s") + } +} + ///| /// Get the length of the current node, not the total number of elements in the tree. -fn num_children[A](self : Tree[A]) -> Int { +fn size[A](self : Tree[A]) -> Int { match self { Empty => 0 Leaf(l) => l.length() From 846147cbfd8f5d7635e4fee848c707d00db6b030 Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Fri, 27 Dec 2024 17:21:58 +0800 Subject: [PATCH 04/31] sync --- immut/array/tree.mbt | 78 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 60 insertions(+), 18 deletions(-) diff --git a/immut/array/tree.mbt b/immut/array/tree.mbt index 006e3cfa1..7a5955f3c 100644 --- a/immut/array/tree.mbt +++ b/immut/array/tree.mbt @@ -360,7 +360,8 @@ fn concat[A]( } ///| -/// Given three `Node`s of the same height, rebalance them into two. +/// Given three `Node`s of the same height (`shift` / `num_bits`), rebalance them into two. +/// `top` is `true` if the resulting node has no upper node. fn rebalance[A]( left : Tree[A], center : Tree[A], @@ -368,8 +369,29 @@ fn rebalance[A]( shift : Int, top : Bool ) -> Tree[A] { - let t = merge(left, center, right) - + let t = tri_merge(left, center, right) + let (nc, nc_len) = redis_plan(t) + let new_t = redis(t, nc, nc_len, shift) + if nc_len < branching_factor { + guard new_t.length() == 1 + // All nodes can be accommodated in a single node + if not(top) { + return Node(new_t, compute_sizes(new_t, shift)) + } else { + return new_t[0] + } + } else { + let new_child_1 = FixedArray::makei(branching_factor, fn(i) { new_t[i] }) + let new_child_2 = FixedArray::makei(new_t.length() - branching_factor, fn( + i + ) { + new_t[i + branching_factor] + }) + let new_node_1 = Node(new_child_1, compute_sizes(new_child_1, shift)) + let new_node_2 = Node(new_child_2, compute_sizes(new_child_2, shift)) + let new_children = FixedArray::from_array([new_node_1, new_node_2]) + return Node(new_children, compute_sizes(new_children, shift)) + } } ///| @@ -378,7 +400,7 @@ fn rebalance[A]( /// `center` is always a `Node`. /// The resulting array might be longer than `branching_factor`, /// which will be handled by `rebalance` later. -fn merge[A]( +fn tri_merge[A]( left : Tree[A], center : Tree[A], right : Tree[A] @@ -414,9 +436,7 @@ fn merge[A]( ///| /// Create a redistribution plan for the tree. fn redis_plan[A](t : FixedArray[Tree[A]]) -> (FixedArray[Int], Int) { - let mut node_counts = FixedArray::makei(t.length(), fn { - i => t[i].size() - }) + let mut node_counts = FixedArray::makei(t.length(), fn { i => t[i].size() }) let total_nodes = node_counts.fold(init=0, fn { acc, x => acc + x }) // round up to the nearest integer of S/branching_factor let opt_len = (total_nodes + branching_factor - 1) / branching_factor @@ -506,11 +526,11 @@ fn redis[A]( if old_offset == 0 && old_node_len == node_counts[i] { new_t[i] = old_t[i] } else { - let mut new_offset = 0 // the accumulated number of elements in the new leaf + let mut new_offset = 0 let new_node_len = node_counts[i] let new_node_chldrn = FixedArray::make(new_node_len, old_node_chldrn[0]) while new_offset < node_counts[i] { - guard j < old_len // This shouldn't be triggered if the plan was correctly generated + guard j < old_len let remaining = min( node_counts[i] - new_offset, old_node_len - old_offset, @@ -525,25 +545,35 @@ fn redis[A]( old_offset = 0 } } - new_t[i] = Node(new_node_chldrn, compute_sizes(new_node_chldrn)) + new_t[i] = Node(new_node_chldrn, compute_sizes(new_node_chldrn, shift)) } - } } new_t } -fn compute_sizes[A](children : FixedArray[Tree[A]]) -> FixedArray[Int] { - let mut sizes = FixedArray::make(children.length(), 0) +///| +/// Given a list of trees as `children` and the `shift` of them. +fn compute_sizes[A]( + children : FixedArray[Tree[A]], + shift : Int +) -> FixedArray[Int]? { + let len = children.length() + let mut sizes = FixedArray::make(len, 0) let mut sum = 0 let mut flag = true - for i = 0; i < children.length(); i = i + 1 { - let sz = children[i].size() - flag = flag & (sz == branching_factor) + let full_subtree_size = branching_factor << shift + for i = 0; i < len; i = i + 1 { + let sz = children[i].length(shift) + flag = flag && sz == full_subtree_size sum += sz sizes[i] = sum } - sizes + if flag { + None + } else { + Some(sizes) + } } ///| @@ -609,7 +639,7 @@ fn node_children[A](self : Tree[A]) -> FixedArray[Tree[A]] { } ///| -/// Get the length of the current node, not the total number of elements in the tree. +/// Get the physical size of the current node, not the total number of elements in the tree. fn size[A](self : Tree[A]) -> Int { match self { Empty => 0 @@ -618,6 +648,18 @@ fn size[A](self : Tree[A]) -> Int { } } +///| +/// Get the total number of elements in the tree. +fn length[A](self : Tree[A], shift : Int) -> Int { + match self { + Empty => 0 + Leaf(l) => l.length() + Node(children, Some(sizes)) => sizes[-1] + Node(children, None) => + ((children.length() - 1) << shift) + children[-1].length(shift - num_bits) + } +} + ///| fn min(a : Int, b : Int) -> Int { if a < b { From 1170529110fb08097d4757bba4857fb0d177bf09 Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Sat, 28 Dec 2024 11:38:52 +0800 Subject: [PATCH 05/31] add concat, waiting for tests --- immut/array/array.mbt | 23 +++++++++- immut/array/tree.mbt | 98 +++++++++++++++++++++++++++++-------------- 2 files changed, 88 insertions(+), 33 deletions(-) diff --git a/immut/array/array.mbt b/immut/array/array.mbt index 169390b4a..57727e663 100644 --- a/immut/array/array.mbt +++ b/immut/array/array.mbt @@ -18,6 +18,27 @@ pub fn T::new[A]() -> T[A] { { tree: Tree::empty(), size: 0, shift: 0 } } +///| +/// Given two trees, concatenate them into a new tree. +/// +/// # Example +/// ``` +/// let a = @immut/array::Tree::from_array([1, 2, 3]) +/// let b = @immut/array::Tree::from_array([4, 5, 6]) +/// let c = a.concat(b) +/// assert_eq!(tree, @immut/array::Tree::from_array([1, 2, 3, 4, 5, 6])) +/// ``` +pub fn T::concat[A](self : T[A], other : T[A]) -> T[A] { + let (tree, shift) = concat( + self.tree, + self.shift, + other.tree, + other.shift, + true, + ) + { tree, size: self.size + other.size, shift } +} + ///| pub impl[A : Show] Show for T[A] with output(self, logger) { logger.write_iter(self.iter(), prefix="@immut/array.of([", suffix="])") @@ -49,8 +70,6 @@ pub fn iter[A](self : T[A]) -> Iter[A] { }) } -///| - ///| pub fn T::from_iter[A](iter : Iter[A]) -> T[A] { iter.fold(init=new(), fn(arr, e) { arr.push(e) }) diff --git a/immut/array/tree.mbt b/immut/array/tree.mbt index 7a5955f3c..ab8bf05bf 100644 --- a/immut/array/tree.mbt +++ b/immut/array/tree.mbt @@ -309,6 +309,8 @@ fn map[A, B](self : Tree[A], f : (A) -> B) -> Tree[B] { } ///| +/// Concatenate two trees. +/// Should be called as with `top = true`. fn concat[A]( left : Tree[A], left_shift : Int, @@ -324,7 +326,8 @@ fn concat[A]( right_shift, false, ) - return rebalance(left, left_shift, c, c_shift, Empty, 0, false), + guard c_shift == left_shift + return rebalance(left, c, Empty, left_shift, top) } else if right_shift > left_shift { let (c, c_shift) = concat( left, @@ -333,52 +336,74 @@ fn concat[A]( right_shift - num_bits, false, ) - return rebalance(Empty, 0, c, c_shift, right, right_shift, false) - } - else { - - if left_shift == 0 { - let left_elems = left.leaf_elements() - let right_elems = right.leaf_elements() - let left_len = left_elems.length() - let right_len = right_elems.length() - let len = left_len+right_len - let children = { - if top && len <= branching_factor{ - [Leaf(FixedArray::makei(len, fn (i: Int) { if i < left_len {left_elems[i]} else {right_elems[i-left_len]}}))] - } - else { - [left, right] - } - } - return Node(children, None) + guard c_shift == right_shift + return rebalance(Empty, c, right, right_shift, top) + } else if left_shift == 0 { + // Handle Leaf case + let left_elems = left.leaf_elements() + let right_elems = right.leaf_elements() + let left_len = left_elems.length() + let right_len = right_elems.length() + let len = left_len + right_len + if top && len <= branching_factor { + return ( + Leaf( + FixedArray::makei(len, fn(i : Int) { + if i < left_len { + left_elems[i] + } else { + right_elems[i - left_len] + } + }), + ), + 0, + ) } else { - let c = concat(left.right_child(), left_child - num_bits, right.left_child(), right_child - num_bits, false) - return rebalance() + return ( + Node( + FixedArray::from_array([left, right]), + Some(FixedArray::from_array([left_len, len])), + ), + num_bits, + ) } + } else { + // Handle Node case + let (c, c_shift) = concat( + left.right_child(), + left_shift - num_bits, + right.left_child(), + right_shift - num_bits, + false, + ) + guard c_shift == left_shift + guard c_shift == right_shift + return rebalance(left, c, right, left_shift, top) } } ///| /// Given three `Node`s of the same height (`shift` / `num_bits`), rebalance them into two. /// `top` is `true` if the resulting node has no upper node. +/// Returns the new node and its shift. fn rebalance[A]( left : Tree[A], center : Tree[A], right : Tree[A], shift : Int, top : Bool -) -> Tree[A] { - let t = tri_merge(left, center, right) +) -> (Tree[A], Int) { + // Suppose H = shift / num_bits + let t = tri_merge(left, center, right) // t is a list of trees of (H-1) height let (nc, nc_len) = redis_plan(t) - let new_t = redis(t, nc, nc_len, shift) + let new_t = redis(t, nc, nc_len, shift) // new_t is a list of trees of (H-1) height if nc_len < branching_factor { guard new_t.length() == 1 // All nodes can be accommodated in a single node if not(top) { - return Node(new_t, compute_sizes(new_t, shift)) + return (Node(new_t, compute_sizes(new_t, shift)), shift) // return H height node } else { - return new_t[0] + return (new_t[0], shift - num_bits) // return H-1 height node } } else { let new_child_1 = FixedArray::makei(branching_factor, fn(i) { new_t[i] }) @@ -390,7 +415,7 @@ fn rebalance[A]( let new_node_1 = Node(new_child_1, compute_sizes(new_child_1, shift)) let new_node_2 = Node(new_child_2, compute_sizes(new_child_2, shift)) let new_children = FixedArray::from_array([new_node_1, new_node_2]) - return Node(new_children, compute_sizes(new_children, shift)) + return (Node(new_children, compute_sizes(new_children, shift)), shift) // return H height node } } @@ -400,6 +425,14 @@ fn rebalance[A]( /// `center` is always a `Node`. /// The resulting array might be longer than `branching_factor`, /// which will be handled by `rebalance` later. +/// +/// Preconditions: +/// - `left` and `right` are `Empty` or `Node`. +/// - `center` is `Node`. +/// +/// Postconditions: +/// - The resulting array is of length `left.size() + center.size() + right.size()`. +/// - The height of a `Tree` in the resulting array is one less than the height of the input `Tree`s. fn tri_merge[A]( left : Tree[A], center : Tree[A], @@ -436,7 +469,7 @@ fn tri_merge[A]( ///| /// Create a redistribution plan for the tree. fn redis_plan[A](t : FixedArray[Tree[A]]) -> (FixedArray[Int], Int) { - let mut node_counts = FixedArray::makei(t.length(), fn { i => t[i].size() }) + let node_counts = FixedArray::makei(t.length(), fn { i => t[i].size() }) let total_nodes = node_counts.fold(init=0, fn { acc, x => acc + x }) // round up to the nearest integer of S/branching_factor let opt_len = (total_nodes + branching_factor - 1) / branching_factor @@ -473,6 +506,9 @@ fn redis_plan[A](t : FixedArray[Tree[A]]) -> (FixedArray[Int], Int) { /// - `old_t` contains a list of trees, each of (`shift` / `num_bits`) height. /// - `node_counts` contains the number of children of each node in `new_t` (the redistributed version of `old_t`). /// - `node_nums` is the length of `node_counts`. +/// +/// Postcondition: +/// - The resulting trees in `new_t` are of the same height as trees in `old_t`. fn redis[A]( old_t : FixedArray[Tree[A]], node_counts : FixedArray[Int], @@ -559,7 +595,7 @@ fn compute_sizes[A]( shift : Int ) -> FixedArray[Int]? { let len = children.length() - let mut sizes = FixedArray::make(len, 0) + let sizes = FixedArray::make(len, 0) let mut sum = 0 let mut flag = true let full_subtree_size = branching_factor << shift @@ -654,7 +690,7 @@ fn length[A](self : Tree[A], shift : Int) -> Int { match self { Empty => 0 Leaf(l) => l.length() - Node(children, Some(sizes)) => sizes[-1] + Node(_, Some(sizes)) => sizes[-1] Node(children, None) => ((children.length() - 1) << shift) + children[-1].length(shift - num_bits) } From 8fae296a04d55b2ad79f09e3eda2d15b4b750778 Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Sat, 28 Dec 2024 11:41:59 +0800 Subject: [PATCH 06/31] add moon info --- immut/array/array.mbti | 1 + 1 file changed, 1 insertion(+) diff --git a/immut/array/array.mbti b/immut/array/array.mbti index efbb990f6..7a06c3062 100644 --- a/immut/array/array.mbti +++ b/immut/array/array.mbti @@ -7,6 +7,7 @@ alias @moonbitlang/core/quickcheck as @quickcheck // Types and methods type T impl T { + concat[A](Self[A], Self[A]) -> Self[A] copy[A](Self[A]) -> Self[A] each[A](Self[A], (A) -> Unit) -> Unit eachi[A](Self[A], (Int, A) -> Unit) -> Unit From 7706d48dc44df68894bb3c2596433b2ac8c2cbe9 Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Sat, 28 Dec 2024 11:45:48 +0800 Subject: [PATCH 07/31] remove doc test for now --- immut/array/array.mbt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/immut/array/array.mbt b/immut/array/array.mbt index 57727e663..310a20c15 100644 --- a/immut/array/array.mbt +++ b/immut/array/array.mbt @@ -20,14 +20,6 @@ pub fn T::new[A]() -> T[A] { ///| /// Given two trees, concatenate them into a new tree. -/// -/// # Example -/// ``` -/// let a = @immut/array::Tree::from_array([1, 2, 3]) -/// let b = @immut/array::Tree::from_array([4, 5, 6]) -/// let c = a.concat(b) -/// assert_eq!(tree, @immut/array::Tree::from_array([1, 2, 3, 4, 5, 6])) -/// ``` pub fn T::concat[A](self : T[A], other : T[A]) -> T[A] { let (tree, shift) = concat( self.tree, From 7f733bbce576296f54f65a20ce17dc67040b9821 Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Tue, 31 Dec 2024 10:05:35 +0800 Subject: [PATCH 08/31] add 1 simple test --- immut/array/array.mbt | 79 +------------ immut/array/array_wbtest.mbt | 97 ++++++++++++++++ immut/array/moon.pkg.json | 3 +- immut/array/tree.mbt | 140 +++++++---------------- immut/array/tree_utils.mbt | 103 +++++++++++++++++ immut/array/types.mbt | 2 +- immut/array/{operation.mbt => utils.mbt} | 14 ++- 7 files changed, 263 insertions(+), 175 deletions(-) create mode 100644 immut/array/array_wbtest.mbt create mode 100644 immut/array/tree_utils.mbt rename immut/array/{operation.mbt => utils.mbt} (87%) diff --git a/immut/array/array.mbt b/immut/array/array.mbt index 310a20c15..ab3274df3 100644 --- a/immut/array/array.mbt +++ b/immut/array/array.mbt @@ -68,13 +68,14 @@ pub fn T::from_iter[A](iter : Iter[A]) -> T[A] { } ///| -/// pub fn length[A](self : T[A]) -> Int { self.size } ///| -/// +/// Physically copy the array. +/// Since it is an immutable data structure, +/// it is rarely the case that you would need this function. pub fn copy[A](self : T[A]) -> T[A] { fn copy(t : Tree[A]) -> Tree[A] { match t { @@ -95,7 +96,6 @@ pub fn copy[A](self : T[A]) -> T[A] { } ///| -/// /// Get a value at the given index. /// /// # Examples @@ -114,7 +114,6 @@ pub fn op_get[A](self : T[A], index : Int) -> A { } ///| -/// /// Set a value at the given index (immutable). /// /// # Example @@ -131,7 +130,6 @@ pub fn set[A](self : T[A], index : Int, value : A) -> T[A] { } ///| -/// /// Push a value to the end of the array. /// /// # Example @@ -145,7 +143,6 @@ pub fn push[A](self : T[A], value : A) -> T[A] { } ///| -/// /// Create a persistent array from an array. /// /// # Example @@ -158,7 +155,6 @@ pub fn T::from_array[A](arr : Array[A]) -> T[A] { } ///| -/// /// Iterate over the array. /// /// # Example @@ -173,8 +169,7 @@ pub fn each[A](self : T[A], f : (A) -> Unit) -> Unit { } ///| -/// -/// Iterate over the array with index. +////// Iterate over the array with index. /// /// # Example /// ``` @@ -188,13 +183,11 @@ pub fn eachi[A](self : T[A], f : (Int, A) -> Unit) -> Unit { } ///| -/// pub impl[A : Eq] Eq for T[A] with op_equal(self, other) { self.size == other.size && self.tree == other.tree } ///| -/// /// Fold the array. /// /// # Example @@ -207,7 +200,6 @@ pub fn fold[A, B](self : T[A], init~ : B, f : (B, A) -> B) -> B { } ///| -/// /// Fold the array in reverse order. /// /// # Example @@ -220,7 +212,6 @@ pub fn rev_fold[A, B](self : T[A], init~ : B, f : (B, A) -> B) -> B { } ///| -/// /// Fold the array from left to right. /// /// # Example @@ -235,7 +226,6 @@ pub fn fold_left[A](self : T[A], f : (A, A) -> A, init~ : A) -> A { } ///| -/// /// Fold the array from right to left. /// /// # Example @@ -250,7 +240,6 @@ pub fn fold_right[A](self : T[A], f : (A, A) -> A, init~ : A) -> A { } ///| -/// /// Map a function over the array. /// /// # Example @@ -263,7 +252,6 @@ pub fn map[A, B](self : T[A], f : (A) -> B) -> T[B] { } ///| -/// fn new_by_leaves[A](len : Int, gen_leaf : (Int, Int) -> FixedArray[A]) -> T[A] { fn tree(cap, len, s) -> Tree[A] { if cap == branching_factor { @@ -301,45 +289,24 @@ fn new_by_leaves[A](len : Int, gen_leaf : (Int, Int) -> FixedArray[A]) -> T[A] { } } -test "new_by_leaves" { - let e : T[Int] = new_by_leaves(0, fn(_s, _l) { abort("never reach") }) - let v = new_by_leaves(5, fn(_s, l) { FixedArray::make(l, 1) }) - let v2 = new_by_leaves(33, fn(_s, l) { FixedArray::make(l, 10) }) - inspect!(e, content="@immut/array.of([])") - inspect!(v, content="@immut/array.of([1, 1, 1, 1, 1])") - inspect!( - v2, - content="@immut/array.of([10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10])", - ) - let v3 = new_by_leaves(32, fn(_s, l) { FixedArray::make(l, 10) }) - inspect!( - v3, - content="@immut/array.of([10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10])", - ) -} - ///| -/// /// Create a persistent array with a given length and value. pub fn T::make[A](len : Int, value : A) -> T[A] { new_by_leaves(len, fn(_s, l) { FixedArray::make(l, value) }) } ///| -/// /// Create a persistent array with a given length and a function to generate values. pub fn T::makei[A](len : Int, f : (Int) -> A) -> T[A] { new_by_leaves(len, fn(s, l) { FixedArray::makei(l, fn(i) { f(s + i) }) }) } ///| -/// pub fn T::of[A](arr : FixedArray[A]) -> T[A] { makei(arr.length(), fn(i) { arr[i] }) } ///| -/// pub impl[X : @quickcheck.Arbitrary] @quickcheck.Arbitrary for T[X] with arbitrary( size, rs @@ -348,46 +315,8 @@ pub impl[X : @quickcheck.Arbitrary] @quickcheck.Arbitrary for T[X] with arbitrar } ///| -/// pub impl[A : Hash] Hash for T[A] with hash_combine(self, hasher) { for e in self { hasher.combine(e) } } - -test "mix" { - let mut v = T::new() - inspect!(v.tree.is_empty_tree(), content="true") - for i = 0; i < 100; i = i + 1 { - v = v.push(i) - } - inspect!( - v, - content="@immut/array.of([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99])", - ) - let mut v2 = v.copy() - for i = 0; i < 100; i = i + 1 { - v2 = v2.set(i, i * 2) - } - let mut ct = 0 - v.each(fn(e) { ct = ct + e }) - inspect!(ct, content="4950") - v2.each(fn(e) { ct = ct + e }) - inspect!(ct, content="14850") - v2 = v2.map(fn(e) { e * 2 }) - let ct1 = v2.fold(fn(a, b) { a + b }, init=0) - let ct2 = v2.rev_fold(fn(a, b) { a + b }, init=0) - inspect!(ct1, content="19800") - inspect!(ct2, content="19800") - inspect!(v.tree.is_empty_tree(), content="false") - let large_const = branching_factor * branching_factor + 1 - let mut v = T::new() - for i = 0; i < large_const; i = i + 1 { - v = v.push(i) - } - let vec = [] - v.eachi(fn(i, _e) { vec.push(i) }) - for i = 0; i < large_const; i = i + 1 { - assert_eq!(vec[i], i) - } -} diff --git a/immut/array/array_wbtest.mbt b/immut/array/array_wbtest.mbt new file mode 100644 index 000000000..1fc730b46 --- /dev/null +++ b/immut/array/array_wbtest.mbt @@ -0,0 +1,97 @@ +// Copyright 2024 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +test "new_by_leaves" { + let e : T[Int] = new_by_leaves(0, fn(_s, _l) { abort("never reach") }) + let v = new_by_leaves(5, fn(_s, l) { FixedArray::make(l, 1) }) + let v2 = new_by_leaves(33, fn(_s, l) { FixedArray::make(l, 10) }) + inspect!(e, content="@immut/array.of([])") + inspect!(v, content="@immut/array.of([1, 1, 1, 1, 1])") + inspect!( + v2, + content="@immut/array.of([10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10])", + ) + let v3 = new_by_leaves(32, fn(_s, l) { FixedArray::make(l, 10) }) + inspect!( + v3, + content="@immut/array.of([10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10])", + ) +} + +test "mix" { + let mut v = T::new() + inspect!(v.tree.is_empty_tree(), content="true") + for i = 0; i < 100; i = i + 1 { + v = v.push(i) + } + inspect!( + v, + content="@immut/array.of([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99])", + ) + let mut v2 = v.copy() + for i = 0; i < 100; i = i + 1 { + v2 = v2.set(i, i * 2) + } + let mut ct = 0 + v.each(fn(e) { ct = ct + e }) + inspect!(ct, content="4950") + v2.each(fn(e) { ct = ct + e }) + inspect!(ct, content="14850") + v2 = v2.map(fn(e) { e * 2 }) + let ct1 = v2.fold(fn(a, b) { a + b }, init=0) + let ct2 = v2.rev_fold(fn(a, b) { a + b }, init=0) + inspect!(ct1, content="19800") + inspect!(ct2, content="19800") + inspect!(v.tree.is_empty_tree(), content="false") + let large_const = branching_factor * branching_factor + 1 + let mut v = T::new() + for i = 0; i < large_const; i = i + 1 { + v = v.push(i) + } + let vec = [] + v.eachi(fn(i, _e) { vec.push(i) }) + for i = 0; i < large_const; i = i + 1 { + assert_eq!(vec[i], i) + } +} + + +test "concat" { + // 16 elements + let mut v = T::new() + for i = 0; i < 16; i = i + 1 { + v = v.push(i) + } + inspect!( + v, + content="@immut/array.of([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])", + ) + + let mut v2 = T::new() + for i = 0; i < 16; i = i + 1 { + v2 = v2.push(i) + } + inspect!( + v2, + content="@immut/array.of([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])", + ) + + let c = v.concat(v2) + let tree = c.tree + println(tree.to_string()) + inspect!( + c, + content="@immut/array.of([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])", + ) +} diff --git a/immut/array/moon.pkg.json b/immut/array/moon.pkg.json index 687357ba9..d8a1f022b 100644 --- a/immut/array/moon.pkg.json +++ b/immut/array/moon.pkg.json @@ -5,8 +5,7 @@ { "path": "moonbitlang/core/array", "alias": "_core/array" - }, - "moonbitlang/core/bool" + } ], "targets": { "panic_test.mbt": ["not", "native"], diff --git a/immut/array/tree.mbt b/immut/array/tree.mbt index ab8bf05bf..1c08f3755 100644 --- a/immut/array/tree.mbt +++ b/immut/array/tree.mbt @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +/// A tree data structure that backs the `immut/array`. + ///| -let num_bits = 5 +let num_bits = 2 ///| /// Invariant: `branching_factor` is a power of 2. @@ -230,7 +232,6 @@ fn push_end[T](self : Tree[T], shift : Int, value : T) -> (Tree[T], Int) { } ///| -/// /// For each element in the tree, apply the function `f`. fn each[A](self : Tree[A], f : (A) -> Unit) -> Unit { match self { @@ -241,7 +242,6 @@ fn each[A](self : Tree[A], f : (A) -> Unit) -> Unit { } ///| -/// /// For each element in the tree, apply the function `f` with the index of the element. fn eachi[A]( self : Tree[A], @@ -396,12 +396,12 @@ fn rebalance[A]( // Suppose H = shift / num_bits let t = tri_merge(left, center, right) // t is a list of trees of (H-1) height let (nc, nc_len) = redis_plan(t) - let new_t = redis(t, nc, nc_len, shift) // new_t is a list of trees of (H-1) height + let new_t = redis(t, nc, nc_len, shift - num_bits) // new_t is a list of trees of (H-1) height if nc_len < branching_factor { guard new_t.length() == 1 // All nodes can be accommodated in a single node if not(top) { - return (Node(new_t, compute_sizes(new_t, shift)), shift) // return H height node + return (Node(new_t, compute_sizes(new_t, shift-num_bits)), shift) // return H height node } else { return (new_t[0], shift - num_bits) // return H-1 height node } @@ -412,10 +412,13 @@ fn rebalance[A]( ) { new_t[i + branching_factor] }) - let new_node_1 = Node(new_child_1, compute_sizes(new_child_1, shift)) - let new_node_2 = Node(new_child_2, compute_sizes(new_child_2, shift)) + let new_node_1 = Node(new_child_1, compute_sizes(new_child_1, shift-num_bits)) // height H + let new_node_2 = Node(new_child_2, compute_sizes(new_child_2, shift-num_bits)) // height H let new_children = FixedArray::from_array([new_node_1, new_node_2]) - return (Node(new_children, compute_sizes(new_children, shift)), shift) // return H height node + return ( + Node(new_children, compute_sizes(new_children, shift)), + shift + num_bits, + ) // return (H+1) height node } } @@ -453,15 +456,19 @@ fn tri_merge[A]( let center_children = get_children(center) let right_children = get_children(right) let left_len = left_children.length() + let left_len = if left_len == 0 { 0 } else { left_len - 1 } let center_len = center_children.length() let right_len = right_children.length() + let right_len = if right_len == 0 { 0 } else { right_len - 1 } FixedArray::makei(left_len + center_len + right_len, fn(i) { if i < left_len { left_children[i] } else if i < left_len + center_len { center_children[i - left_len] + } else if right_len > 0 { + right_children[1 + i - left_len - center_len] } else { - right_children[i - left_len - center_len] + abort("Unreachable") } }) } @@ -581,7 +588,7 @@ fn redis[A]( old_offset = 0 } } - new_t[i] = Node(new_node_chldrn, compute_sizes(new_node_chldrn, shift)) + new_t[i] = Node(new_node_chldrn, compute_sizes(new_node_chldrn, shift)) // each node in `new_t` is of height (`shift` / `num_bits`) } } } @@ -613,94 +620,35 @@ fn compute_sizes[A]( } ///| -fn is_node[A](self : Tree[A]) -> Bool { - match self { - Node(_, _) => true - _ => false - } -} - -///| -fn is_leaf[A](self : Tree[A]) -> Bool { - match self { - Leaf(_) => true - _ => false - } -} - -///| -fn is_empty[A](self : Tree[A]) -> Bool { - match self { - Empty => true - _ => false - } -} - -///| -/// Get the rightmost child of a tree node. Abort if -/// it is not a `Node`. -fn right_child[A](self : Tree[A]) -> Tree[A] { - match self { - Node(children, _) => children[-1] - Leaf(_) | Empty => abort("Should not get children on non-`Node`s") - } -} - -///| -/// Get the leftmost child of a tree node. Abort if -/// it is not a `Node`. -fn left_child[A](self : Tree[A]) -> Tree[A] { - match self { - Node(children, _) => children[0] - Leaf(_) | Empty => abort("Should not get children on non-`Node`s") - } -} - -///| -/// Get the leaf contents. Abort if it is not a `Leaf`. -fn leaf_elements[A](self : Tree[A]) -> FixedArray[A] { - match self { - Leaf(children) => children - _ => abort("Should not call `get_leaf_elements` on non-leaf nodes") - } -} - -///| -/// Get the children of a `Node`. Abort if it is not a `Node`. -fn node_children[A](self : Tree[A]) -> FixedArray[Tree[A]] { - match self { - Node(children, _) => children - _ => abort("Should not call `node_children` on non-`Node`s") +/// Print the tree structure. +impl[A : Show] Show for Tree[A] with output(self, logger : &Logger) { + fn indent_str(s : String, indent : Int) -> String { + String::make(indent, ' ') + s } -} -///| -/// Get the physical size of the current node, not the total number of elements in the tree. -fn size[A](self : Tree[A]) -> Int { - match self { - Empty => 0 - Leaf(l) => l.length() - Node(children, _) => children.length() - } -} - -///| -/// Get the total number of elements in the tree. -fn length[A](self : Tree[A], shift : Int) -> Int { - match self { - Empty => 0 - Leaf(l) => l.length() - Node(_, Some(sizes)) => sizes[-1] - Node(children, None) => - ((children.length() - 1) << shift) + children[-1].length(shift - num_bits) + fn rec(t : Tree[A], ident : Int) { + match t { + Empty => indent_str("Empty", ident) + Leaf(l) => { + let mut s = "Leaf(" + for i = 0; i < l.length(); i = i + 1 { + s += l[i].to_string() + if i != l.length() - 1 { + s += ", " + } + } + s += ")" + indent_str(s, ident) + "\n" + } + Node(children, _sizes) => { + let mut s = indent_str("Node(", ident) + "\n" + for i = 0; i < children.length(); i = i + 1 { + s += rec(children[i], ident + 2) + } + s + indent_str(")", ident) + "\n" + } + } } -} -///| -fn min(a : Int, b : Int) -> Int { - if a < b { - a - } else { - b - } + logger.write_string(rec(self, 0)) } diff --git a/immut/array/tree_utils.mbt b/immut/array/tree_utils.mbt new file mode 100644 index 000000000..a36185a64 --- /dev/null +++ b/immut/array/tree_utils.mbt @@ -0,0 +1,103 @@ +// Copyright 2024 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// Utility functions for working with trees. +/// Other utility functions are in `utils.mbt`. + +///| +/// If the tree is a `Node`. +fn is_node[A](self : Tree[A]) -> Bool { + match self { + Node(_, _) => true + _ => false + } +} + +///| +fn is_leaf[A](self : Tree[A]) -> Bool { + match self { + Leaf(_) => true + _ => false + } +} + +///| +fn is_empty[A](self : Tree[A]) -> Bool { + match self { + Empty => true + _ => false + } +} + +///| +/// Get the rightmost child of a tree node. Abort if +/// it is not a `Node`. +fn right_child[A](self : Tree[A]) -> Tree[A] { + match self { + Node(children, _) => children[children.length() - 1] + Leaf(_) | Empty => abort("Should not get children on non-`Node`s") + } +} + +///| +/// Get the leftmost child of a tree node. Abort if +/// it is not a `Node`. +fn left_child[A](self : Tree[A]) -> Tree[A] { + match self { + Node(children, _) => children[0] + Leaf(_) | Empty => abort("Should not get children on non-`Node`s") + } +} + +///| +/// Get the leaf contents. Abort if it is not a `Leaf`. +fn leaf_elements[A](self : Tree[A]) -> FixedArray[A] { + match self { + Leaf(children) => children + _ => abort("Should not call `get_leaf_elements` on non-leaf nodes") + } +} + +///| +/// Get the children of a `Node`. Abort if it is not a `Node`. +fn node_children[A](self : Tree[A]) -> FixedArray[Tree[A]] { + match self { + Node(children, _) => children + _ => abort("Should not call `node_children` on non-`Node`s") + } +} + +///| +/// Get the physical size of the current node, not the total number of elements in the tree. +fn size[A](self : Tree[A]) -> Int { + match self { + Empty => 0 + Leaf(l) => l.length() + Node(children, _) => children.length() + } +} + +///| +/// Get the total number of elements in the tree. +fn length[A](self : Tree[A], shift : Int) -> Int { + match self { + Empty => 0 + Leaf(l) => l.length() + Node(_, Some(sizes)) => sizes[-1] + Node(children, None) => { + let len_1 = children.length() - 1 + (len_1 << shift) + children[len_1].length(shift - num_bits) + } + } +} diff --git a/immut/array/types.mbt b/immut/array/types.mbt index e9688c7d4..579e97837 100644 --- a/immut/array/types.mbt +++ b/immut/array/types.mbt @@ -30,4 +30,4 @@ priv enum Tree[A] { Empty Node(FixedArray[Tree[A]], FixedArray[Int]?) // (Subtrees, Sizes of subtrees) Leaf(FixedArray[A]) -} derive(Eq, Show) +} derive(Eq) diff --git a/immut/array/operation.mbt b/immut/array/utils.mbt similarity index 87% rename from immut/array/operation.mbt rename to immut/array/utils.mbt index 9cf85783b..549e74ca0 100644 --- a/immut/array/operation.mbt +++ b/immut/array/utils.mbt @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +/// Utils for `FixedArray`s in the immutable array implementation. +/// Typically utility functions that are not related with trees. + ///| /// Set the value at the given index. This operation is O(n). fn immutable_set[T](arr : FixedArray[T], i : Int, v : T) -> FixedArray[T] { @@ -31,6 +34,7 @@ fn immutable_push[T](arr : FixedArray[T], val : T) -> FixedArray[T] { } ///| +/// x >> y as unsigned integers, then reinterpret as signed integers. fn shr_as_uint(x : Int, y : Int) -> Int { (x.reinterpret_as_uint() >> y).reinterpret_as_int() } @@ -42,7 +46,6 @@ fn radix_indexing(index : Int, shift : Int) -> Int { } ///| -/// /// Get the index of the branch that contains the given index. /// For example, if the sizes are [0, 3, 6, 10] and the index is 5, the function should return 2. fn get_branch_index(sizes : FixedArray[Int], index : Int) -> Int { @@ -70,3 +73,12 @@ fn copy_sizes(sizes : FixedArray[Int]?) -> FixedArray[Int]? { None => None } } + +///| +fn min(a : Int, b : Int) -> Int { + if a < b { + a + } else { + b + } +} From d753242f52834b8a7e4c1552bec6c8de165a3b45 Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Tue, 31 Dec 2024 15:33:27 +0800 Subject: [PATCH 09/31] add more tests --- immut/array/array_wbtest.mbt | 116 ++++++++++++++++++++++++++++------- immut/array/tree.mbt | 27 +++++--- immut/array/tree_utils.mbt | 8 +-- 3 files changed, 117 insertions(+), 34 deletions(-) diff --git a/immut/array/array_wbtest.mbt b/immut/array/array_wbtest.mbt index 1fc730b46..cc4456e69 100644 --- a/immut/array/array_wbtest.mbt +++ b/immut/array/array_wbtest.mbt @@ -66,32 +66,104 @@ test "mix" { } } +test "concat-two-full-tree" { + let bf2 = branching_factor * branching_factor + let v_content = [] + for i = 0; i < bf2; i = i + 1 { + v_content.push(i) + } + let v_content = FixedArray::from_array(v_content) + let v = T::of(v_content) + inspect!(v, content="@immut/array.of(\{v_content})") + let v2_content = [] + for i = 0; i < bf2; i = i + 1 { + v2_content.push(i) + } + let v2_content = FixedArray::from_array(v2_content) + let v2 = T::of(v2_content) + inspect!(v2, content="@immut/array.of(\{v2_content})") + let c = v.concat(v2) + let c_content = v_content + v2_content + inspect!(c, content="@immut/array.of(\{c_content})") +} -test "concat" { - // 16 elements - let mut v = T::new() - for i = 0; i < 16; i = i + 1 { - v = v.push(i) +test "concat-full-tree-and-a-leaf" { + let bf2 = branching_factor * branching_factor + let v_content = [] + for i = 0; i < bf2; i = i + 1 { + v_content.push(i) } - inspect!( - v, - content="@immut/array.of([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])", - ) + let v_content = FixedArray::from_array(v_content) + let v = T::of(v_content) + inspect!(v, content="@immut/array.of(\{v_content})") + let v2_content = FixedArray::from_array([0]) + let v2 = T::of(v2_content) + inspect!(v2, content="@immut/array.of(\{v2_content})") + let c = v.concat(v2) + let c_content = v_content + v2_content + inspect!(c, content="@immut/array.of(\{c_content})") +} - let mut v2 = T::new() - for i = 0; i < 16; i = i + 1 { - v2 = v2.push(i) +test "concat-a-leaf-and-full-tree" { + let v_content = FixedArray::from_array([0]) + let v = T::of(v_content) + inspect!(v, content="@immut/array.of(\{v_content})") + let bf2 = branching_factor * branching_factor + let v2_content = [] + for i = 0; i < bf2; i = i + 1 { + v2_content.push(i) } - inspect!( - v2, - content="@immut/array.of([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])", - ) + let v2_content = FixedArray::from_array(v2_content) + let v2 = T::of(v2_content) + inspect!(v2, content="@immut/array.of(\{v2_content})") + let c = v.concat(v2) + let c_content = v_content + v2_content + inspect!(c, content="@immut/array.of(\{c_content})") +} +test "concat-two-leaf" { + let v_content = FixedArray::makei(branching_factor, fn(i) { i }) + let v = T::of(v_content) + inspect!(v, content="@immut/array.of(\{v_content})") + let v2_content = FixedArray::makei(branching_factor, fn(i) { + i + branching_factor + }) + let v2 = T::of(v2_content) + inspect!(v2, content="@immut/array.of(\{v2_content})") let c = v.concat(v2) - let tree = c.tree - println(tree.to_string()) - inspect!( - c, - content="@immut/array.of([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])", - ) + let c_content = v_content + v2_content + inspect!(c, content="@immut/array.of(\{c_content})") +} + +test "concat-three-full-tree" { + let bf2 = branching_factor * branching_factor + let t = T::new() + for i = 0; i<10;i=i+1{ + let v1_content = [] + for i = 0; i < bf2; i = i + 1 { + v1_content.push(i) + } + let v1_content = FixedArray::from_array(v1_content) + let v1 = T::of(v1_content) + inspect!(v1, content="@immut/array.of(\{v1_content})") + + + } + + let v1_content = [] + for i = 0; i < bf2; i = i + 1 { + v1_content.push(i) + } + let v1_content = FixedArray::from_array(v1_content) + let v1 = T::of(v1_content) + inspect!(v1, content="@immut/array.of(\{v1_content})") + + let v1_content = [] + for i = 0; i < bf2; i = i + 1 { + v1_content.push(i) + } + let v1_content = FixedArray::from_array(v1_content) + let v1 = T::of(v1_content) + inspect!(v1, content="@immut/array.of(\{v1_content})") + } diff --git a/immut/array/tree.mbt b/immut/array/tree.mbt index 1c08f3755..b018faa51 100644 --- a/immut/array/tree.mbt +++ b/immut/array/tree.mbt @@ -15,6 +15,8 @@ /// A tree data structure that backs the `immut/array`. ///| +/// The controlling factor of the tree depth. +/// This is the only parameter you normally would adjust. let num_bits = 2 ///| @@ -397,13 +399,16 @@ fn rebalance[A]( let t = tri_merge(left, center, right) // t is a list of trees of (H-1) height let (nc, nc_len) = redis_plan(t) let new_t = redis(t, nc, nc_len, shift - num_bits) // new_t is a list of trees of (H-1) height + guard new_t.length() == nc_len if nc_len < branching_factor { - guard new_t.length() == 1 // All nodes can be accommodated in a single node + let node = Node(new_t, compute_sizes(new_t, shift - num_bits)) // node of H height if not(top) { - return (Node(new_t, compute_sizes(new_t, shift-num_bits)), shift) // return H height node + return (Node(FixedArray::from_array([node]), None), shift + num_bits) + // return (H+1) height node, add another layer to align with the case at the end of the thisfunction } else { - return (new_t[0], shift - num_bits) // return H-1 height node + return (node, shift) + // return H height node, no upper node so no need to add another layer on top of it } } else { let new_child_1 = FixedArray::makei(branching_factor, fn(i) { new_t[i] }) @@ -412,8 +417,14 @@ fn rebalance[A]( ) { new_t[i + branching_factor] }) - let new_node_1 = Node(new_child_1, compute_sizes(new_child_1, shift-num_bits)) // height H - let new_node_2 = Node(new_child_2, compute_sizes(new_child_2, shift-num_bits)) // height H + let new_node_1 = Node( + new_child_1, + compute_sizes(new_child_1, shift - num_bits), + ) // height H + let new_node_2 = Node( + new_child_2, + compute_sizes(new_child_2, shift - num_bits), + ) // height H let new_children = FixedArray::from_array([new_node_1, new_node_2]) return ( Node(new_children, compute_sizes(new_children, shift)), @@ -476,7 +487,7 @@ fn tri_merge[A]( ///| /// Create a redistribution plan for the tree. fn redis_plan[A](t : FixedArray[Tree[A]]) -> (FixedArray[Int], Int) { - let node_counts = FixedArray::makei(t.length(), fn { i => t[i].size() }) + let node_counts = FixedArray::makei(t.length(), fn { i => t[i].local_size() }) let total_nodes = node_counts.fold(init=0, fn { acc, x => acc + x }) // round up to the nearest integer of S/branching_factor let opt_len = (total_nodes + branching_factor - 1) / branching_factor @@ -596,7 +607,7 @@ fn redis[A]( } ///| -/// Given a list of trees as `children` and the `shift` of them. +/// Given a list of trees as `children` with heights of (`shift` / `num_bits`), compute the sizes array of the subtrees. fn compute_sizes[A]( children : FixedArray[Tree[A]], shift : Int @@ -607,7 +618,7 @@ fn compute_sizes[A]( let mut flag = true let full_subtree_size = branching_factor << shift for i = 0; i < len; i = i + 1 { - let sz = children[i].length(shift) + let sz = children[i].size(shift) flag = flag && sz == full_subtree_size sum += sz sizes[i] = sum diff --git a/immut/array/tree_utils.mbt b/immut/array/tree_utils.mbt index a36185a64..fb338c88a 100644 --- a/immut/array/tree_utils.mbt +++ b/immut/array/tree_utils.mbt @@ -80,7 +80,7 @@ fn node_children[A](self : Tree[A]) -> FixedArray[Tree[A]] { ///| /// Get the physical size of the current node, not the total number of elements in the tree. -fn size[A](self : Tree[A]) -> Int { +fn local_size[A](self : Tree[A]) -> Int { match self { Empty => 0 Leaf(l) => l.length() @@ -90,14 +90,14 @@ fn size[A](self : Tree[A]) -> Int { ///| /// Get the total number of elements in the tree. -fn length[A](self : Tree[A], shift : Int) -> Int { +fn size[A](self : Tree[A], shift : Int) -> Int { match self { Empty => 0 Leaf(l) => l.length() - Node(_, Some(sizes)) => sizes[-1] + Node(_, Some(sizes)) => sizes[sizes.length() - 1] Node(children, None) => { let len_1 = children.length() - 1 - (len_1 << shift) + children[len_1].length(shift - num_bits) + (len_1 << shift) + children[len_1].size(shift - num_bits) } } } From 5b38a17742048393988dc2919fb2228006b21860 Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Tue, 31 Dec 2024 16:38:46 +0800 Subject: [PATCH 10/31] add random test --- immut/array/array_wbtest.mbt | 63 ++++++++++++++++++++---------------- immut/array/moon.pkg.json | 1 + immut/array/tree.mbt | 2 +- 3 files changed, 38 insertions(+), 28 deletions(-) diff --git a/immut/array/array_wbtest.mbt b/immut/array/array_wbtest.mbt index cc4456e69..5b394ae74 100644 --- a/immut/array/array_wbtest.mbt +++ b/immut/array/array_wbtest.mbt @@ -1,4 +1,4 @@ -// Copyright 2024 International Digital Economy Academy +/ Copyright 2024 International Digital Economy Academy // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -135,35 +135,44 @@ test "concat-two-leaf" { inspect!(c, content="@immut/array.of(\{c_content})") } -test "concat-three-full-tree" { - let bf2 = branching_factor * branching_factor - let t = T::new() - for i = 0; i<10;i=i+1{ - let v1_content = [] - for i = 0; i < bf2; i = i + 1 { - v1_content.push(i) - } - let v1_content = FixedArray::from_array(v1_content) - let v1 = T::of(v1_content) - inspect!(v1, content="@immut/array.of(\{v1_content})") - - +test "concat-multiple-full-tree" { + let bf2 = branching_factor * branching_factor + let mut t = T::new() + let mut t_content = FixedArray::from_array([]) + for i = 0; i < 42; i = i + 1 { + let v1_content = [] + for i = 0; i < bf2; i = i + 1 { + v1_content.push(i) } - - let v1_content = [] - for i = 0; i < bf2; i = i + 1 { - v1_content.push(i) + let v1_content = FixedArray::from_array(v1_content) + let v1 = T::of(v1_content) + inspect!(v1, content="@immut/array.of(\{v1_content})") + t = t.concat(v1) + t_content = t_content + v1_content + inspect!(t, content="@immut/array.of(\{t_content})") } - let v1_content = FixedArray::from_array(v1_content) - let v1 = T::of(v1_content) - inspect!(v1, content="@immut/array.of(\{v1_content})") +} - let v1_content = [] - for i = 0; i < bf2; i = i + 1 { - v1_content.push(i) +test "concat-multiple-random-tree" { + fn rand(upper : Int) -> Int { + let rng = @random.new() + rng.int() % upper } - let v1_content = FixedArray::from_array(v1_content) - let v1 = T::of(v1_content) - inspect!(v1, content="@immut/array.of(\{v1_content})") + let bq4 = branching_factor * + branching_factor * + branching_factor * + branching_factor + let max_val = 2024 + let mut t = T::new() + let mut t_content = FixedArray::from_array([]) + for i = 0; i < 42; i = i + 1 { + let len = rand(bq4) + let v1_content = FixedArray::makei(len, fn(_i) { rand(max_val) }) + let v1 = T::of(v1_content) + inspect!(v1, content="@immut/array.of(\{v1_content})") + t = t.concat(v1) + t_content = t_content + v1_content + inspect!(t, content="@immut/array.of(\{t_content})") + } } diff --git a/immut/array/moon.pkg.json b/immut/array/moon.pkg.json index d8a1f022b..1b4b0bd84 100644 --- a/immut/array/moon.pkg.json +++ b/immut/array/moon.pkg.json @@ -7,6 +7,7 @@ "alias": "_core/array" } ], + "wbtest-import": ["moonbitlang/core/random"], "targets": { "panic_test.mbt": ["not", "native"], "panic_wbtest.mbt": ["not", "native"] diff --git a/immut/array/tree.mbt b/immut/array/tree.mbt index b018faa51..d775f64ab 100644 --- a/immut/array/tree.mbt +++ b/immut/array/tree.mbt @@ -400,7 +400,7 @@ fn rebalance[A]( let (nc, nc_len) = redis_plan(t) let new_t = redis(t, nc, nc_len, shift - num_bits) // new_t is a list of trees of (H-1) height guard new_t.length() == nc_len - if nc_len < branching_factor { + if nc_len <= branching_factor { // All nodes can be accommodated in a single node let node = Node(new_t, compute_sizes(new_t, shift - num_bits)) // node of H height if not(top) { From 221d5b78b38f5b22dad3823a15e0b284f14f1e2c Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Tue, 31 Dec 2024 16:49:07 +0800 Subject: [PATCH 11/31] fix empty --- immut/array/array.mbt | 8 +++++++- immut/array/array_wbtest.mbt | 2 +- immut/array/tree.mbt | 4 ++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/immut/array/array.mbt b/immut/array/array.mbt index ab3274df3..40afdddf8 100644 --- a/immut/array/array.mbt +++ b/immut/array/array.mbt @@ -21,6 +21,12 @@ pub fn T::new[A]() -> T[A] { ///| /// Given two trees, concatenate them into a new tree. pub fn T::concat[A](self : T[A], other : T[A]) -> T[A] { + if self.is_empty() { + return other + } + if other.is_empty() { + return self + } let (tree, shift) = concat( self.tree, self.shift, @@ -169,7 +175,7 @@ pub fn each[A](self : T[A], f : (A) -> Unit) -> Unit { } ///| -////// Iterate over the array with index. +/// Iterate over the array with index. /// /// # Example /// ``` diff --git a/immut/array/array_wbtest.mbt b/immut/array/array_wbtest.mbt index 5b394ae74..16580fcd7 100644 --- a/immut/array/array_wbtest.mbt +++ b/immut/array/array_wbtest.mbt @@ -1,4 +1,4 @@ -/ Copyright 2024 International Digital Economy Academy +// Copyright 2024 International Digital Economy Academy // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/immut/array/tree.mbt b/immut/array/tree.mbt index d775f64ab..93fbd783f 100644 --- a/immut/array/tree.mbt +++ b/immut/array/tree.mbt @@ -313,6 +313,10 @@ fn map[A, B](self : Tree[A], f : (A) -> B) -> Tree[B] { ///| /// Concatenate two trees. /// Should be called as with `top = true`. +/// +/// Preconditions: +/// - `left` and `right` are not `Empty`. +/// - `left` and `right` are of height `left_shift / num_bits` and `right_shift / num_bits`, respectively. fn concat[A]( left : Tree[A], left_shift : Int, From a54a083576dfa8f32a3ba69e831b1d9ddb5daaa6 Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Tue, 31 Dec 2024 17:52:36 +0800 Subject: [PATCH 12/31] doc --- immut/array/array_wbtest.mbt | 57 ++++++++++++++------------ immut/array/tree.mbt | 77 ++++++++++++++++++++++++++++-------- 2 files changed, 92 insertions(+), 42 deletions(-) diff --git a/immut/array/array_wbtest.mbt b/immut/array/array_wbtest.mbt index 16580fcd7..c7ddffd43 100644 --- a/immut/array/array_wbtest.mbt +++ b/immut/array/array_wbtest.mbt @@ -137,42 +137,49 @@ test "concat-two-leaf" { test "concat-multiple-full-tree" { let bf2 = branching_factor * branching_factor - let mut t = T::new() - let mut t_content = FixedArray::from_array([]) - for i = 0; i < 42; i = i + 1 { - let v1_content = [] - for i = 0; i < bf2; i = i + 1 { - v1_content.push(i) - } - let v1_content = FixedArray::from_array(v1_content) - let v1 = T::of(v1_content) - inspect!(v1, content="@immut/array.of(\{v1_content})") - t = t.concat(v1) - t_content = t_content + v1_content - inspect!(t, content="@immut/array.of(\{t_content})") - } + seq_concat_test!( + 42, + fn(_i) { FixedArray::makei(bf2, fn(i) { i }) }, + FixedArray::from_array([]), + ) } test "concat-multiple-random-tree" { - fn rand(upper : Int) -> Int { - let rng = @random.new() - rng.int() % upper - } - let bq4 = branching_factor * branching_factor * branching_factor * branching_factor let max_val = 2024 - let mut t = T::new() - let mut t_content = FixedArray::from_array([]) - for i = 0; i < 42; i = i + 1 { - let len = rand(bq4) - let v1_content = FixedArray::makei(len, fn(_i) { rand(max_val) }) + seq_concat_test!( + 42, + fn(_i) { + let len = rand(bq4) + FixedArray::makei(len, fn(_i) { rand(max_val) }) + }, + FixedArray::from_array([]), + ) +} + +///| +fn rand(upper : Int) -> Int { + let rng = @random.new() + rng.int() % upper +} + +///| +fn seq_concat_test[A : Show]( + rep : Int, + gen : (Int) -> FixedArray[A], + init : FixedArray[A] +) -> Unit! { + let mut t = T::of(init) + let mut t_content = init + for i = 0; i < rep; i = i + 1 { + let v1_content = gen(i) let v1 = T::of(v1_content) inspect!(v1, content="@immut/array.of(\{v1_content})") t = t.concat(v1) t_content = t_content + v1_content - inspect!(t, content="@immut/array.of(\{t_content})") + inspect!(init, content="@immut/array.of(\{t_content})") } } diff --git a/immut/array/tree.mbt b/immut/array/tree.mbt index 93fbd783f..7ec081de5 100644 --- a/immut/array/tree.mbt +++ b/immut/array/tree.mbt @@ -14,6 +14,10 @@ /// A tree data structure that backs the `immut/array`. +//----------------------------------------------------------------------------- +// Hyperparameters +//------------------------------------------------------------------------- + ///| /// The controlling factor of the tree depth. /// This is the only parameter you normally would adjust. @@ -38,12 +42,42 @@ const E_MAX : Int = 2 /// $e_{max} / 2$. let e_max_2 : Int = E_MAX / 2 +//----------------------------------------------------------------------------- +// Constructors +//----------------------------------------------------------------------------- + ///| fn Tree::empty[T]() -> Tree[T] { Tree::Empty } ///| +/// Create a new tree with a single leaf. Note that the resulting tree is a left-skewed tree. +fn new_branch_left[T](leaf : FixedArray[T], shift : Int) -> Tree[T] { + match shift { + 0 => Leaf(leaf) + s => Node([new_branch_left(leaf, s - num_bits)], None) // size is None because we can use radix indexing + } +} + +//----------------------------------------------------------------------------- +// Properties +//----------------------------------------------------------------------------- + +///| +fn is_empty_tree[T](self : Tree[T]) -> Bool { + match self { + Tree::Empty => true + _ => false + } +} + +//----------------------------------------------------------------------------- +// Getters +//----------------------------------------------------------------------------- + +///| +/// Get the rightmost child of a tree. fn get_first[T](self : Tree[T]) -> T { match self { Leaf(leaf) => leaf[0] @@ -53,6 +87,7 @@ fn get_first[T](self : Tree[T]) -> T { } ///| +/// Get the leftmost child of a tree. fn get_last[T](self : Tree[T]) -> T { match self { Leaf(leaf) => leaf[leaf.length() - 1] @@ -62,6 +97,10 @@ fn get_last[T](self : Tree[T]) -> T { } ///| +/// Get the element at the given index. +/// +/// Precondition: +/// - `self` is of height `shift / num_bits`. fn get[T](self : Tree[T], index : Int, shift : Int) -> T { fn get_radix(node : Tree[T], shift : Int) -> T { match node { @@ -86,7 +125,15 @@ fn get[T](self : Tree[T], index : Int, shift : Int) -> T { } } +//----------------------------------------------------------------------------- +// Mutators +//----------------------------------------------------------------------------- + ///| +/// Set a value at the given index. +/// +/// Precondition: +/// - `self` is of height `shift / num_bits`. fn set[T](self : Tree[T], index : Int, shift : Int, value : T) -> Tree[T] { fn set_radix(node : Tree[T], shift : Int) -> Tree[T] { match node { @@ -127,25 +174,9 @@ fn set[T](self : Tree[T], index : Int, shift : Int, value : T) -> Tree[T] { } } -///| -fn is_empty_tree[T](self : Tree[T]) -> Bool { - match self { - Tree::Empty => true - _ => false - } -} - -///| -/// Create a new tree with a single leaf. Note that the resulting tree is a left-skewed tree. -fn new_branch_left[T](leaf : FixedArray[T], shift : Int) -> Tree[T] { - match shift { - 0 => Leaf(leaf) - s => Node([new_branch_left(leaf, s - num_bits)], None) // size is None because we can use radix indexing - } -} - ///| /// Push a value to the end of the tree. +/// /// Precondition: /// - The height of `self` = `shift` / `num_bits` (the height starts from 0). /// - `length` is the number of elements in the tree. @@ -233,6 +264,10 @@ fn push_end[T](self : Tree[T], shift : Int, value : T) -> (Tree[T], Int) { } } +//----------------------------------------------------------------------------- +// Iteration +//----------------------------------------------------------------------------- + ///| /// For each element in the tree, apply the function `f`. fn each[A](self : Tree[A], f : (A) -> Unit) -> Unit { @@ -310,6 +345,10 @@ fn map[A, B](self : Tree[A], f : (A) -> B) -> Tree[B] { } } +//----------------------------------------------------------------------------- +// Concatenation +//----------------------------------------------------------------------------- + ///| /// Concatenate two trees. /// Should be called as with `top = true`. @@ -634,6 +673,10 @@ fn compute_sizes[A]( } } +//----------------------------------------------------------------------------- +// Common Trait Implementations +//----------------------------------------------------------------------------- + ///| /// Print the tree structure. impl[A : Show] Show for Tree[A] with output(self, logger : &Logger) { From 4f9f0c0f525f2c33e99a6e181b163ed8fe31d223 Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Wed, 1 Jan 2025 11:40:06 +0800 Subject: [PATCH 13/31] add random test generator --- immut/array/array.mbt | 226 ++++++++++++++++++------------- immut/array/array_mix_wbtest.mbt | 116 ++++++++++++++++ immut/array/array_wbtest.mbt | 13 +- immut/array/tree.mbt | 4 +- 4 files changed, 254 insertions(+), 105 deletions(-) create mode 100644 immut/array/array_mix_wbtest.mbt diff --git a/immut/array/array.mbt b/immut/array/array.mbt index 40afdddf8..3e3ee392e 100644 --- a/immut/array/array.mbt +++ b/immut/array/array.mbt @@ -12,6 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +//----------------------------------------------------------------------------- +// Constructors (construct T or convert other types to T) +//----------------------------------------------------------------------------- + ///| /// Return a new empty array pub fn T::new[A]() -> T[A] { @@ -19,63 +23,21 @@ pub fn T::new[A]() -> T[A] { } ///| -/// Given two trees, concatenate them into a new tree. -pub fn T::concat[A](self : T[A], other : T[A]) -> T[A] { - if self.is_empty() { - return other - } - if other.is_empty() { - return self - } - let (tree, shift) = concat( - self.tree, - self.shift, - other.tree, - other.shift, - true, - ) - { tree, size: self.size + other.size, shift } -} - -///| -pub impl[A : Show] Show for T[A] with output(self, logger) { - logger.write_iter(self.iter(), prefix="@immut/array.of([", suffix="])") -} - -///| -pub fn is_empty[A](self : T[A]) -> Bool { - self.size == 0 -} - -///| -pub fn to_array[A](self : T[A]) -> Array[A] { - let arr = [] - self.each(fn(v) { arr.push(v) }) - arr -} - -///| -pub fn iter[A](self : T[A]) -> Iter[A] { - Iter::new(fn(yield_) { - let arr = self.to_array() - for i = 0; i < self.size; i = i + 1 { - if yield_(arr[i]) == IterEnd { - break IterEnd - } - } else { - IterContinue - } - }) +/// Create a persistent array with a given length and value. +pub fn T::make[A](len : Int, value : A) -> T[A] { + new_by_leaves(len, fn(_s, l) { FixedArray::make(l, value) }) } ///| -pub fn T::from_iter[A](iter : Iter[A]) -> T[A] { - iter.fold(init=new(), fn(arr, e) { arr.push(e) }) +/// Create a persistent array with a given length and a function to generate values. +pub fn T::makei[A](len : Int, f : (Int) -> A) -> T[A] { + new_by_leaves(len, fn(s, l) { FixedArray::makei(l, fn(i) { f(s + i) }) }) } ///| -pub fn length[A](self : T[A]) -> Int { - self.size +/// Convert a FixedArray to an @immut/array. +pub fn T::of[A](arr : FixedArray[A]) -> T[A] { + makei(arr.length(), fn(i) { arr[i] }) } ///| @@ -101,6 +63,52 @@ pub fn copy[A](self : T[A]) -> T[A] { { tree: copy(self.tree), size: self.size, shift: self.shift } } +///| +/// Create a persistent array from an array. +/// +/// # Example +/// ``` +/// let v = @array.of([1, 2, 3]) +/// assert_eq!(v, @array.from_array([1, 2, 3])) +/// ``` +pub fn T::from_array[A](arr : Array[A]) -> T[A] { + makei(arr.length(), fn(i) { arr[i] }) +} + +//----------------------------------------------------------------------------- +// Convertor (convert T to other types) +//----------------------------------------------------------------------------- + +///| +pub fn to_array[A](self : T[A]) -> Array[A] { + let arr = [] + self.each(fn(v) { arr.push(v) }) + arr +} + +///| +pub fn T::from_iter[A](iter : Iter[A]) -> T[A] { + iter.fold(init=new(), fn(arr, e) { arr.push(e) }) +} + +//----------------------------------------------------------------------------- +// Properties +//----------------------------------------------------------------------------- + +///| +pub fn is_empty[A](self : T[A]) -> Bool { + self.size == 0 +} + +///| +pub fn length[A](self : T[A]) -> Int { + self.size +} + +//----------------------------------------------------------------------------- +// Lookup +//----------------------------------------------------------------------------- + ///| /// Get a value at the given index. /// @@ -119,6 +127,10 @@ pub fn op_get[A](self : T[A], index : Int) -> A { } } +//----------------------------------------------------------------------------- +// Modifier +//----------------------------------------------------------------------------- + ///| /// Set a value at the given index (immutable). /// @@ -149,15 +161,39 @@ pub fn push[A](self : T[A], value : A) -> T[A] { } ///| -/// Create a persistent array from an array. -/// -/// # Example -/// ``` -/// let v = @array.of([1, 2, 3]) -/// assert_eq!(v, @array.from_array([1, 2, 3])) -/// ``` -pub fn T::from_array[A](arr : Array[A]) -> T[A] { - makei(arr.length(), fn(i) { arr[i] }) +/// Given two trees, concatenate them into a new tree. +pub fn T::concat[A](self : T[A], other : T[A]) -> T[A] { + if self.is_empty() { + return other + } + if other.is_empty() { + return self + } + let (tree, shift) = concat( + self.tree, + self.shift, + other.tree, + other.shift, + true, + ) + { tree, size: self.size + other.size, shift } +} +//----------------------------------------------------------------------------- +// Iterators +//----------------------------------------------------------------------------- + +///| +pub fn iter[A](self : T[A]) -> Iter[A] { + Iter::new(fn(yield_) { + let arr = self.to_array() + for i = 0; i < self.size; i = i + 1 { + if yield_(arr[i]) == IterEnd { + break IterEnd + } + } else { + IterContinue + } + }) } ///| @@ -188,11 +224,6 @@ pub fn eachi[A](self : T[A], f : (Int, A) -> Unit) -> Unit { self.tree.eachi(f, self.shift, 0) } -///| -pub impl[A : Eq] Eq for T[A] with op_equal(self, other) { - self.size == other.size && self.tree == other.tree -} - ///| /// Fold the array. /// @@ -257,6 +288,39 @@ pub fn map[A, B](self : T[A], f : (A) -> B) -> T[B] { { tree: self.tree.map(f), size: self.size, shift: self.shift } } +//----------------------------------------------------------------------------- +// Common Traits Implementation +//----------------------------------------------------------------------------- + +///| +pub impl[X : @quickcheck.Arbitrary] @quickcheck.Arbitrary for T[X] with arbitrary( + size, + rs +) { + @quickcheck.Arbitrary::arbitrary(size, rs) |> from_array +} + +///| +pub impl[A : Hash] Hash for T[A] with hash_combine(self, hasher) { + for e in self { + hasher.combine(e) + } +} + +///| +pub impl[A : Eq] Eq for T[A] with op_equal(self, other) { + self.size == other.size && self.tree == other.tree +} + +///| +pub impl[A : Show] Show for T[A] with output(self, logger) { + logger.write_iter(self.iter(), prefix="@immut/array.of([", suffix="])") +} + +//----------------------------------------------------------------------------- +// For Internal Use +//----------------------------------------------------------------------------- + ///| fn new_by_leaves[A](len : Int, gen_leaf : (Int, Int) -> FixedArray[A]) -> T[A] { fn tree(cap, len, s) -> Tree[A] { @@ -296,33 +360,7 @@ fn new_by_leaves[A](len : Int, gen_leaf : (Int, Int) -> FixedArray[A]) -> T[A] { } ///| -/// Create a persistent array with a given length and value. -pub fn T::make[A](len : Int, value : A) -> T[A] { - new_by_leaves(len, fn(_s, l) { FixedArray::make(l, value) }) -} - -///| -/// Create a persistent array with a given length and a function to generate values. -pub fn T::makei[A](len : Int, f : (Int) -> A) -> T[A] { - new_by_leaves(len, fn(s, l) { FixedArray::makei(l, fn(i) { f(s + i) }) }) -} - -///| -pub fn T::of[A](arr : FixedArray[A]) -> T[A] { - makei(arr.length(), fn(i) { arr[i] }) -} - -///| -pub impl[X : @quickcheck.Arbitrary] @quickcheck.Arbitrary for T[X] with arbitrary( - size, - rs -) { - @quickcheck.Arbitrary::arbitrary(size, rs) |> from_array -} - -///| -pub impl[A : Hash] Hash for T[A] with hash_combine(self, hasher) { - for e in self { - hasher.combine(e) - } +/// Print the tree structure of the internal tree. For debug use. +fn to_debug_string[A : Show](self : T[A]) -> String { + self.tree.to_string() } diff --git a/immut/array/array_mix_wbtest.mbt b/immut/array/array_mix_wbtest.mbt new file mode 100644 index 000000000..be0a11847 --- /dev/null +++ b/immut/array/array_mix_wbtest.mbt @@ -0,0 +1,116 @@ +// Copyright 2024 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +test "DEADBEEF-2" { + let rng = @random.new(seed=b"DEADBEEFDEADBEEFDEADBEEFDEADBEEF") + let rs = random_test_gen(2, rng) + println(rs) + random_test!(rs) +} + +///| +fn random_test_gen(times : Int, rng : @random.Rand) -> Array[Int] { + // Hyperparameters + let op_count = 3 + let max_len = branching_factor * branching_factor + // * + // branching_factor * + // branching_factor + let max_val = 2025 + + // Start constructing the array + let ret = [] + let mut cur_len = 0 + for i = 0; i < times; i = i + 1 { + let op = rng.int(limit=op_count) + match op { + 0 => { + // push_end + ret.push(op) + let val = rng.int(limit=max_val) + ret.push(val) + cur_len += 1 + } + 1 => { + // concat + + ret.push(op) + let len = rng.int(limit=max_len) + ret.push(len) + for j = 0; j < len; j = j + 1 { + let val = rng.int(limit=max_val) + ret.push(val) + } + cur_len += len + } + 2 => { + // set + if cur_len == 0 { + continue + } + ret.push(op) + let idx = rng.int(limit=cur_len) + let val = rng.int(limit=max_val) + ret.push(idx) + ret.push(val) + } + _ => abort("Invalid op") + } + } + ret +} + +///| +fn random_test(rs : Array[Int]) -> Unit! { + fn check(a : Array[Int], t : T[Int]) -> Unit! { + inspect!(t, content="@immut/array.of(\{a})") + } + + let mut t = T::new() + let a = [] + let len = rs.length() + let mut i = 0 + while i < len { + let op = rs[i] + i += 1 + match op { + 0 => { + // push_end + a.push(rs[i]) + t = t.push(rs[i]) + i += 1 + check!(a, t) + } + 1 => { + // concat + let len = rs[i] + i += 1 + rs.blit_to(a, len~, src_offset=i, dst_offset=a.length()) + t = t.concat(T::from_iter(rs[i:i + len].iter())) + i += len + check!(a, t) + } + 2 => { + // set + let idx = rs[i] + let v = rs[i + 1] + i += 2 + rs[idx] = v + t = t.set(idx, v) + check!(a, t) + } + _ => abort("Invalid op") + } + } +} diff --git a/immut/array/array_wbtest.mbt b/immut/array/array_wbtest.mbt index c7ddffd43..0528b2ccd 100644 --- a/immut/array/array_wbtest.mbt +++ b/immut/array/array_wbtest.mbt @@ -150,22 +150,17 @@ test "concat-multiple-random-tree" { branching_factor * branching_factor let max_val = 2024 + let rng = @random.new(seed=b"DEADBEEFDEADBEEFDEADBEEFDEADBEEF") seq_concat_test!( 42, fn(_i) { - let len = rand(bq4) - FixedArray::makei(len, fn(_i) { rand(max_val) }) + let len = rng.int(limit=bq4) + FixedArray::makei(len, fn(_i) { rng.int(limit=max_val) }) }, FixedArray::from_array([]), ) } -///| -fn rand(upper : Int) -> Int { - let rng = @random.new() - rng.int() % upper -} - ///| fn seq_concat_test[A : Show]( rep : Int, @@ -180,6 +175,6 @@ fn seq_concat_test[A : Show]( inspect!(v1, content="@immut/array.of(\{v1_content})") t = t.concat(v1) t_content = t_content + v1_content - inspect!(init, content="@immut/array.of(\{t_content})") + inspect!(t, content="@immut/array.of(\{t_content})") } } diff --git a/immut/array/tree.mbt b/immut/array/tree.mbt index 7ec081de5..8d79f2a1f 100644 --- a/immut/array/tree.mbt +++ b/immut/array/tree.mbt @@ -16,12 +16,12 @@ //----------------------------------------------------------------------------- // Hyperparameters -//------------------------------------------------------------------------- +//----------------------------------------------------------------------------- ///| /// The controlling factor of the tree depth. /// This is the only parameter you normally would adjust. -let num_bits = 2 +let num_bits = 5 ///| /// Invariant: `branching_factor` is a power of 2. From bf7951e596a1e7e5af407a30817e6d7231d69457 Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Mon, 6 Jan 2025 16:01:30 +0800 Subject: [PATCH 14/31] fix --- immut/array/array.mbt | 2 +- immut/array/array_mix_wbtest.mbt | 98 +++++++++++++++++---- immut/array/array_test.mbt | 6 +- immut/array/array_wbtest.mbt | 142 +++++++++++-------------------- immut/array/moon.pkg.json | 15 +++- immut/array/tree.mbt | 109 +++++++++++++++++++----- 6 files changed, 236 insertions(+), 136 deletions(-) diff --git a/immut/array/array.mbt b/immut/array/array.mbt index 3e3ee392e..98af6c529 100644 --- a/immut/array/array.mbt +++ b/immut/array/array.mbt @@ -350,7 +350,7 @@ fn new_by_leaves[A](len : Int, gen_leaf : (Int, Int) -> FixedArray[A]) -> T[A] { } else { let (cap, shift) = loop len, branching_factor, 1 { len, m_pow, depth => - match len < m_pow { + match len <= m_pow { false => continue len, m_pow * branching_factor, depth + 1 true => (m_pow, (depth - 1) * num_bits) } diff --git a/immut/array/array_mix_wbtest.mbt b/immut/array/array_mix_wbtest.mbt index be0a11847..d5c963abb 100644 --- a/immut/array/array_mix_wbtest.mbt +++ b/immut/array/array_mix_wbtest.mbt @@ -12,21 +12,47 @@ // See the License for the specific language governing permissions and // limitations under the License. -test "DEADBEEF-2" { - let rng = @random.new(seed=b"DEADBEEFDEADBEEFDEADBEEFDEADBEEF") - let rs = random_test_gen(2, rng) - println(rs) - random_test!(rs) +/// The naming of test follows the format +/// random seed - number of operations - maximum level of the tree +/// If the given random seed is shorter than 32, it will be repeated +/// up to 32 chars. + +test "DEADBEEF-10-4" { + run_test!("DEADBEEF", 10, 4) +} + +test "LIVEBEEF-50-3" { + run_test!("LIVEBEEF", 50, 3) +} + +test "HAPPYDOG-1000-2" { + run_test!("HAPPYDOG", 200, 2) } ///| -fn random_test_gen(times : Int, rng : @random.Rand) -> Array[Int] { +fn run_test(seed : String, rep : Int, max_lvl : Int) -> Unit! { + let seed = repeat_up_to_32(seed) + let rng = @random.new(seed~) + let rs = random_test_gen(rng, rep, max_lvl) + execute_array_test!(rs) +} + +///| +/// Generate a random test sequence for the array, +/// following the format described in the `execute_array_test` function. +/// +/// Inputs: +/// - `rng`: a random number generator +/// - `times`: the number of operations to be generated +/// - `max_lvl`: the maximum level of the tree +fn random_test_gen( + rng : @random.Rand, + times : Int, + max_lvl : Int +) -> Array[Int] { // Hyperparameters let op_count = 3 - let max_len = branching_factor * branching_factor - // * - // branching_factor * - // branching_factor + let max_len = branching_factor_power(max_lvl) let max_val = 2025 // Start constructing the array @@ -44,7 +70,6 @@ fn random_test_gen(times : Int, rng : @random.Rand) -> Array[Int] { } 1 => { // concat - ret.push(op) let len = rng.int(limit=max_len) ret.push(len) @@ -72,7 +97,20 @@ fn random_test_gen(times : Int, rng : @random.Rand) -> Array[Int] { } ///| -fn random_test(rs : Array[Int]) -> Unit! { +/// This function runs a series of operations on an array and checks +/// if the result matches the expected array. +/// +/// Currently, the operations are: +/// 0. push_end +/// 1. concat +/// 2. set +/// +/// The `rs` array is a sequence of operations to be executed. +/// For each operation, the following value sequence are expected: +/// - push_end: 0 value +/// - concat: 1 length v0 v1 ... v_{length - 1} +/// - set: 2 index value +fn execute_array_test(rs : Array[Int]) -> Unit! { fn check(a : Array[Int], t : T[Int]) -> Unit! { inspect!(t, content="@immut/array.of(\{a})") } @@ -87,8 +125,9 @@ fn random_test(rs : Array[Int]) -> Unit! { match op { 0 => { // push_end - a.push(rs[i]) - t = t.push(rs[i]) + let rsi = rs[i] + a.push(rsi) + t = t.push(rsi) i += 1 check!(a, t) } @@ -97,7 +136,8 @@ fn random_test(rs : Array[Int]) -> Unit! { let len = rs[i] i += 1 rs.blit_to(a, len~, src_offset=i, dst_offset=a.length()) - t = t.concat(T::from_iter(rs[i:i + len].iter())) + let t1 = T::from_iter(rs[i:i + len].iter()) + t = t.concat(t1) i += len check!(a, t) } @@ -106,7 +146,7 @@ fn random_test(rs : Array[Int]) -> Unit! { let idx = rs[i] let v = rs[i + 1] i += 2 - rs[idx] = v + a[idx] = v t = t.set(idx, v) check!(a, t) } @@ -114,3 +154,29 @@ fn random_test(rs : Array[Int]) -> Unit! { } } } + +///| +/// Compute the power of the branching factor. +fn branching_factor_power(a : Int) -> Int { + let mut ret = 1 + for i = 0; i < a; i = i + 1 { + ret *= branching_factor + } + ret +} + +///| +/// Repeat the given string up to 32 characters. +/// Used to generate a random seed for the test. +fn repeat_up_to_32(s : String) -> Bytes { + let a_len = 32 + let s_len = s.length() + let a = FixedArray::make(a_len, b'0') + let mut j = 0 + for i = 0; i < a_len; i = i + 1 { + let l = a.set_utf8_char(i, s[j]) + guard l == 1 + j = (j + 1) % s_len + } + Bytes::from_fixedarray(a) +} diff --git a/immut/array/array_test.mbt b/immut/array/array_test.mbt index a0feedb58..e6d4212c3 100644 --- a/immut/array/array_test.mbt +++ b/immut/array/array_test.mbt @@ -1,11 +1,11 @@ // Copyright 2024 International Digital Economy Academy -// + // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// + // http://www.apache.org/licenses/LICENSE-2.0 -// + // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff --git a/immut/array/array_wbtest.mbt b/immut/array/array_wbtest.mbt index 0528b2ccd..1563bd7aa 100644 --- a/immut/array/array_wbtest.mbt +++ b/immut/array/array_wbtest.mbt @@ -67,114 +67,76 @@ test "mix" { } test "concat-two-full-tree" { - let bf2 = branching_factor * branching_factor - let v_content = [] - for i = 0; i < bf2; i = i + 1 { - v_content.push(i) - } - let v_content = FixedArray::from_array(v_content) - let v = T::of(v_content) - inspect!(v, content="@immut/array.of(\{v_content})") - let v2_content = [] - for i = 0; i < bf2; i = i + 1 { - v2_content.push(i) - } - let v2_content = FixedArray::from_array(v2_content) - let v2 = T::of(v2_content) - inspect!(v2, content="@immut/array.of(\{v2_content})") - let c = v.concat(v2) - let c_content = v_content + v2_content - inspect!(c, content="@immut/array.of(\{c_content})") + let bf = branching_factor_power(4) + execute_array_test!(gen_concat_seq_from_len_array([bf, bf])) } test "concat-full-tree-and-a-leaf" { - let bf2 = branching_factor * branching_factor - let v_content = [] - for i = 0; i < bf2; i = i + 1 { - v_content.push(i) - } - let v_content = FixedArray::from_array(v_content) - let v = T::of(v_content) - inspect!(v, content="@immut/array.of(\{v_content})") - let v2_content = FixedArray::from_array([0]) - let v2 = T::of(v2_content) - inspect!(v2, content="@immut/array.of(\{v2_content})") - let c = v.concat(v2) - let c_content = v_content + v2_content - inspect!(c, content="@immut/array.of(\{c_content})") + let bf = branching_factor_power(4) + execute_array_test!(gen_concat_seq_from_len_array([bf, 1])) } test "concat-a-leaf-and-full-tree" { - let v_content = FixedArray::from_array([0]) - let v = T::of(v_content) - inspect!(v, content="@immut/array.of(\{v_content})") - let bf2 = branching_factor * branching_factor - let v2_content = [] - for i = 0; i < bf2; i = i + 1 { - v2_content.push(i) - } - let v2_content = FixedArray::from_array(v2_content) - let v2 = T::of(v2_content) - inspect!(v2, content="@immut/array.of(\{v2_content})") - let c = v.concat(v2) - let c_content = v_content + v2_content - inspect!(c, content="@immut/array.of(\{c_content})") + let bf = branching_factor_power(4) + execute_array_test!(gen_concat_seq_from_len_array([1, bf])) } test "concat-two-leaf" { - let v_content = FixedArray::makei(branching_factor, fn(i) { i }) - let v = T::of(v_content) - inspect!(v, content="@immut/array.of(\{v_content})") - let v2_content = FixedArray::makei(branching_factor, fn(i) { - i + branching_factor - }) - let v2 = T::of(v2_content) - inspect!(v2, content="@immut/array.of(\{v2_content})") - let c = v.concat(v2) - let c_content = v_content + v2_content - inspect!(c, content="@immut/array.of(\{c_content})") + execute_array_test!(gen_concat_seq_from_len_array([1, 1])) } test "concat-multiple-full-tree" { - let bf2 = branching_factor * branching_factor - seq_concat_test!( - 42, - fn(_i) { FixedArray::makei(bf2, fn(i) { i }) }, - FixedArray::from_array([]), - ) + let bf4 = branching_factor_power(4) + execute_array_test!(gen_concat_seq(3, fn(_i) { bf4 })) } test "concat-multiple-random-tree" { - let bq4 = branching_factor * - branching_factor * - branching_factor * - branching_factor - let max_val = 2024 + let bq4 = branching_factor_power(3) let rng = @random.new(seed=b"DEADBEEFDEADBEEFDEADBEEFDEADBEEF") - seq_concat_test!( - 42, - fn(_i) { - let len = rng.int(limit=bq4) - FixedArray::makei(len, fn(_i) { rng.int(limit=max_val) }) - }, - FixedArray::from_array([]), - ) + execute_array_test!(gen_concat_seq(40, fn(_i) { rng.int(limit=bq4) })) } ///| -fn seq_concat_test[A : Show]( - rep : Int, - gen : (Int) -> FixedArray[A], - init : FixedArray[A] -) -> Unit! { - let mut t = T::of(init) - let mut t_content = init - for i = 0; i < rep; i = i + 1 { - let v1_content = gen(i) - let v1 = T::of(v1_content) - inspect!(v1, content="@immut/array.of(\{v1_content})") - t = t.concat(v1) - t_content = t_content + v1_content - inspect!(t, content="@immut/array.of(\{t_content})") +/// Generate a sequence of concatenation operations as a test case, +/// following the format described in the `execute_array_test` function. +/// +/// Inputs: +/// - `n`: the number of operations to be generated +/// - `len_gen`: a function that generates the length of the array to be concatenated +fn gen_concat_seq(n : Int, len_gen : (Int) -> Int) -> Array[Int] { + let ret = [] + for i = 0; i < n; i = i + 1 { + ret.push(1) + let len = len_gen(i) + ret.push(len) + let arr = random_array(len) + for j = 0; j < len; j = j + 1 { + ret.push(arr[j]) + } } + ret +} + +///| +/// Generate a sequence of concatenation operations as a test case, +/// similar to `gen_concat_seq`, but this time, the length of the array +/// to be concatenated is given as an array. +fn gen_concat_seq_from_len_array(len : Array[Int]) -> Array[Int] { + let ret = [] + for i = 0; i < len.length(); i = i + 1 { + ret.push(1) + let arr = random_array(len[i]) + ret.push(len[i]) + for j = 0; j < len[i]; j = j + 1 { + ret.push(arr[j]) + } + } + ret +} + +///| +/// Generate a random array of length `n`. +fn random_array(n : Int) -> FixedArray[Int] { + let rng = @random.new(seed=b"DEADBEEFLIVEBEEFDEADBEEFDEADBEEF") + FixedArray::makei(n, fn(i) { rng.int(limit=i) }) } diff --git a/immut/array/moon.pkg.json b/immut/array/moon.pkg.json index 1b4b0bd84..38a2553a6 100644 --- a/immut/array/moon.pkg.json +++ b/immut/array/moon.pkg.json @@ -7,9 +7,18 @@ "alias": "_core/array" } ], - "wbtest-import": ["moonbitlang/core/random"], + "wbtest-import": [ + "moonbitlang/core/random", + "moonbitlang/core/bytes" + ], "targets": { - "panic_test.mbt": ["not", "native"], - "panic_wbtest.mbt": ["not", "native"] + "panic_test.mbt": [ + "not", + "native" + ], + "panic_wbtest.mbt": [ + "not", + "native" + ] } } diff --git a/immut/array/tree.mbt b/immut/array/tree.mbt index 8d79f2a1f..48b6e9666 100644 --- a/immut/array/tree.mbt +++ b/immut/array/tree.mbt @@ -117,7 +117,11 @@ fn get[T](self : Tree[T], index : Int, shift : Int) -> T { Leaf(leaf) => leaf[index] Node(children, Some(sizes)) => { let branch_index = get_branch_index(sizes, index) - let sub_index = index - sizes[branch_index] + let sub_index = if branch_index == 0 { + 0 + } else { + index - sizes[branch_index - 1] + } get(children[branch_index], sub_index, shift - num_bits) } Node(_, None) => get_radix(self, shift) @@ -135,6 +139,7 @@ fn get[T](self : Tree[T], index : Int, shift : Int) -> T { /// Precondition: /// - `self` is of height `shift / num_bits`. fn set[T](self : Tree[T], index : Int, shift : Int, value : T) -> Tree[T] { + // TODO: optimize this as loop fn set_radix(node : Tree[T], shift : Int) -> Tree[T] { match node { Leaf(leaf) => Leaf(immutable_set(leaf, index & bitmask, value)) @@ -159,14 +164,20 @@ fn set[T](self : Tree[T], index : Int, shift : Int, value : T) -> Tree[T] { Leaf(leaf) => Leaf(immutable_set(leaf, index & bitmask, value)) Node(children, Some(sizes)) => { let branch_index = get_branch_index(sizes, index) - let sub_index = index - sizes[branch_index] + let sub_index = if branch_index == 0 { + index + } else { + index - sizes[branch_index - 1] + } + let sizes = sizes.copy() + check_sizes(sizes, shift - num_bits) Node( immutable_set( children, branch_index, children[branch_index].set(sub_index, shift - num_bits, value), ), - Some(sizes.copy()), + Some(sizes), ) } Node(_children, None) => set_radix(self, shift) @@ -194,7 +205,7 @@ fn push_end[T](self : Tree[T], shift : Int, value : T) -> (Tree[T], Int) { fn push_sizes_last(sizes : FixedArray[Int]?) -> FixedArray[Int]? { match sizes { - Some(sizes) => Some(immutable_push(sizes, 1)) + Some(sizes) => Some(immutable_push(sizes, 1 + sizes[sizes.length() - 1])) None => None } } @@ -220,18 +231,28 @@ fn push_end[T](self : Tree[T], shift : Int, value : T) -> (Tree[T], Int) { Some(new_node) => { let new_nodes = nodes.copy() new_nodes[len - 1] = new_node - Some(Node(new_nodes, update_sizes_last(sizes))) + let sizes = update_sizes_last(sizes) + match sizes { + Some(sizes) => check_sizes(sizes, shift - num_bits) + None => () + } + Some(Node(new_nodes, sizes)) } // We need to create a new node to push the value. None => if len < branching_factor { + let sizes = push_sizes_last(sizes) + match sizes { + Some(sizes) => check_sizes(sizes, shift - num_bits) + None => () + } Some( Node( immutable_push( nodes, new_branch_left([value], shift - num_bits), ), - push_sizes_last(sizes), + sizes, ), ) } else { @@ -250,8 +271,12 @@ fn push_end[T](self : Tree[T], shift : Int, value : T) -> (Tree[T], Int) { ( match self { Leaf(_leaf) => Node([self, new_branch], None) - Node(_nodes, Some(sizes)) => - Node([self, new_branch], Some([sizes[sizes.length() - 1], 1])) + Node(_nodes, Some(sizes)) => { + let len = sizes[sizes.length() - 1] + let sizes = FixedArray::from_array([len, 1 + len]) + check_sizes(sizes, shift) + Node([self, new_branch], Some(sizes)) + } Node(_nodes, None) => Node([self, new_branch], None) Empty => abort( @@ -559,6 +584,12 @@ fn redis_plan[A](t : FixedArray[Tree[A]]) -> (FixedArray[Int], Int) { return (node_counts, new_len) } +// test "redis_plan" { +// let bf = branching_factor +// let t = FixedArray::from_array([bf, bf, 7]) +// let res = redis_plan(t) +// } + ///| /// This function redistributes the nodes in `old_t` according to the plan in `node_counts`. /// @@ -582,28 +613,35 @@ fn redis[A]( let mut j = 0 // the index of in the old tree if shift == 0 { // Handle Leaf case + + let mut old_leaf_elems = FixedArray::default() + let mut old_leaf_len = 0 for i = 0; i < node_nums; i = i + 1 { - // t[j] is the next to be redistributed + + // old_t[j] is the next to be redistributed // old_offset is the index of the next node to be redistributed in old_t[j] // old_offset == 0 means all nodes in old_t[j] are to be redistributed // new_t[i] is the current node to be filled with redistributed nodes - let old_leaf_elems = old_t[i].leaf_elements() - let old_leaf_len = old_leaf_elems.length() + old_leaf_elems = old_t[j].leaf_elements() + old_leaf_len = old_leaf_elems.length() if old_offset == 0 && old_leaf_len == node_counts[i] { // Perfect, we just point to the old leaf - new_t[i] = old_t[i] + new_t[i] = old_t[j] + j += 1 } else { let mut new_offset = 0 // the accumulated number of elements in the new leaf let new_leaf_len = node_counts[i] - let new_elems = FixedArray::make(new_leaf_len, old_leaf_elems[0]) - while new_offset < node_counts[i] { + let new_leaf_elems = FixedArray::make(new_leaf_len, old_leaf_elems[0]) + while new_offset < new_leaf_len { + old_leaf_elems = old_t[j].leaf_elements() + old_leaf_len = old_leaf_elems.length() guard j < old_len // This shouldn't be triggered if the plan was correctly generated let remaining = min( - node_counts[i] - new_offset, + new_leaf_len - new_offset, old_leaf_len - old_offset, ) FixedArray::unsafe_blit( - new_elems, new_offset, old_leaf_elems, old_offset, remaining, + new_leaf_elems, new_offset, old_leaf_elems, old_offset, remaining, ) new_offset += remaining old_offset += remaining @@ -612,24 +650,30 @@ fn redis[A]( old_offset = 0 } } - new_t[i] = Leaf(new_elems) + new_t[i] = Leaf(new_leaf_elems) } } } else { // Handle Node case, pretty much the same as the Leaf case + + let mut old_node_chldrn = FixedArray::default() + let mut old_node_len = 0 for i = 0; i < node_nums; i = i + 1 { - let old_node_chldrn = old_t[i].node_children() - let old_node_len = old_node_chldrn.length() + old_node_chldrn = old_t[j].node_children() + old_node_len = old_node_chldrn.length() if old_offset == 0 && old_node_len == node_counts[i] { - new_t[i] = old_t[i] + new_t[i] = old_t[j] + j += 1 } else { let mut new_offset = 0 let new_node_len = node_counts[i] let new_node_chldrn = FixedArray::make(new_node_len, old_node_chldrn[0]) - while new_offset < node_counts[i] { + while new_offset < new_node_len { + old_node_chldrn = old_t[j].node_children() + old_node_len = old_node_chldrn.length() guard j < old_len let remaining = min( - node_counts[i] - new_offset, + new_node_len - new_offset, old_node_len - old_offset, ) FixedArray::unsafe_blit( @@ -642,7 +686,10 @@ fn redis[A]( old_offset = 0 } } - new_t[i] = Node(new_node_chldrn, compute_sizes(new_node_chldrn, shift)) // each node in `new_t` is of height (`shift` / `num_bits`) + new_t[i] = Node( + new_node_chldrn, + compute_sizes(new_node_chldrn, shift - num_bits), + ) // each node in `new_t` is of height (`shift` / `num_bits`) } } } @@ -663,16 +710,32 @@ fn compute_sizes[A]( for i = 0; i < len; i = i + 1 { let sz = children[i].size(shift) flag = flag && sz == full_subtree_size + if sz > full_subtree_size { + let _ = children[i].size(shift) + abort("TOOO BOGO") + } sum += sz sizes[i] = sum } if flag { None } else { + check_sizes(sizes, shift) Some(sizes) } } +///| +/// `shift` is the height of the subtree, whose size is sizes[i]-sizes[i-1] +fn check_sizes(sizes : FixedArray[Int], shift : Int) -> Unit { + for i = 1; i < sizes.length(); i = i + 1 { + if sizes[i] <= sizes[i - 1] || + sizes[i] - sizes[i - 1] > (1 << (shift + num_bits)) { + abort("Fuck") + } + } +} + //----------------------------------------------------------------------------- // Common Trait Implementations //----------------------------------------------------------------------------- From 5b9f5c6a88e533a0bea794848e14787f24e7c377 Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Mon, 6 Jan 2025 16:04:54 +0800 Subject: [PATCH 15/31] fix header --- immut/array/array_test.mbt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/immut/array/array_test.mbt b/immut/array/array_test.mbt index e6d4212c3..a0feedb58 100644 --- a/immut/array/array_test.mbt +++ b/immut/array/array_test.mbt @@ -1,11 +1,11 @@ // Copyright 2024 International Digital Economy Academy - +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at - +// // http://www.apache.org/licenses/LICENSE-2.0 - +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. From ff02ea083a5ca2a68fc798d84ad4ea36691ec0fd Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Mon, 6 Jan 2025 16:10:48 +0800 Subject: [PATCH 16/31] add README --- immut/array/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/immut/array/README.md b/immut/array/README.md index 5382925d0..75990e948 100644 --- a/immut/array/README.md +++ b/immut/array/README.md @@ -57,4 +57,9 @@ println(arr.each(fn(v) { println("element \{v}") })) println(arr.eachi(fn(i, v) { println("index: \{i}, element: \{v}") })) ``` +# TODO + +- [] Add `split` and other operations that can be derived from `split` and `concat` like `insert` and `delete`. +- [] Add an algorithm description in README, since this algorithm does not use the invariant in the ICFP paper. Instead, it uses the "search step invariant" in Hypirion's thesis. +- [] Add a benchmark to compare the performance with the previous version. From ad74d329844c7f17d4d9cdb5c6c1f9fbf574a053 Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Mon, 6 Jan 2025 17:10:00 +0800 Subject: [PATCH 17/31] don't use inspect for array too long --- immut/array/README.md | 13 ++++++++++++- immut/array/array.mbt | 3 ++- immut/array/array_mix_wbtest.mbt | 23 ++++++++++++++++------- immut/array/array_wbtest.mbt | 8 ++++---- 4 files changed, 34 insertions(+), 13 deletions(-) diff --git a/immut/array/README.md b/immut/array/README.md index 75990e948..9a59ecb38 100644 --- a/immut/array/README.md +++ b/immut/array/README.md @@ -35,6 +35,17 @@ println(arr1) // of([1, 2, 3, 4, 5]) println(arr2) // of([1, 2, 10, 4, 5, 6]) ``` +## Concatenation + +You can use `concat()` to concatenate two arrays. + +```moonbit +let arr1 = @immut/array.of([1, 2, 3]) +let arr2 = @immut/array.of([4, 5, 6]) +let arr3 = arr1.concat(arr2) +println(arr3) // of([1, 2, 3, 4, 5, 6]) +``` + ## Query You can use `op_get()` to get the value at the index, or `length()` to get the length of the array, or `is_empty()` to check whether the array is empty. @@ -62,4 +73,4 @@ println(arr.eachi(fn(i, v) { println("index: \{i}, element: \{v}") })) - [] Add `split` and other operations that can be derived from `split` and `concat` like `insert` and `delete`. - [] Add an algorithm description in README, since this algorithm does not use the invariant in the ICFP paper. Instead, it uses the "search step invariant" in Hypirion's thesis. - [] Add a benchmark to compare the performance with the previous version. - +- [] Optimizations such as tail. diff --git a/immut/array/array.mbt b/immut/array/array.mbt index 98af6c529..e82f31015 100644 --- a/immut/array/array.mbt +++ b/immut/array/array.mbt @@ -183,9 +183,10 @@ pub fn T::concat[A](self : T[A], other : T[A]) -> T[A] { //----------------------------------------------------------------------------- ///| +/// Return an iterator over the array. pub fn iter[A](self : T[A]) -> Iter[A] { Iter::new(fn(yield_) { - let arr = self.to_array() + let arr = self.to_array() // TODO: it first converts to an array, which is not efficient for i = 0; i < self.size; i = i + 1 { if yield_(arr[i]) == IterEnd { break IterEnd diff --git a/immut/array/array_mix_wbtest.mbt b/immut/array/array_mix_wbtest.mbt index d5c963abb..a6ac1192c 100644 --- a/immut/array/array_mix_wbtest.mbt +++ b/immut/array/array_mix_wbtest.mbt @@ -111,10 +111,6 @@ fn random_test_gen( /// - concat: 1 length v0 v1 ... v_{length - 1} /// - set: 2 index value fn execute_array_test(rs : Array[Int]) -> Unit! { - fn check(a : Array[Int], t : T[Int]) -> Unit! { - inspect!(t, content="@immut/array.of(\{a})") - } - let mut t = T::new() let a = [] let len = rs.length() @@ -129,7 +125,7 @@ fn execute_array_test(rs : Array[Int]) -> Unit! { a.push(rsi) t = t.push(rsi) i += 1 - check!(a, t) + check_array_eq!(a, t) } 1 => { // concat @@ -139,7 +135,7 @@ fn execute_array_test(rs : Array[Int]) -> Unit! { let t1 = T::from_iter(rs[i:i + len].iter()) t = t.concat(t1) i += len - check!(a, t) + check_array_eq!(a, t) } 2 => { // set @@ -148,7 +144,7 @@ fn execute_array_test(rs : Array[Int]) -> Unit! { i += 2 a[idx] = v t = t.set(idx, v) - check!(a, t) + check_array_eq!(a, t) } _ => abort("Invalid op") } @@ -180,3 +176,16 @@ fn repeat_up_to_32(s : String) -> Bytes { } Bytes::from_fixedarray(a) } + +///| +/// Use this function to check if the array and the @immut/array are equal. +/// If we `inspect` the array, it will raise an error if the arrays are too long. +/// I guess that's because it exceeds the heap limit of the VM. +fn check_array_eq(a : Array[Int], t : T[Int]) -> Unit! { + assert_eq!(a.length(), t.size) + let mut i = 0 + for tt in t.iter() { + assert_eq!(tt, a[i]) + i += 1 + } +} diff --git a/immut/array/array_wbtest.mbt b/immut/array/array_wbtest.mbt index 1563bd7aa..4eb5852dc 100644 --- a/immut/array/array_wbtest.mbt +++ b/immut/array/array_wbtest.mbt @@ -86,14 +86,14 @@ test "concat-two-leaf" { } test "concat-multiple-full-tree" { - let bf4 = branching_factor_power(4) - execute_array_test!(gen_concat_seq(3, fn(_i) { bf4 })) + let bf = branching_factor_power(4) + execute_array_test!(gen_concat_seq(3, fn(_i) { bf })) } test "concat-multiple-random-tree" { - let bq4 = branching_factor_power(3) + let bf = branching_factor_power(2) let rng = @random.new(seed=b"DEADBEEFDEADBEEFDEADBEEFDEADBEEF") - execute_array_test!(gen_concat_seq(40, fn(_i) { rng.int(limit=bq4) })) + execute_array_test!(gen_concat_seq(2, fn(_i) { rng.int(limit=bf) })) } ///| From 40d031c7238025013599e179148ab155b99b67f2 Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Tue, 7 Jan 2025 12:04:03 +0800 Subject: [PATCH 18/31] coverage --- immut/array/array_mix_wbtest.mbt | 153 ------------------------- immut/array/array_test.mbt | 11 ++ immut/array/array_wbtest.mbt | 25 +++-- immut/array/tree.mbt | 3 +- immut/array/utils_wbtest.mbt | 187 +++++++++++++++++++++++++++++++ 5 files changed, 218 insertions(+), 161 deletions(-) create mode 100644 immut/array/utils_wbtest.mbt diff --git a/immut/array/array_mix_wbtest.mbt b/immut/array/array_mix_wbtest.mbt index a6ac1192c..ffe4d0f81 100644 --- a/immut/array/array_mix_wbtest.mbt +++ b/immut/array/array_mix_wbtest.mbt @@ -36,156 +36,3 @@ fn run_test(seed : String, rep : Int, max_lvl : Int) -> Unit! { let rs = random_test_gen(rng, rep, max_lvl) execute_array_test!(rs) } - -///| -/// Generate a random test sequence for the array, -/// following the format described in the `execute_array_test` function. -/// -/// Inputs: -/// - `rng`: a random number generator -/// - `times`: the number of operations to be generated -/// - `max_lvl`: the maximum level of the tree -fn random_test_gen( - rng : @random.Rand, - times : Int, - max_lvl : Int -) -> Array[Int] { - // Hyperparameters - let op_count = 3 - let max_len = branching_factor_power(max_lvl) - let max_val = 2025 - - // Start constructing the array - let ret = [] - let mut cur_len = 0 - for i = 0; i < times; i = i + 1 { - let op = rng.int(limit=op_count) - match op { - 0 => { - // push_end - ret.push(op) - let val = rng.int(limit=max_val) - ret.push(val) - cur_len += 1 - } - 1 => { - // concat - ret.push(op) - let len = rng.int(limit=max_len) - ret.push(len) - for j = 0; j < len; j = j + 1 { - let val = rng.int(limit=max_val) - ret.push(val) - } - cur_len += len - } - 2 => { - // set - if cur_len == 0 { - continue - } - ret.push(op) - let idx = rng.int(limit=cur_len) - let val = rng.int(limit=max_val) - ret.push(idx) - ret.push(val) - } - _ => abort("Invalid op") - } - } - ret -} - -///| -/// This function runs a series of operations on an array and checks -/// if the result matches the expected array. -/// -/// Currently, the operations are: -/// 0. push_end -/// 1. concat -/// 2. set -/// -/// The `rs` array is a sequence of operations to be executed. -/// For each operation, the following value sequence are expected: -/// - push_end: 0 value -/// - concat: 1 length v0 v1 ... v_{length - 1} -/// - set: 2 index value -fn execute_array_test(rs : Array[Int]) -> Unit! { - let mut t = T::new() - let a = [] - let len = rs.length() - let mut i = 0 - while i < len { - let op = rs[i] - i += 1 - match op { - 0 => { - // push_end - let rsi = rs[i] - a.push(rsi) - t = t.push(rsi) - i += 1 - check_array_eq!(a, t) - } - 1 => { - // concat - let len = rs[i] - i += 1 - rs.blit_to(a, len~, src_offset=i, dst_offset=a.length()) - let t1 = T::from_iter(rs[i:i + len].iter()) - t = t.concat(t1) - i += len - check_array_eq!(a, t) - } - 2 => { - // set - let idx = rs[i] - let v = rs[i + 1] - i += 2 - a[idx] = v - t = t.set(idx, v) - check_array_eq!(a, t) - } - _ => abort("Invalid op") - } - } -} - -///| -/// Compute the power of the branching factor. -fn branching_factor_power(a : Int) -> Int { - let mut ret = 1 - for i = 0; i < a; i = i + 1 { - ret *= branching_factor - } - ret -} - -///| -/// Repeat the given string up to 32 characters. -/// Used to generate a random seed for the test. -fn repeat_up_to_32(s : String) -> Bytes { - let a_len = 32 - let s_len = s.length() - let a = FixedArray::make(a_len, b'0') - let mut j = 0 - for i = 0; i < a_len; i = i + 1 { - let l = a.set_utf8_char(i, s[j]) - guard l == 1 - j = (j + 1) % s_len - } - Bytes::from_fixedarray(a) -} - -///| -/// Use this function to check if the array and the @immut/array are equal. -/// If we `inspect` the array, it will raise an error if the arrays are too long. -/// I guess that's because it exceeds the heap limit of the VM. -fn check_array_eq(a : Array[Int], t : T[Int]) -> Unit! { - assert_eq!(a.length(), t.size) - let mut i = 0 - for tt in t.iter() { - assert_eq!(tt, a[i]) - i += 1 - } -} diff --git a/immut/array/array_test.mbt b/immut/array/array_test.mbt index a0feedb58..0407f928a 100644 --- a/immut/array/array_test.mbt +++ b/immut/array/array_test.mbt @@ -222,3 +222,14 @@ test "hash" { inspect!(l1.hash() == l4.hash(), content="false") inspect!(l4.hash() == l4.hash(), content="true") } + +test "concat_empty" { + let a = @array.of([1, 2, 3]) + let b : @array.T[Int] = @array.new() + let c = a.concat(b) + inspect!(c, content="@immut/array.of([1, 2, 3])") + let a : @array.T[Int] = @array.new() + let b = @array.of([1, 2, 3]) + let c = a.concat(b) + inspect!(c, content="@immut/array.of([1, 2, 3])") +} diff --git a/immut/array/array_wbtest.mbt b/immut/array/array_wbtest.mbt index 4eb5852dc..4b9a7de28 100644 --- a/immut/array/array_wbtest.mbt +++ b/immut/array/array_wbtest.mbt @@ -13,6 +13,11 @@ // limitations under the License. test "new_by_leaves" { + let full : T[Int] = new_by_leaves(branching_factor_power(2), fn(_s, l) { + FixedArray::make(l, 1) + }) + let full_arr = FixedArray::make(branching_factor_power(2), 1) + check_fixedarray_eq!(full_arr, full) let e : T[Int] = new_by_leaves(0, fn(_s, _l) { abort("never reach") }) let v = new_by_leaves(5, fn(_s, l) { FixedArray::make(l, 1) }) let v2 = new_by_leaves(33, fn(_s, l) { FixedArray::make(l, 10) }) @@ -66,6 +71,19 @@ test "mix" { } } +test "copy" { + let bf = branching_factor_power(4) + let v1_content = random_array(bf / 2) + let v2_content = random_array(bf / 3) + let v_content = v1_content + v2_content + let v1 = T::from_iter(v1_content.iter()) + let v2 = T::from_iter(v2_content.iter()) + let v = v1.concat(v2) + let v_copy = v.copy() + check_fixedarray_eq!(v_content, v_copy) + check_fixedarray_eq!(v_content, v) +} + test "concat-two-full-tree" { let bf = branching_factor_power(4) execute_array_test!(gen_concat_seq_from_len_array([bf, bf])) @@ -133,10 +151,3 @@ fn gen_concat_seq_from_len_array(len : Array[Int]) -> Array[Int] { } ret } - -///| -/// Generate a random array of length `n`. -fn random_array(n : Int) -> FixedArray[Int] { - let rng = @random.new(seed=b"DEADBEEFLIVEBEEFDEADBEEFDEADBEEF") - FixedArray::makei(n, fn(i) { rng.int(limit=i) }) -} diff --git a/immut/array/tree.mbt b/immut/array/tree.mbt index 48b6e9666..ad37ddaae 100644 --- a/immut/array/tree.mbt +++ b/immut/array/tree.mbt @@ -741,7 +741,8 @@ fn check_sizes(sizes : FixedArray[Int], shift : Int) -> Unit { //----------------------------------------------------------------------------- ///| -/// Print the tree structure. +/// Print the tree structure. For debug use only. +/// @coverage.skip impl[A : Show] Show for Tree[A] with output(self, logger : &Logger) { fn indent_str(s : String, indent : Int) -> String { String::make(indent, ' ') + s diff --git a/immut/array/utils_wbtest.mbt b/immut/array/utils_wbtest.mbt new file mode 100644 index 000000000..fe11ad5d7 --- /dev/null +++ b/immut/array/utils_wbtest.mbt @@ -0,0 +1,187 @@ +// Copyright 2024 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +///| +/// Generate a random test sequence for the array, +/// following the format described in the `execute_array_test` function. +/// +/// Inputs: +/// - `rng`: a random number generator +/// - `times`: the number of operations to be generated +/// - `max_lvl`: the maximum level of the tree +fn random_test_gen( + rng : @random.Rand, + times : Int, + max_lvl : Int +) -> Array[Int] { + // Hyperparameters + let op_count = 3 + let max_len = branching_factor_power(max_lvl) + let max_val = 2025 + + // Start constructing the array + let ret = [] + let mut cur_len = 0 + for i = 0; i < times; i = i + 1 { + let op = rng.int(limit=op_count) + match op { + 0 => { + // push_end + ret.push(op) + let val = rng.int(limit=max_val) + ret.push(val) + cur_len += 1 + } + 1 => { + // concat + ret.push(op) + let len = rng.int(limit=max_len) + ret.push(len) + for j = 0; j < len; j = j + 1 { + let val = rng.int(limit=max_val) + ret.push(val) + } + cur_len += len + } + 2 => { + // set + if cur_len == 0 { + continue + } + ret.push(op) + let idx = rng.int(limit=cur_len) + let val = rng.int(limit=max_val) + ret.push(idx) + ret.push(val) + } + _ => abort("Invalid op") + } + } + ret +} + +///| +/// This function runs a series of operations on an array and checks +/// if the result matches the expected array. +/// +/// Currently, the operations are: +/// 0. push_end +/// 1. concat +/// 2. set +/// +/// The `rs` array is a sequence of operations to be executed. +/// For each operation, the following value sequence are expected: +/// - push_end: 0 value +/// - concat: 1 length v0 v1 ... v_{length - 1} +/// - set: 2 index value +fn execute_array_test(rs : Array[Int]) -> Unit! { + let mut t = T::new() + let a = [] + let len = rs.length() + let mut i = 0 + while i < len { + let op = rs[i] + i += 1 + match op { + 0 => { + // push_end + let rsi = rs[i] + a.push(rsi) + t = t.push(rsi) + i += 1 + check_array_eq!(a, t) + } + 1 => { + // concat + let len = rs[i] + i += 1 + rs.blit_to(a, len~, src_offset=i, dst_offset=a.length()) + let t1 = T::from_iter(rs[i:i + len].iter()) + t = t.concat(t1) + i += len + check_array_eq!(a, t) + } + 2 => { + // set + let idx = rs[i] + let v = rs[i + 1] + i += 2 + a[idx] = v + t = t.set(idx, v) + check_array_eq!(a, t) + } + _ => abort("Invalid op") + } + } +} + +///| +/// Compute the power of the branching factor. +fn branching_factor_power(a : Int) -> Int { + let mut ret = 1 + for i = 0; i < a; i = i + 1 { + ret *= branching_factor + } + ret +} + +///| +/// Repeat the given string up to 32 characters. +/// Used to generate a random seed for the test. +fn repeat_up_to_32(s : String) -> Bytes { + let a_len = 32 + let s_len = s.length() + let a = FixedArray::make(a_len, b'0') + let mut j = 0 + for i = 0; i < a_len; i = i + 1 { + let l = a.set_utf8_char(i, s[j]) + guard l == 1 + j = (j + 1) % s_len + } + Bytes::from_fixedarray(a) +} + +///| +/// Use this function to check if the array and the @immut/array are equal. +/// If we `inspect` the array, it will raise an error if the arrays are too long. +/// I guess that's because it exceeds the heap limit of the VM. +fn check_array_eq(a : Array[Int], t : T[Int]) -> Unit! { + assert_eq!(a.length(), t.size) + let mut i = 0 + for tt in t.iter() { + assert_eq!(tt, a[i]) + i += 1 + } +} + +///| +/// Use this function to check if the FixedArray and the @immut/array are equal. +/// If we `inspect` the array, it will raise an error if the arrays are too long. +/// I guess that's because it exceeds the heap limit of the VM. +fn check_fixedarray_eq(a : FixedArray[Int], t : T[Int]) -> Unit! { + assert_eq!(a.length(), t.size) + let mut i = 0 + for tt in t.iter() { + assert_eq!(tt, a[i]) + i += 1 + } +} + + +///| +/// Generate a random array of length `n`. +fn random_array(n : Int) -> FixedArray[Int] { + let rng = @random.new(seed=b"DEADBEEFLIVEBEEFDEADBEEFDEADBEEF") + FixedArray::makei(n, fn(i) { rng.int(limit=i) }) +} From 9b1867199ad045b341c549888ea664ce3edda9d6 Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Tue, 7 Jan 2025 16:33:41 +0800 Subject: [PATCH 19/31] coverage --- immut/array/array_mix_wbtest.mbt | 4 ++++ immut/array/array_wbtest.mbt | 17 +++++++++++++++++ immut/array/tree.mbt | 14 +++++++++----- immut/array/utils_wbtest.mbt | 17 +++++++---------- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/immut/array/array_mix_wbtest.mbt b/immut/array/array_mix_wbtest.mbt index ffe4d0f81..bcc2e7b6a 100644 --- a/immut/array/array_mix_wbtest.mbt +++ b/immut/array/array_mix_wbtest.mbt @@ -29,6 +29,10 @@ test "HAPPYDOG-1000-2" { run_test!("HAPPYDOG", 200, 2) } +test "HELLOWOLRD-5-3" { + run_test!("HELLOWOLRD", 4, 1) +} + ///| fn run_test(seed : String, rep : Int, max_lvl : Int) -> Unit! { let seed = repeat_up_to_32(seed) diff --git a/immut/array/array_wbtest.mbt b/immut/array/array_wbtest.mbt index 4b9a7de28..155e85a51 100644 --- a/immut/array/array_wbtest.mbt +++ b/immut/array/array_wbtest.mbt @@ -71,6 +71,23 @@ test "mix" { } } +test "op_get" { + let bf = branching_factor_power(4) + let v_content = random_array(bf) + let v = T::from_iter(v_content.iter()) + inspect!(v.length(), content=bf.to_string()) + inspect!(v[0], content=v_content[0].to_string()) + inspect!(v[bf - 1], content=v_content[bf - 1].to_string()) + inspect!(v[bf / 2], content=v_content[bf / 2].to_string()) + let bf = branching_factor + let v_content = random_array(bf) + let v = T::from_iter(v_content.iter()) + inspect!(v.length(), content=bf.to_string()) + inspect!(v[0], content=v_content[0].to_string()) + inspect!(v[bf - 1], content=v_content[bf - 1].to_string()) + inspect!(v[bf / 2], content=v_content[bf / 2].to_string()) +} + test "copy" { let bf = branching_factor_power(4) let v1_content = random_array(bf / 2) diff --git a/immut/array/tree.mbt b/immut/array/tree.mbt index ad37ddaae..99a8e0f70 100644 --- a/immut/array/tree.mbt +++ b/immut/array/tree.mbt @@ -81,7 +81,7 @@ fn is_empty_tree[T](self : Tree[T]) -> Bool { fn get_first[T](self : Tree[T]) -> T { match self { Leaf(leaf) => leaf[0] - Node(node, _) => get_first(node[0]) // TODO: prove that this should always be non-zero + Node(node, _) => get_first(node[0]) Empty => abort("Index out of bounds") } } @@ -91,7 +91,7 @@ fn get_first[T](self : Tree[T]) -> T { fn get_last[T](self : Tree[T]) -> T { match self { Leaf(leaf) => leaf[leaf.length() - 1] - Node(node, _) => get_last(node[node.length() - 1]) // TODO: prove that this should always be non-zero + Node(node, _) => get_last(node[node.length() - 1]) Empty => abort("Index out of bounds") } } @@ -105,8 +105,9 @@ fn get[T](self : Tree[T], index : Int, shift : Int) -> T { fn get_radix(node : Tree[T], shift : Int) -> T { match node { Leaf(leaf) => leaf[index & bitmask] - Node(node, None) => + Node(node, None) => { get_radix(node[radix_indexing(index, shift)], shift - num_bits) + } Node(_, Some(_)) => abort("Unreachable: Node should not have sizes in get_radix") Empty => abort("Index out of bounds") @@ -118,13 +119,15 @@ fn get[T](self : Tree[T], index : Int, shift : Int) -> T { Node(children, Some(sizes)) => { let branch_index = get_branch_index(sizes, index) let sub_index = if branch_index == 0 { - 0 + index } else { index - sizes[branch_index - 1] } get(children[branch_index], sub_index, shift - num_bits) } - Node(_, None) => get_radix(self, shift) + Node(_, None) => { + get_radix(self, shift) + } Empty => abort("Index out of bounds") } } @@ -272,6 +275,7 @@ fn push_end[T](self : Tree[T], shift : Int, value : T) -> (Tree[T], Int) { match self { Leaf(_leaf) => Node([self, new_branch], None) Node(_nodes, Some(sizes)) => { + // println("push-end dada") let len = sizes[sizes.length() - 1] let sizes = FixedArray::from_array([len, 1 + len]) check_sizes(sizes, shift) diff --git a/immut/array/utils_wbtest.mbt b/immut/array/utils_wbtest.mbt index fe11ad5d7..728ad4e63 100644 --- a/immut/array/utils_wbtest.mbt +++ b/immut/array/utils_wbtest.mbt @@ -158,27 +158,24 @@ fn repeat_up_to_32(s : String) -> Bytes { /// I guess that's because it exceeds the heap limit of the VM. fn check_array_eq(a : Array[Int], t : T[Int]) -> Unit! { assert_eq!(a.length(), t.size) - let mut i = 0 - for tt in t.iter() { - assert_eq!(tt, a[i]) - i += 1 + let len = t.size + for i = 0; i < len; i = i + 1 { + assert_eq!(t[i], a[i]) } } -///| +///| /// Use this function to check if the FixedArray and the @immut/array are equal. /// If we `inspect` the array, it will raise an error if the arrays are too long. /// I guess that's because it exceeds the heap limit of the VM. fn check_fixedarray_eq(a : FixedArray[Int], t : T[Int]) -> Unit! { assert_eq!(a.length(), t.size) - let mut i = 0 - for tt in t.iter() { - assert_eq!(tt, a[i]) - i += 1 + let len = t.size + for i = 0; i < len; i = i + 1 { + assert_eq!(t[i], a[i]) } } - ///| /// Generate a random array of length `n`. fn random_array(n : Int) -> FixedArray[Int] { From 15c2b8644485d85389439b343b89219eb877d6e4 Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Tue, 7 Jan 2025 16:44:51 +0800 Subject: [PATCH 20/31] remove unused functions --- immut/array/tree.mbt | 34 ++-------------------------------- 1 file changed, 2 insertions(+), 32 deletions(-) diff --git a/immut/array/tree.mbt b/immut/array/tree.mbt index 99a8e0f70..39cbab3ae 100644 --- a/immut/array/tree.mbt +++ b/immut/array/tree.mbt @@ -105,9 +105,8 @@ fn get[T](self : Tree[T], index : Int, shift : Int) -> T { fn get_radix(node : Tree[T], shift : Int) -> T { match node { Leaf(leaf) => leaf[index & bitmask] - Node(node, None) => { + Node(node, None) => get_radix(node[radix_indexing(index, shift)], shift - num_bits) - } Node(_, Some(_)) => abort("Unreachable: Node should not have sizes in get_radix") Empty => abort("Index out of bounds") @@ -125,9 +124,7 @@ fn get[T](self : Tree[T], index : Int, shift : Int) -> T { } get(children[branch_index], sub_index, shift - num_bits) } - Node(_, None) => { - get_radix(self, shift) - } + Node(_, None) => get_radix(self, shift) Empty => abort("Index out of bounds") } } @@ -172,8 +169,6 @@ fn set[T](self : Tree[T], index : Int, shift : Int, value : T) -> Tree[T] { } else { index - sizes[branch_index - 1] } - let sizes = sizes.copy() - check_sizes(sizes, shift - num_bits) Node( immutable_set( children, @@ -235,20 +230,12 @@ fn push_end[T](self : Tree[T], shift : Int, value : T) -> (Tree[T], Int) { let new_nodes = nodes.copy() new_nodes[len - 1] = new_node let sizes = update_sizes_last(sizes) - match sizes { - Some(sizes) => check_sizes(sizes, shift - num_bits) - None => () - } Some(Node(new_nodes, sizes)) } // We need to create a new node to push the value. None => if len < branching_factor { let sizes = push_sizes_last(sizes) - match sizes { - Some(sizes) => check_sizes(sizes, shift - num_bits) - None => () - } Some( Node( immutable_push( @@ -278,7 +265,6 @@ fn push_end[T](self : Tree[T], shift : Int, value : T) -> (Tree[T], Int) { // println("push-end dada") let len = sizes[sizes.length() - 1] let sizes = FixedArray::from_array([len, 1 + len]) - check_sizes(sizes, shift) Node([self, new_branch], Some(sizes)) } Node(_nodes, None) => Node([self, new_branch], None) @@ -714,32 +700,16 @@ fn compute_sizes[A]( for i = 0; i < len; i = i + 1 { let sz = children[i].size(shift) flag = flag && sz == full_subtree_size - if sz > full_subtree_size { - let _ = children[i].size(shift) - abort("TOOO BOGO") - } sum += sz sizes[i] = sum } if flag { None } else { - check_sizes(sizes, shift) Some(sizes) } } -///| -/// `shift` is the height of the subtree, whose size is sizes[i]-sizes[i-1] -fn check_sizes(sizes : FixedArray[Int], shift : Int) -> Unit { - for i = 1; i < sizes.length(); i = i + 1 { - if sizes[i] <= sizes[i - 1] || - sizes[i] - sizes[i - 1] > (1 << (shift + num_bits)) { - abort("Fuck") - } - } -} - //----------------------------------------------------------------------------- // Common Trait Implementations //----------------------------------------------------------------------------- From 9105c3a90a82dc993adb00fadec984bb967d7213 Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Tue, 7 Jan 2025 17:11:54 +0800 Subject: [PATCH 21/31] comment --- immut/array/tree.mbt | 1 - 1 file changed, 1 deletion(-) diff --git a/immut/array/tree.mbt b/immut/array/tree.mbt index 39cbab3ae..c234e4728 100644 --- a/immut/array/tree.mbt +++ b/immut/array/tree.mbt @@ -262,7 +262,6 @@ fn push_end[T](self : Tree[T], shift : Int, value : T) -> (Tree[T], Int) { match self { Leaf(_leaf) => Node([self, new_branch], None) Node(_nodes, Some(sizes)) => { - // println("push-end dada") let len = sizes[sizes.length() - 1] let sizes = FixedArray::from_array([len, 1 + len]) Node([self, new_branch], Some(sizes)) From 10271ec0831a35e0ceddea7359d0e7433ed15367 Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Wed, 8 Jan 2025 15:19:18 +0800 Subject: [PATCH 22/31] refactor test --- immut/array/array_wbtest.mbt | 24 +++--------- immut/array/utils_wbtest.mbt | 74 +++++++++++++----------------------- 2 files changed, 33 insertions(+), 65 deletions(-) diff --git a/immut/array/array_wbtest.mbt b/immut/array/array_wbtest.mbt index 155e85a51..723688cb9 100644 --- a/immut/array/array_wbtest.mbt +++ b/immut/array/array_wbtest.mbt @@ -97,8 +97,8 @@ test "copy" { let v2 = T::from_iter(v2_content.iter()) let v = v1.concat(v2) let v_copy = v.copy() - check_fixedarray_eq!(v_content, v_copy) - check_fixedarray_eq!(v_content, v) + check_array_eq!(v_content, v_copy) + check_array_eq!(v_content, v) } test "concat-two-full-tree" { @@ -133,21 +133,14 @@ test "concat-multiple-random-tree" { ///| /// Generate a sequence of concatenation operations as a test case, -/// following the format described in the `execute_array_test` function. /// /// Inputs: /// - `n`: the number of operations to be generated /// - `len_gen`: a function that generates the length of the array to be concatenated -fn gen_concat_seq(n : Int, len_gen : (Int) -> Int) -> Array[Int] { +fn gen_concat_seq(n : Int, len_gen : (Int) -> Int) -> Array[Op] { let ret = [] for i = 0; i < n; i = i + 1 { - ret.push(1) - let len = len_gen(i) - ret.push(len) - let arr = random_array(len) - for j = 0; j < len; j = j + 1 { - ret.push(arr[j]) - } + ret.push(Op::Concat(random_array(len_gen(i)))) } ret } @@ -156,15 +149,10 @@ fn gen_concat_seq(n : Int, len_gen : (Int) -> Int) -> Array[Int] { /// Generate a sequence of concatenation operations as a test case, /// similar to `gen_concat_seq`, but this time, the length of the array /// to be concatenated is given as an array. -fn gen_concat_seq_from_len_array(len : Array[Int]) -> Array[Int] { +fn gen_concat_seq_from_len_array(len : Array[Int]) -> Array[Op] { let ret = [] for i = 0; i < len.length(); i = i + 1 { - ret.push(1) - let arr = random_array(len[i]) - ret.push(len[i]) - for j = 0; j < len[i]; j = j + 1 { - ret.push(arr[j]) - } + ret.push(Op::Concat(random_array(len[i]))) } ret } diff --git a/immut/array/utils_wbtest.mbt b/immut/array/utils_wbtest.mbt index 728ad4e63..2930c78a2 100644 --- a/immut/array/utils_wbtest.mbt +++ b/immut/array/utils_wbtest.mbt @@ -12,6 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +///| +enum Op { + PushEnd(Int) + Concat(Array[Int]) + Set(Int, Int) +} + ///| /// Generate a random test sequence for the array, /// following the format described in the `execute_array_test` function. @@ -20,11 +27,7 @@ /// - `rng`: a random number generator /// - `times`: the number of operations to be generated /// - `max_lvl`: the maximum level of the tree -fn random_test_gen( - rng : @random.Rand, - times : Int, - max_lvl : Int -) -> Array[Int] { +fn random_test_gen(rng : @random.Rand, times : Int, max_lvl : Int) -> Array[Op] { // Hyperparameters let op_count = 3 let max_len = branching_factor_power(max_lvl) @@ -38,32 +41,24 @@ fn random_test_gen( match op { 0 => { // push_end - ret.push(op) - let val = rng.int(limit=max_val) - ret.push(val) + ret.push(Op::PushEnd(rng.int(limit=max_val))) cur_len += 1 } 1 => { // concat - ret.push(op) let len = rng.int(limit=max_len) - ret.push(len) - for j = 0; j < len; j = j + 1 { - let val = rng.int(limit=max_val) - ret.push(val) - } + let a = Array::make(len, rng.int(limit=max_val)) cur_len += len + ret.push(Op::Concat(a)) } 2 => { // set if cur_len == 0 { continue } - ret.push(op) let idx = rng.int(limit=cur_len) let val = rng.int(limit=max_val) - ret.push(idx) - ret.push(val) + ret.push(Op::Set(idx, val)) } _ => abort("Invalid op") } @@ -81,47 +76,32 @@ fn random_test_gen( /// 2. set /// /// The `rs` array is a sequence of operations to be executed. -/// For each operation, the following value sequence are expected: -/// - push_end: 0 value -/// - concat: 1 length v0 v1 ... v_{length - 1} -/// - set: 2 index value -fn execute_array_test(rs : Array[Int]) -> Unit! { +fn execute_array_test(rs : Array[Op]) -> Unit! { let mut t = T::new() - let a = [] - let len = rs.length() - let mut i = 0 - while i < len { - let op = rs[i] - i += 1 + let a : Array[Int] = [] + for op in rs { match op { - 0 => { + PushEnd(v) => { // push_end - let rsi = rs[i] - a.push(rsi) - t = t.push(rsi) - i += 1 + a.push(v) + t = t.push(v) check_array_eq!(a, t) } - 1 => { + Concat(v) => { // concat - let len = rs[i] - i += 1 - rs.blit_to(a, len~, src_offset=i, dst_offset=a.length()) - let t1 = T::from_iter(rs[i:i + len].iter()) - t = t.concat(t1) - i += len + let len = v.length() + for i = 0; i < len; i = i + 1 { + a.push(v[i]) + } + t = t.concat(T::from_iter(v.iter())) check_array_eq!(a, t) } - 2 => { + Set(idx, v) => { // set - let idx = rs[i] - let v = rs[i + 1] - i += 2 a[idx] = v t = t.set(idx, v) check_array_eq!(a, t) } - _ => abort("Invalid op") } } } @@ -178,7 +158,7 @@ fn check_fixedarray_eq(a : FixedArray[Int], t : T[Int]) -> Unit! { ///| /// Generate a random array of length `n`. -fn random_array(n : Int) -> FixedArray[Int] { +fn random_array(n : Int) -> Array[Int] { let rng = @random.new(seed=b"DEADBEEFLIVEBEEFDEADBEEFDEADBEEF") - FixedArray::makei(n, fn(i) { rng.int(limit=i) }) + Array::makei(n, fn(i) { rng.int(limit=i) }) } From 7806c0452948fc2e262adee49b53cabd5b18e83c Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Wed, 15 Jan 2025 11:55:30 -0500 Subject: [PATCH 23/31] add from_iter --- immut/array/array.mbt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/immut/array/array.mbt b/immut/array/array.mbt index 90e90b9cb..9b8fb207c 100644 --- a/immut/array/array.mbt +++ b/immut/array/array.mbt @@ -75,6 +75,11 @@ pub fn T::from_array[A](arr : Array[A]) -> T[A] { makei(arr.length(), fn(i) { arr[i] }) } +///| +pub fn from_iter[A](iter : Iter[A]) -> T[A] { + iter.fold(init=new(), fn(arr, e) { arr.push(e) }) +} + //----------------------------------------------------------------------------- // Convertor (convert T to other types) //----------------------------------------------------------------------------- @@ -86,11 +91,6 @@ pub fn to_array[A](self : T[A]) -> Array[A] { arr } -///| -pub fn from_iter[A](iter : Iter[A]) -> T[A] { - iter.fold(init=new(), fn(arr, e) { arr.push(e) }) -} - //----------------------------------------------------------------------------- // Properties //----------------------------------------------------------------------------- From aa2c14e727ef96044dff434cc9614db5ccf90532 Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Wed, 15 Jan 2025 11:59:19 -0500 Subject: [PATCH 24/31] fix --- immut/array/array.mbt | 8 ++++---- immut/array/array_wbtest.mbt | 12 ++++++------ immut/array/operation.mbt | 34 ---------------------------------- immut/array/utils_wbtest.mbt | 4 ++-- 4 files changed, 12 insertions(+), 46 deletions(-) delete mode 100644 immut/array/operation.mbt diff --git a/immut/array/array.mbt b/immut/array/array.mbt index 9b8fb207c..b14533c35 100644 --- a/immut/array/array.mbt +++ b/immut/array/array.mbt @@ -24,19 +24,19 @@ pub fn new[A]() -> T[A] { ///| /// Create a persistent array with a given length and value. -pub fn T::make[A](len : Int, value : A) -> T[A] { +pub fn make[A](len : Int, value : A) -> T[A] { new_by_leaves(len, fn(_s, l) { FixedArray::make(l, value) }) } ///| /// Create a persistent array with a given length and a function to generate values. -pub fn T::makei[A](len : Int, f : (Int) -> A) -> T[A] { +pub fn makei[A](len : Int, f : (Int) -> A) -> T[A] { new_by_leaves(len, fn(s, l) { FixedArray::makei(l, fn(i) { f(s + i) }) }) } ///| /// Convert a FixedArray to an @immut/array. -pub fn T::of[A](arr : FixedArray[A]) -> T[A] { +pub fn of[A](arr : FixedArray[A]) -> T[A] { makei(arr.length(), fn(i) { arr[i] }) } @@ -71,7 +71,7 @@ pub fn copy[A](self : T[A]) -> T[A] { /// let v = @array.of([1, 2, 3]) /// assert_eq!(v, @array.from_array([1, 2, 3])) /// ``` -pub fn T::from_array[A](arr : Array[A]) -> T[A] { +pub fn from_array[A](arr : Array[A]) -> T[A] { makei(arr.length(), fn(i) { arr[i] }) } diff --git a/immut/array/array_wbtest.mbt b/immut/array/array_wbtest.mbt index 723688cb9..5b74f7875 100644 --- a/immut/array/array_wbtest.mbt +++ b/immut/array/array_wbtest.mbt @@ -35,7 +35,7 @@ test "new_by_leaves" { } test "mix" { - let mut v = T::new() + let mut v = new() inspect!(v.tree.is_empty_tree(), content="true") for i = 0; i < 100; i = i + 1 { v = v.push(i) @@ -60,7 +60,7 @@ test "mix" { inspect!(ct2, content="19800") inspect!(v.tree.is_empty_tree(), content="false") let large_const = branching_factor * branching_factor + 1 - let mut v = T::new() + let mut v = new() for i = 0; i < large_const; i = i + 1 { v = v.push(i) } @@ -74,14 +74,14 @@ test "mix" { test "op_get" { let bf = branching_factor_power(4) let v_content = random_array(bf) - let v = T::from_iter(v_content.iter()) + let v = from_iter(v_content.iter()) inspect!(v.length(), content=bf.to_string()) inspect!(v[0], content=v_content[0].to_string()) inspect!(v[bf - 1], content=v_content[bf - 1].to_string()) inspect!(v[bf / 2], content=v_content[bf / 2].to_string()) let bf = branching_factor let v_content = random_array(bf) - let v = T::from_iter(v_content.iter()) + let v = from_iter(v_content.iter()) inspect!(v.length(), content=bf.to_string()) inspect!(v[0], content=v_content[0].to_string()) inspect!(v[bf - 1], content=v_content[bf - 1].to_string()) @@ -93,8 +93,8 @@ test "copy" { let v1_content = random_array(bf / 2) let v2_content = random_array(bf / 3) let v_content = v1_content + v2_content - let v1 = T::from_iter(v1_content.iter()) - let v2 = T::from_iter(v2_content.iter()) + let v1 = from_iter(v1_content.iter()) + let v2 = from_iter(v2_content.iter()) let v = v1.concat(v2) let v_copy = v.copy() check_array_eq!(v_content, v_copy) diff --git a/immut/array/operation.mbt b/immut/array/operation.mbt deleted file mode 100644 index 8dfd8ced9..000000000 --- a/immut/array/operation.mbt +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2025 International Digital Economy Academy -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -///| -fn immutable_set[T](arr : FixedArray[T], i : Int, v : T) -> FixedArray[T] { - let arr = arr.copy() - arr[i] = v - arr -} - -///| -fn immutable_push[T](arr : FixedArray[T], val : T) -> FixedArray[T] { - let len = arr.length() - let new_arr = FixedArray::make(len + 1, val) - arr.blit_to(new_arr, len~) - new_arr[len] = val - new_arr -} - -///| -fn shr_as_uint(x : Int, y : Int) -> Int { - (x.reinterpret_as_uint() >> y).reinterpret_as_int() -} diff --git a/immut/array/utils_wbtest.mbt b/immut/array/utils_wbtest.mbt index 2930c78a2..af9c040ba 100644 --- a/immut/array/utils_wbtest.mbt +++ b/immut/array/utils_wbtest.mbt @@ -77,7 +77,7 @@ fn random_test_gen(rng : @random.Rand, times : Int, max_lvl : Int) -> Array[Op] /// /// The `rs` array is a sequence of operations to be executed. fn execute_array_test(rs : Array[Op]) -> Unit! { - let mut t = T::new() + let mut t = new() let a : Array[Int] = [] for op in rs { match op { @@ -93,7 +93,7 @@ fn execute_array_test(rs : Array[Op]) -> Unit! { for i = 0; i < len; i = i + 1 { a.push(v[i]) } - t = t.concat(T::from_iter(v.iter())) + t = t.concat(from_iter(v.iter())) check_array_eq!(a, t) } Set(idx, v) => { From f06aa7443935153debcba058939a6ff0bfbeae7c Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Wed, 15 Jan 2025 12:01:20 -0500 Subject: [PATCH 25/31] header --- immut/array/array_mix_wbtest.mbt | 2 +- immut/array/array_wbtest.mbt | 2 +- immut/array/tree_utils.mbt | 2 +- immut/array/utils.mbt | 2 +- immut/array/utils_wbtest.mbt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/immut/array/array_mix_wbtest.mbt b/immut/array/array_mix_wbtest.mbt index bcc2e7b6a..b45eb003a 100644 --- a/immut/array/array_mix_wbtest.mbt +++ b/immut/array/array_mix_wbtest.mbt @@ -1,4 +1,4 @@ -// Copyright 2024 International Digital Economy Academy +// Copyright 2025 International Digital Economy Academy // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/immut/array/array_wbtest.mbt b/immut/array/array_wbtest.mbt index 5b74f7875..ea8bf11da 100644 --- a/immut/array/array_wbtest.mbt +++ b/immut/array/array_wbtest.mbt @@ -1,4 +1,4 @@ -// Copyright 2024 International Digital Economy Academy +// Copyright 2025 International Digital Economy Academy // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/immut/array/tree_utils.mbt b/immut/array/tree_utils.mbt index fb338c88a..3a947cb71 100644 --- a/immut/array/tree_utils.mbt +++ b/immut/array/tree_utils.mbt @@ -1,4 +1,4 @@ -// Copyright 2024 International Digital Economy Academy +// Copyright 2025 International Digital Economy Academy // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/immut/array/utils.mbt b/immut/array/utils.mbt index 549e74ca0..4d79a26f7 100644 --- a/immut/array/utils.mbt +++ b/immut/array/utils.mbt @@ -1,4 +1,4 @@ -// Copyright 2024 International Digital Economy Academy +// Copyright 2025 International Digital Economy Academy // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/immut/array/utils_wbtest.mbt b/immut/array/utils_wbtest.mbt index af9c040ba..30ba32b66 100644 --- a/immut/array/utils_wbtest.mbt +++ b/immut/array/utils_wbtest.mbt @@ -1,4 +1,4 @@ -// Copyright 2024 International Digital Economy Academy +// Copyright 2025 International Digital Economy Academy // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. From 4046743aadccdde09c7e791dcb5cd0073f8fb58c Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Wed, 15 Jan 2025 12:38:15 -0500 Subject: [PATCH 26/31] name clash --- immut/array/tree.mbt | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/immut/array/tree.mbt b/immut/array/tree.mbt index 8cc6b73e3..4f768ffce 100644 --- a/immut/array/tree.mbt +++ b/immut/array/tree.mbt @@ -65,7 +65,7 @@ fn new_branch_left[T](leaf : FixedArray[T], shift : Int) -> Tree[T] { //----------------------------------------------------------------------------- ///| -fn is_empty_tree[T](self : Tree[T]) -> Bool { +fn Tree::is_empty_tree[T](self : Tree[T]) -> Bool { match self { Tree::Empty => true _ => false @@ -78,7 +78,7 @@ fn is_empty_tree[T](self : Tree[T]) -> Bool { ///| /// Get the rightmost child of a tree. -fn get_first[T](self : Tree[T]) -> T { +fn Tree::get_first[T](self : Tree[T]) -> T { match self { Leaf(leaf) => leaf[0] Node(node, _) => get_first(node[0]) @@ -88,7 +88,7 @@ fn get_first[T](self : Tree[T]) -> T { ///| /// Get the leftmost child of a tree. -fn get_last[T](self : Tree[T]) -> T { +fn Tree::get_last[T](self : Tree[T]) -> T { match self { Leaf(leaf) => leaf[leaf.length() - 1] Node(node, _) => get_last(node[node.length() - 1]) @@ -101,7 +101,7 @@ fn get_last[T](self : Tree[T]) -> T { /// /// Precondition: /// - `self` is of height `shift / num_bits`. -fn get[T](self : Tree[T], index : Int, shift : Int) -> T { +fn Tree::get[T](self : Tree[T], index : Int, shift : Int) -> T { fn get_radix(node : Tree[T], shift : Int) -> T { match node { Leaf(leaf) => leaf[index & bitmask] @@ -138,7 +138,7 @@ fn get[T](self : Tree[T], index : Int, shift : Int) -> T { /// /// Precondition: /// - `self` is of height `shift / num_bits`. -fn set[T](self : Tree[T], index : Int, shift : Int, value : T) -> Tree[T] { +fn Tree::set[T](self : Tree[T], index : Int, shift : Int, value : T) -> Tree[T] { // TODO: optimize this as loop fn set_radix(node : Tree[T], shift : Int) -> Tree[T] { match node { @@ -189,7 +189,7 @@ fn set[T](self : Tree[T], index : Int, shift : Int, value : T) -> Tree[T] { /// Precondition: /// - The height of `self` = `shift` / `num_bits` (the height starts from 0). /// - `length` is the number of elements in the tree. -fn push_end[T](self : Tree[T], shift : Int, value : T) -> (Tree[T], Int) { +fn Tree::push_end[T](self : Tree[T], shift : Int, value : T) -> (Tree[T], Int) { fn update_sizes_last(sizes : FixedArray[Int]?) -> FixedArray[Int]? { match sizes { Some(sizes) => { @@ -284,7 +284,7 @@ fn push_end[T](self : Tree[T], shift : Int, value : T) -> (Tree[T], Int) { ///| /// For each element in the tree, apply the function `f`. -fn each[A](self : Tree[A], f : (A) -> Unit) -> Unit { +fn Tree::each[A](self : Tree[A], f : (A) -> Unit) -> Unit { match self { Empty => () Leaf(l) => l.each(f) @@ -294,7 +294,7 @@ fn each[A](self : Tree[A], f : (A) -> Unit) -> Unit { ///| /// For each element in the tree, apply the function `f` with the index of the element. -fn eachi[A]( +fn Tree::eachi[A]( self : Tree[A], f : (Int, A) -> Unit, shift : Int, @@ -327,7 +327,7 @@ fn eachi[A]( ///| /// Fold the tree. -fn fold[A, B](self : Tree[A], acc : B, f : (B, A) -> B) -> B { +fn Tree::fold[A, B](self : Tree[A], acc : B, f : (B, A) -> B) -> B { match self { Empty => acc Leaf(l) => l.fold(f, init=acc) @@ -337,7 +337,7 @@ fn fold[A, B](self : Tree[A], acc : B, f : (B, A) -> B) -> B { ///| /// Fold the tree in reverse order. -fn rev_fold[A, B](self : Tree[A], acc : B, f : (B, A) -> B) -> B { +fn Tree::rev_fold[A, B](self : Tree[A], acc : B, f : (B, A) -> B) -> B { match self { Empty => acc Leaf(l) => l.rev_fold(f, init=acc) @@ -347,7 +347,7 @@ fn rev_fold[A, B](self : Tree[A], acc : B, f : (B, A) -> B) -> B { ///| /// Map the tree. -fn map[A, B](self : Tree[A], f : (A) -> B) -> Tree[B] { +fn Tree::map[A, B](self : Tree[A], f : (A) -> B) -> Tree[B] { match self { Empty => Empty Leaf(l) => Leaf(l.map(f)) @@ -573,12 +573,6 @@ fn redis_plan[A](t : FixedArray[Tree[A]]) -> (FixedArray[Int], Int) { return (node_counts, new_len) } -// test "redis_plan" { -// let bf = branching_factor -// let t = FixedArray::from_array([bf, bf, 7]) -// let res = redis_plan(t) -// } - ///| /// This function redistributes the nodes in `old_t` according to the plan in `node_counts`. /// From 7db85fd3493f7c4b10f8287e8bdb0a381dba2af4 Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Wed, 15 Jan 2025 12:44:23 -0500 Subject: [PATCH 27/31] name clash --- immut/array/panic_wbtest.mbt | 6 +++--- immut/array/tree.mbt | 6 +++--- immut/array/tree_utils.mbt | 18 +++++++++--------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/immut/array/panic_wbtest.mbt b/immut/array/panic_wbtest.mbt index f9309a194..366968855 100644 --- a/immut/array/panic_wbtest.mbt +++ b/immut/array/panic_wbtest.mbt @@ -14,15 +14,15 @@ test "panic get_first on empty tree should panic" { let tree : Tree[Int] = Tree::Empty - get_first(tree) |> ignore + tree.get_first() |> ignore } test "panic get_last on empty tree should panic" { let tree : Tree[Int] = Tree::Empty - get_last(tree) |> ignore + tree.get_last() |> ignore } test "panic get on empty tree should panic" { let tree : Tree[Int] = Tree::Empty - get(tree, 0, 5) |> ignore + tree.get(0, 5) |> ignore } diff --git a/immut/array/tree.mbt b/immut/array/tree.mbt index 4f768ffce..a2150d2d3 100644 --- a/immut/array/tree.mbt +++ b/immut/array/tree.mbt @@ -81,7 +81,7 @@ fn Tree::is_empty_tree[T](self : Tree[T]) -> Bool { fn Tree::get_first[T](self : Tree[T]) -> T { match self { Leaf(leaf) => leaf[0] - Node(node, _) => get_first(node[0]) + Node(node, _) => node[0].get_first() Empty => abort("Index out of bounds") } } @@ -91,7 +91,7 @@ fn Tree::get_first[T](self : Tree[T]) -> T { fn Tree::get_last[T](self : Tree[T]) -> T { match self { Leaf(leaf) => leaf[leaf.length() - 1] - Node(node, _) => get_last(node[node.length() - 1]) + Node(node, _) => node[node.length() - 1].get_last() Empty => abort("Index out of bounds") } } @@ -122,7 +122,7 @@ fn Tree::get[T](self : Tree[T], index : Int, shift : Int) -> T { } else { index - sizes[branch_index - 1] } - get(children[branch_index], sub_index, shift - num_bits) + children[branch_index].get(sub_index, shift - num_bits) } Node(_, None) => get_radix(self, shift) Empty => abort("Index out of bounds") diff --git a/immut/array/tree_utils.mbt b/immut/array/tree_utils.mbt index 3a947cb71..c648b2001 100644 --- a/immut/array/tree_utils.mbt +++ b/immut/array/tree_utils.mbt @@ -17,7 +17,7 @@ ///| /// If the tree is a `Node`. -fn is_node[A](self : Tree[A]) -> Bool { +fn Tree::is_node[A](self : Tree[A]) -> Bool { match self { Node(_, _) => true _ => false @@ -25,7 +25,7 @@ fn is_node[A](self : Tree[A]) -> Bool { } ///| -fn is_leaf[A](self : Tree[A]) -> Bool { +fn Tree::is_leaf[A](self : Tree[A]) -> Bool { match self { Leaf(_) => true _ => false @@ -33,7 +33,7 @@ fn is_leaf[A](self : Tree[A]) -> Bool { } ///| -fn is_empty[A](self : Tree[A]) -> Bool { +fn Tree::is_empty[A](self : Tree[A]) -> Bool { match self { Empty => true _ => false @@ -43,7 +43,7 @@ fn is_empty[A](self : Tree[A]) -> Bool { ///| /// Get the rightmost child of a tree node. Abort if /// it is not a `Node`. -fn right_child[A](self : Tree[A]) -> Tree[A] { +fn Tree::right_child[A](self : Tree[A]) -> Tree[A] { match self { Node(children, _) => children[children.length() - 1] Leaf(_) | Empty => abort("Should not get children on non-`Node`s") @@ -53,7 +53,7 @@ fn right_child[A](self : Tree[A]) -> Tree[A] { ///| /// Get the leftmost child of a tree node. Abort if /// it is not a `Node`. -fn left_child[A](self : Tree[A]) -> Tree[A] { +fn Tree::left_child[A](self : Tree[A]) -> Tree[A] { match self { Node(children, _) => children[0] Leaf(_) | Empty => abort("Should not get children on non-`Node`s") @@ -62,7 +62,7 @@ fn left_child[A](self : Tree[A]) -> Tree[A] { ///| /// Get the leaf contents. Abort if it is not a `Leaf`. -fn leaf_elements[A](self : Tree[A]) -> FixedArray[A] { +fn Tree::leaf_elements[A](self : Tree[A]) -> FixedArray[A] { match self { Leaf(children) => children _ => abort("Should not call `get_leaf_elements` on non-leaf nodes") @@ -71,7 +71,7 @@ fn leaf_elements[A](self : Tree[A]) -> FixedArray[A] { ///| /// Get the children of a `Node`. Abort if it is not a `Node`. -fn node_children[A](self : Tree[A]) -> FixedArray[Tree[A]] { +fn Tree::node_children[A](self : Tree[A]) -> FixedArray[Tree[A]] { match self { Node(children, _) => children _ => abort("Should not call `node_children` on non-`Node`s") @@ -80,7 +80,7 @@ fn node_children[A](self : Tree[A]) -> FixedArray[Tree[A]] { ///| /// Get the physical size of the current node, not the total number of elements in the tree. -fn local_size[A](self : Tree[A]) -> Int { +fn Tree::local_size[A](self : Tree[A]) -> Int { match self { Empty => 0 Leaf(l) => l.length() @@ -90,7 +90,7 @@ fn local_size[A](self : Tree[A]) -> Int { ///| /// Get the total number of elements in the tree. -fn size[A](self : Tree[A], shift : Int) -> Int { +fn Tree::size[A](self : Tree[A], shift : Int) -> Int { match self { Empty => 0 Leaf(l) => l.length() From 6f97ff77ec49069d56634f352740945ed3aaa622 Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Thu, 16 Jan 2025 21:19:56 -0500 Subject: [PATCH 28/31] calling convention --- immut/array/array.mbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/immut/array/array.mbt b/immut/array/array.mbt index b14533c35..1acba5afd 100644 --- a/immut/array/array.mbt +++ b/immut/array/array.mbt @@ -162,7 +162,7 @@ pub fn push[A](self : T[A], value : A) -> T[A] { ///| /// Given two trees, concatenate them into a new tree. -pub fn T::concat[A](self : T[A], other : T[A]) -> T[A] { +pub fn concat[A](self : T[A], other : T[A]) -> T[A] { if self.is_empty() { return other } From ca55910b020478a388da4e6bf07d56334ebb554c Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Thu, 16 Jan 2025 21:28:12 -0500 Subject: [PATCH 29/31] add `op_add` --- immut/array/array.mbt | 9 ++++++++- immut/array/tree.mbt | 8 ++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/immut/array/array.mbt b/immut/array/array.mbt index 1acba5afd..a7dba5f9e 100644 --- a/immut/array/array.mbt +++ b/immut/array/array.mbt @@ -169,7 +169,7 @@ pub fn concat[A](self : T[A], other : T[A]) -> T[A] { if other.is_empty() { return self } - let (tree, shift) = concat( + let (tree, shift) = Tree::concat( self.tree, self.shift, other.tree, @@ -178,6 +178,13 @@ pub fn concat[A](self : T[A], other : T[A]) -> T[A] { ) { tree, size: self.size + other.size, shift } } + +///| +/// Concat two arrays. +pub fn op_add[A](self : T[A], other : T[A]) -> T[A] { + self.concat(other) +} + //----------------------------------------------------------------------------- // Iterators //----------------------------------------------------------------------------- diff --git a/immut/array/tree.mbt b/immut/array/tree.mbt index a2150d2d3..e74b8da29 100644 --- a/immut/array/tree.mbt +++ b/immut/array/tree.mbt @@ -370,7 +370,7 @@ fn Tree::map[A, B](self : Tree[A], f : (A) -> B) -> Tree[B] { /// Preconditions: /// - `left` and `right` are not `Empty`. /// - `left` and `right` are of height `left_shift / num_bits` and `right_shift / num_bits`, respectively. -fn concat[A]( +fn Tree::concat[A]( left : Tree[A], left_shift : Int, right : Tree[A], @@ -378,7 +378,7 @@ fn concat[A]( top : Bool ) -> (Tree[A], Int) { if left_shift > right_shift { - let (c, c_shift) = concat( + let (c, c_shift) = Tree::concat( left.right_child(), left_shift - num_bits, right, @@ -388,7 +388,7 @@ fn concat[A]( guard c_shift == left_shift return rebalance(left, c, Empty, left_shift, top) } else if right_shift > left_shift { - let (c, c_shift) = concat( + let (c, c_shift) = Tree::concat( left, left_shift, right.left_child(), @@ -428,7 +428,7 @@ fn concat[A]( } } else { // Handle Node case - let (c, c_shift) = concat( + let (c, c_shift) = Tree::concat( left.right_child(), left_shift - num_bits, right.left_child(), From abc8de974eae090ac58f5c46c05c84f160b10073 Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Thu, 16 Jan 2025 21:32:33 -0500 Subject: [PATCH 30/31] moon info --- immut/array/array.mbti | 1 + 1 file changed, 1 insertion(+) diff --git a/immut/array/array.mbti b/immut/array/array.mbti index a03613b38..421190a52 100644 --- a/immut/array/array.mbti +++ b/immut/array/array.mbti @@ -35,6 +35,7 @@ impl T { map[A, B](Self[A], (A) -> B) -> Self[B] new[A]() -> Self[A] //deprecated of[A](FixedArray[A]) -> Self[A] //deprecated + op_add[A](Self[A], Self[A]) -> Self[A] op_get[A](Self[A], Int) -> A push[A](Self[A], A) -> Self[A] rev_fold[A, B](Self[A], init~ : B, (B, A) -> B) -> B From c6abd249262db0ec4b3bb6e176269886ffb9f131 Mon Sep 17 00:00:00 2001 From: Jialun Zhang Date: Fri, 17 Jan 2025 09:12:14 -0500 Subject: [PATCH 31/31] remove `copy` --- immut/array/array.mbt | 23 ----------------------- immut/array/array.mbti | 1 - immut/array/array_test.mbt | 13 ------------- immut/array/array_wbtest.mbt | 15 +-------------- 4 files changed, 1 insertion(+), 51 deletions(-) diff --git a/immut/array/array.mbt b/immut/array/array.mbt index a7dba5f9e..82bc67246 100644 --- a/immut/array/array.mbt +++ b/immut/array/array.mbt @@ -40,29 +40,6 @@ pub fn of[A](arr : FixedArray[A]) -> T[A] { makei(arr.length(), fn(i) { arr[i] }) } -///| -/// Physically copy the array. -/// Since it is an immutable data structure, -/// it is rarely the case that you would need this function. -pub fn copy[A](self : T[A]) -> T[A] { - fn copy(t : Tree[A]) -> Tree[A] { - match t { - Leaf(l) => Leaf(l.copy()) - Empty => Empty - Node(node, sizes) => - Node( - FixedArray::makei(node.length(), fn(i) { copy(node[i]) }), - match sizes { - Some(sizes) => Some(FixedArray::copy(sizes)) - None => None - }, - ) - } - } - - { tree: copy(self.tree), size: self.size, shift: self.shift } -} - ///| /// Create a persistent array from an array. /// diff --git a/immut/array/array.mbti b/immut/array/array.mbti index 421190a52..767de9063 100644 --- a/immut/array/array.mbti +++ b/immut/array/array.mbti @@ -19,7 +19,6 @@ fn of[A](FixedArray[A]) -> T[A] type T impl T { concat[A](Self[A], Self[A]) -> Self[A] - copy[A](Self[A]) -> Self[A] each[A](Self[A], (A) -> Unit) -> Unit eachi[A](Self[A], (Int, A) -> Unit) -> Unit fold[A, B](Self[A], init~ : B, (B, A) -> B) -> B diff --git a/immut/array/array_test.mbt b/immut/array/array_test.mbt index 303c6e75a..c4d29cfa6 100644 --- a/immut/array/array_test.mbt +++ b/immut/array/array_test.mbt @@ -61,19 +61,6 @@ test "length" { inspect!(ve.length(), content="0") } -test "copy" { - let v = @array.of([1, 2, 3, 4, 5]) - let vc = v.copy() - inspect!(vc, content="@immut/array.of([1, 2, 3, 4, 5])") - inspect!(v == vc, content="true") - assert_false!(physical_equal(v, vc)) - let v = @array.new() - let vc : @array.T[Int] = v.copy() - inspect!(vc, content="@immut/array.of([])") - inspect!(v == vc, content="true") - assert_false!(physical_equal(v, vc)) -} - test "op_get" { let v = @array.of([1, 2, 3, 4, 5]) inspect!(v[0], content="1") diff --git a/immut/array/array_wbtest.mbt b/immut/array/array_wbtest.mbt index ea8bf11da..d3560a3e8 100644 --- a/immut/array/array_wbtest.mbt +++ b/immut/array/array_wbtest.mbt @@ -44,7 +44,7 @@ test "mix" { v, content="@immut/array.of([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99])", ) - let mut v2 = v.copy() + let mut v2 = v for i = 0; i < 100; i = i + 1 { v2 = v2.set(i, i * 2) } @@ -88,19 +88,6 @@ test "op_get" { inspect!(v[bf / 2], content=v_content[bf / 2].to_string()) } -test "copy" { - let bf = branching_factor_power(4) - let v1_content = random_array(bf / 2) - let v2_content = random_array(bf / 3) - let v_content = v1_content + v2_content - let v1 = from_iter(v1_content.iter()) - let v2 = from_iter(v2_content.iter()) - let v = v1.concat(v2) - let v_copy = v.copy() - check_array_eq!(v_content, v_copy) - check_array_eq!(v_content, v) -} - test "concat-two-full-tree" { let bf = branching_factor_power(4) execute_array_test!(gen_concat_seq_from_len_array([bf, bf]))