From a248511d7b820af9c18c9975005f648b46255e7c Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Mon, 19 Aug 2024 23:20:14 +0200 Subject: [PATCH] Added ElasticArray --- lib/std/collections/elastic_array.c3 | 434 ++++++++++++++++++ lib/std/collections/list.c3 | 108 +---- lib/std/collections/list_common.c3 | 112 +++++ releasenotes.md | 1 + test/unit/stdlib/collections/elastic_array.c3 | 143 ++++++ 5 files changed, 702 insertions(+), 96 deletions(-) create mode 100644 lib/std/collections/elastic_array.c3 create mode 100644 lib/std/collections/list_common.c3 create mode 100644 test/unit/stdlib/collections/elastic_array.c3 diff --git a/lib/std/collections/elastic_array.c3 b/lib/std/collections/elastic_array.c3 new file mode 100644 index 000000000..c94e6384c --- /dev/null +++ b/lib/std/collections/elastic_array.c3 @@ -0,0 +1,434 @@ +// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved. +// Use of self source code is governed by the MIT license +// a copy of which can be found in the LICENSE_STDLIB file. +/** + * @require MAX_SIZE >= 1 `The size must be at least 1 element big.` + **/ +module std::collections::elastic_array(); +import std::io, std::math, std::collections::list_common; + +def ElementPredicate = fn bool(Type *type); +def ElementTest = fn bool(Type *type, any context); +const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type); +const ELEMENT_IS_POINTER = Type.kindof == POINTER; +macro type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT; + +struct ElasticArray (Printable) +{ + usz size; + Type[MAX_SIZE] entries; +} + +fn usz! ElasticArray.to_format(&self, Formatter* formatter) @dynamic +{ + switch (self.size) + { + case 0: + return formatter.print("[]")!; + case 1: + return formatter.printf("[%s]", self.entries[0])!; + default: + usz n = formatter.print("[")!; + foreach (i, element : self.entries[:self.size]) + { + if (i != 0) formatter.print(", ")!; + n += formatter.printf("%s", element)!; + } + n += formatter.print("]")!; + return n; + } +} + +fn String ElasticArray.to_string(&self, Allocator allocator) @dynamic +{ + return string::new_format("%s", *self, .allocator = allocator); +} + +fn String ElasticArray.to_tstring(&self) +{ + return string::tformat("%s", *self); +} + +fn void! ElasticArray.push_try(&self, Type element) @inline +{ + if (self.size == MAX_SIZE) return AllocationFailure.OUT_OF_MEMORY?; + self.entries[self.size++] = element; +} + +/** + * @require self.size < MAX_SIZE `Tried to exceed the max size` + **/ +fn void ElasticArray.push(&self, Type element) @inline +{ + self.entries[self.size++] = element; +} + +fn Type! ElasticArray.pop(&self) +{ + if (!self.size) return IteratorResult.NO_MORE_ELEMENT?; + return self.entries[--self.size]; +} + +fn void ElasticArray.clear(&self) +{ + self.size = 0; +} + +/** + * @require self.size > 0 + **/ +fn Type! ElasticArray.pop_first(&self) +{ + if (!self.size) return IteratorResult.NO_MORE_ELEMENT?; + defer self.remove_at(0); + return self.entries[0]; +} + +/** + * @require index < self.size + **/ +fn void ElasticArray.remove_at(&self, usz index) +{ + if (!--self.size || index == self.size) return; + self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size]; +} + +/** + * @require other_list.size + self.size <= MAX_SIZE + **/ +fn void ElasticArray.add_all(&self, ElasticArray* other_list) +{ + if (!other_list.size) return; + foreach (&value : other_list) + { + self.entries[self.size++] = *value; + } +} + +/** + * Add as many elements as possible to the new array, + * returning the number of elements that didn't fit. + **/ +fn usz ElasticArray.add_all_to_limit(&self, ElasticArray* other_list) +{ + if (!other_list.size) return 0; + foreach (i, &value : other_list) + { + if (self.size == MAX_SIZE) return other_list.size - i; + self.entries[self.size++] = *value; + } + return 0; +} + +/** + * Add as many values from this array as possible, returning the + * number of elements that didn't fit. + * + * @param [in] array + **/ +fn usz ElasticArray.add_array_to_limit(&self, Type[] array) +{ + if (!array.len) return 0; + foreach (i, &value : array) + { + if (self.size == MAX_SIZE) return array.len - i; + self.entries[self.size++] = *value; + } + return 0; +} + +/** + * Add the values of an array to this list. + * + * @param [in] array + * @require array.len + self.size <= MAX_SIZE `Size would exceed max.` + * @ensure self.size >= array.len + **/ +fn void ElasticArray.add_array(&self, Type[] array) +{ + if (!array.len) return; + foreach (&value : array) + { + self.entries[self.size++] = *value; + } +} + + + +/** + * IMPORTANT The returned array must be freed using free_aligned. + **/ +fn Type[] ElasticArray.to_new_aligned_array(&self, Allocator allocator = allocator::heap()) +{ + return list_common::list_to_new_aligned_array(Type, self, allocator); +} + +/** + * @require !type_is_overaligned() : "This function is not available on overaligned types" + **/ +macro Type[] ElasticArray.to_new_array(&self, Allocator allocator = allocator::heap()) +{ + return list_common::list_to_new_array(Type, self, allocator); +} + +fn Type[] ElasticArray.to_tarray(&self) +{ + $if type_is_overaligned(): + return self.to_new_aligned_array(allocator::temp()); + $else + return self.to_new_array(allocator::temp()); + $endif; +} + +/** + * Reverse the elements in a list. + **/ +fn void ElasticArray.reverse(&self) +{ + list_common::list_reverse(self); +} + +fn Type[] ElasticArray.array_view(&self) +{ + return self.entries[:self.size]; +} + +/** + * @require self.size < MAX_SIZE `List would exceed max size` + **/ +fn void ElasticArray.push_front(&self, Type type) @inline +{ + self.insert_at(0, type); +} + +/** + * @require self.size < MAX_SIZE `List would exceed max size` + **/ +fn void! ElasticArray.push_front_try(&self, Type type) @inline +{ + return self.insert_at_try(0, type); +} + +/** + * @require index <= self.size + **/ +fn void! ElasticArray.insert_at_try(&self, usz index, Type value) +{ + if (self.size == MAX_SIZE) return AllocationFailure.OUT_OF_MEMORY?; + self.insert_at(index, value); +} + +/** + * @require self.size < MAX_SIZE `List would exceed max size` + * @require index <= self.size + **/ +fn void ElasticArray.insert_at(&self, usz index, Type type) +{ + for (usz i = self.size; i > index; i--) + { + self.entries[i] = self.entries[i - 1]; + } + self.size++; + self.entries[index] = type; +} + +/** + * @require index < self.size + **/ +fn void ElasticArray.set_at(&self, usz index, Type type) +{ + self.entries[index] = type; +} + +fn void! ElasticArray.remove_last(&self) @maydiscard +{ + if (!self.size) return IteratorResult.NO_MORE_ELEMENT?; + self.size--; +} + +fn void! ElasticArray.remove_first(&self) @maydiscard +{ + if (!self.size) return IteratorResult.NO_MORE_ELEMENT?; + self.remove_at(0); +} + +fn Type! ElasticArray.first(&self) +{ + if (!self.size) return IteratorResult.NO_MORE_ELEMENT?; + return self.entries[0]; +} + +fn Type! ElasticArray.last(&self) +{ + if (!self.size) return IteratorResult.NO_MORE_ELEMENT?; + return self.entries[self.size - 1]; +} + +fn bool ElasticArray.is_empty(&self) @inline +{ + return !self.size; +} + +fn usz ElasticArray.byte_size(&self) @inline +{ + return Type.sizeof * self.size; +} + +fn usz ElasticArray.len(&self) @operator(len) @inline +{ + return self.size; +} + +fn Type ElasticArray.get(&self, usz index) @inline +{ + return self.entries[index]; +} + +fn void ElasticArray.swap(&self, usz i, usz j) +{ + @swap(self.entries[i], self.entries[j]); +} + +/** + * @param filter "The function to determine if it should be removed or not" + * @return "the number of deleted elements" + **/ +fn usz ElasticArray.remove_if(&self, ElementPredicate filter) +{ + return list_common::list_remove_if(self, filter, false); +} + +/** + * @param selection "The function to determine if it should be kept or not" + * @return "the number of deleted elements" + **/ +fn usz ElasticArray.retain_if(&self, ElementPredicate selection) +{ + return list_common::list_remove_if(self, selection, true); +} + +fn usz ElasticArray.remove_using_test(&self, ElementTest filter, any context) +{ + return list_common::list_remove_using_test(self, filter, false, context); +} + +fn usz ElasticArray.retain_using_test(&self, ElementTest filter, any context) +{ + return list_common::list_remove_using_test(self, filter, true, context); +} + + +macro Type ElasticArray.@item_at(&self, usz index) @operator([]) +{ + return self.entries[index]; +} + +fn Type* ElasticArray.get_ref(&self, usz index) @operator(&[]) @inline +{ + return &self.entries[index]; +} + +fn void ElasticArray.set(&self, usz index, Type value) @operator([]=) +{ + self.entries[index] = value; +} + + +// Functions for equatable types +fn usz! ElasticArray.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE) +{ + foreach (i, v : self) + { + if (equals(v, type)) return i; + } + return SearchResult.MISSING?; +} + +fn usz! ElasticArray.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE) +{ + foreach_r (i, v : self) + { + if (equals(v, type)) return i; + } + return SearchResult.MISSING?; +} + +fn bool ElasticArray.equals(&self, ElasticArray other_list) @if(ELEMENT_IS_EQUATABLE) +{ + if (self.size != other_list.size) return false; + foreach (i, v : self) + { + if (!equals(v, other_list.entries[i])) return false; + } + return true; +} + +/** + * Check for presence of a value in a list. + * + * @param [&in] self "the list to find elements in" + * @param value "The value to search for" + * @return "True if the value is found, false otherwise" + **/ +fn bool ElasticArray.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE) +{ + foreach (i, v : self) + { + if (equals(v, value)) return true; + } + return false; +} + +/** + * @param [&inout] self "The list to remove elements from" + * @param value "The value to remove" + * @return "true if the value was found" + **/ +fn bool ElasticArray.remove_last_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE) +{ + return @ok(self.remove_at(self.rindex_of(value))); +} + +/** + * @param [&inout] self "The list to remove elements from" + * @param value "The value to remove" + * @return "true if the value was found" + **/ +fn bool ElasticArray.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE) +{ + return @ok(self.remove_at(self.index_of(value))); +} + +/** + * @param [&inout] self "The list to remove elements from" + * @param value "The value to remove" + * @return "the number of deleted elements." + **/ +fn usz ElasticArray.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE) +{ + return list_common::list_remove_item(self, value); +} + + + +fn void ElasticArray.remove_all_from(&self, ElasticArray* other_list) @if(ELEMENT_IS_EQUATABLE) +{ + if (!other_list.size) return; + foreach (v : other_list) self.remove_item(v); +} + +/** + * @param [&in] self + * @return "The number non-null values in the list" + **/ +fn usz ElasticArray.compact_count(&self) @if(ELEMENT_IS_POINTER) +{ + usz vals = 0; + foreach (v : self) if (v) vals++; + return vals; +} + +fn usz ElasticArray.compact(&self) @if(ELEMENT_IS_POINTER) +{ + return list_common::list_compact(self); +} \ No newline at end of file diff --git a/lib/std/collections/list.c3 b/lib/std/collections/list.c3 index 435aafc65..ca228408a 100644 --- a/lib/std/collections/list.c3 +++ b/lib/std/collections/list.c3 @@ -2,7 +2,7 @@ // Use of self source code is governed by the MIT license // a copy of which can be found in the LICENSE_STDLIB file. module std::collections::list(); -import std::io,std::math; +import std::io, std::math, std::collections::list_common; def ElementPredicate = fn bool(Type *type); def ElementTest = fn bool(Type *type, any context); @@ -173,10 +173,7 @@ fn void List.add_all(&self, List* other_list) **/ fn Type[] List.to_new_aligned_array(&self, Allocator allocator = allocator::heap()) { - if (!self.size) return Type[] {}; - Type[] result = allocator::alloc_array_aligned(allocator, Type, self.size); - result[..] = self.entries[:self.size]; - return result; + return list_common::list_to_new_aligned_array(Type, self, allocator); } /** @@ -184,10 +181,7 @@ fn Type[] List.to_new_aligned_array(&self, Allocator allocator = allocator::heap **/ macro Type[] List.to_new_array(&self, Allocator allocator = allocator::heap()) { - if (!self.size) return Type[] {}; - Type[] result = allocator::alloc_array(allocator, Type, self.size); - result[..] = self.entries[:self.size]; - return result; + return list_common::list_to_new_array(Type, self, allocator); } fn Type[] List.to_tarray(&self) @@ -204,13 +198,7 @@ fn Type[] List.to_tarray(&self) **/ fn void List.reverse(&self) { - if (self.size < 2) return; - usz half = self.size / 2U; - usz end = self.size - 1; - for (usz i = 0; i < half; i++) - { - @swap(self.entries[i], self.entries[end - i]); - } + list_common::list_reverse(self); } fn Type[] List.array_view(&self) @@ -228,10 +216,8 @@ fn void List.add_array(&self, Type[] array) { if (!array.len) return; self.reserve(array.len); - foreach (&value : array) - { - self.entries[self.size++] = *value; - } + self.entries[self.size : array.len] = array[..]; + self.size += array.len; } fn void List.push_front(&self, Type type) @inline @@ -329,7 +315,7 @@ fn void List.swap(&self, usz i, usz j) **/ fn usz List.remove_if(&self, ElementPredicate filter) { - return self._remove_if(filter, false); + return list_common::list_remove_if(self, filter, false); } /** @@ -338,67 +324,17 @@ fn usz List.remove_if(&self, ElementPredicate filter) **/ fn usz List.retain_if(&self, ElementPredicate selection) { - return self._remove_if(selection, true); -} - -macro usz List._remove_if(&self, ElementPredicate filter, bool $invert) @local -{ - usz size = self.size; - for (usz i = size, usz k = size; k > 0; k = i) - { - // Find last index of item to be deleted. - $if $invert: - while (i > 0 && !filter(&self.entries[i - 1])) i--; - $else - while (i > 0 && filter(&self.entries[i - 1])) i--; - $endif - // Remove the items from this index up to the one not to be deleted. - usz n = self.size - k; - self.entries[i:n] = self.entries[k:n]; - self.size -= k - i; - // Find last index of item not to be deleted. - $if $invert: - while (i > 0 && filter(&self.entries[i - 1])) i--; - $else - while (i > 0 && !filter(&self.entries[i - 1])) i--; - $endif - } - return size - self.size; + return list_common::list_remove_if(self, selection, true); } fn usz List.remove_using_test(&self, ElementTest filter, any context) { - return self._remove_using_test(filter, false, context); + return list_common::list_remove_using_test(self, filter, false, context); } fn usz List.retain_using_test(&self, ElementTest filter, any context) { - return self._remove_using_test(filter, true, context); -} - -macro usz List._remove_using_test(&self, ElementTest filter, bool $invert, ctx) @local -{ - usz size = self.size; - for (usz i = size, usz k = size; k > 0; k = i) - { - // Find last index of item to be deleted. - $if $invert: - while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--; - $else - while (i > 0 && filter(&self.entries[i - 1], ctx)) i--; - $endif - // Remove the items from this index up to the one not to be deleted. - usz n = self.size - k; - self.entries[i:n] = self.entries[k:n]; - self.size -= k - i; - // Find last index of item not to be deleted. - $if $invert: - while (i > 0 && filter(&self.entries[i - 1], ctx)) i--; - $else - while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--; - $endif - } - return size - self.size; + return list_common::list_remove_using_test(self, filter, true, context); } fn void List.ensure_capacity(&self, usz min_capacity) @local @@ -515,17 +451,7 @@ fn bool List.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE) **/ fn usz List.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE) { - usz size = self.size; - for (usz i = size; i > 0; i--) - { - if (!equals(self.entries[i - 1], value)) continue; - for (usz j = i; j < self.size; j++) - { - self.entries[j - 1] = self.entries[j]; - } - self.size--; - } - return size - self.size; + return list_common::list_remove_item(self, value); } @@ -549,17 +475,7 @@ fn usz List.compact_count(&self) @if(ELEMENT_IS_POINTER) fn usz List.compact(&self) @if(ELEMENT_IS_POINTER) { - usz size = self.size; - for (usz i = size; i > 0; i--) - { - if (self.entries[i - 1]) continue; - for (usz j = i; j < size; j++) - { - self.entries[j - 1] = self.entries[j]; - } - self.size--; - } - return size - self.size; + return list_common::list_compact(self); } // --> Deprecated diff --git a/lib/std/collections/list_common.c3 b/lib/std/collections/list_common.c3 new file mode 100644 index 000000000..416e0cad3 --- /dev/null +++ b/lib/std/collections/list_common.c3 @@ -0,0 +1,112 @@ +module std::collections::list_common; + +/** + * IMPORTANT The returned array must be freed using free_aligned. + **/ +macro list_to_new_aligned_array($Type, self, Allocator allocator) +{ + if (!self.size) return $Type[] {}; + $Type[] result = allocator::alloc_array_aligned(allocator, $Type, self.size); + result[..] = self.entries[:self.size]; + return result; +} + +macro list_to_new_array($Type, self, Allocator allocator) +{ + if (!self.size) return $Type[] {}; + $Type[] result = allocator::alloc_array(allocator, $Type, self.size); + result[..] = self.entries[:self.size]; + return result; +} + +macro void list_reverse(self) +{ + if (self.size < 2) return; + usz half = self.size / 2U; + usz end = self.size - 1; + for (usz i = 0; i < half; i++) + { + @swap(self.entries[i], self.entries[end - i]); + } +} + +macro usz list_remove_using_test(self, filter, bool $invert, ctx) +{ + usz size = self.size; + for (usz i = size, usz k = size; k > 0; k = i) + { + // Find last index of item to be deleted. + $if $invert: + while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--; + $else + while (i > 0 && filter(&self.entries[i - 1], ctx)) i--; + $endif + // Remove the items from this index up to the one not to be deleted. + usz n = self.size - k; + self.entries[i:n] = self.entries[k:n]; + self.size -= k - i; + // Find last index of item not to be deleted. + $if $invert: + while (i > 0 && filter(&self.entries[i - 1], ctx)) i--; + $else + while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--; + $endif + } + return size - self.size; +} + +macro usz list_compact(self) +{ + usz size = self.size; + for (usz i = size; i > 0; i--) + { + if (self.entries[i - 1]) continue; + for (usz j = i; j < size; j++) + { + self.entries[j - 1] = self.entries[j]; + } + self.size--; + } + return size - self.size; +} + +macro usz list_remove_item(self, value) +{ + usz size = self.size; + for (usz i = size; i > 0; i--) + { + if (!equals(self.entries[i - 1], value)) continue; + for (usz j = i; j < self.size; j++) + { + self.entries[j - 1] = self.entries[j]; + } + self.size--; + } + return size - self.size; +} + + +macro usz list_remove_if(self, filter, bool $invert) +{ + usz size = self.size; + for (usz i = size, usz k = size; k > 0; k = i) + { + // Find last index of item to be deleted. + $if $invert: + while (i > 0 && !filter(&self.entries[i - 1])) i--; + $else + while (i > 0 && filter(&self.entries[i - 1])) i--; + $endif + // Remove the items from this index up to the one not to be deleted. + usz n = self.size - k; + self.entries[i:n] = self.entries[k:n]; + self.size -= k - i; + // Find last index of item not to be deleted. + $if $invert: + while (i > 0 && filter(&self.entries[i - 1])) i--; + $else + while (i > 0 && !filter(&self.entries[i - 1])) i--; + $endif + } + return size - self.size; +} diff --git a/releasenotes.md b/releasenotes.md index 538e92998..93c2bd576 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -91,6 +91,7 @@ - Added `path.has_extension`, `path.new_append`, `path.temp_append`, `new_cwd`, `temp_cwd`, `path.new_absolute`, `new_ls`, `temp_ls`. - Added `dstring.replace` - New hashmap type, `Map` +- Added `ElasticArray`. ## 0.6.1 Change list diff --git a/test/unit/stdlib/collections/elastic_array.c3 b/test/unit/stdlib/collections/elastic_array.c3 new file mode 100644 index 000000000..a41cfcc28 --- /dev/null +++ b/test/unit/stdlib/collections/elastic_array.c3 @@ -0,0 +1,143 @@ +module elastic_array_test @test; +import std::collections::elastic_array; + +def IntList = ElasticArray(); +def PtrList = ElasticArray(); + +fn void! delete_contains_index() +{ + IntList test; + test.add_array({ 1, 2 }); + assert(test.contains(1)); + assert(test.contains(2)); + assert(!test.contains(0)); + assert(!test.contains(3)); + assert(test.array_view() == int[]{ 1, 2 }); + test.push(3); + assert(test.array_view() == int[]{ 1, 2, 3 }); + assert(test.contains(3)); + test[0] = 10; + assert(test.contains(10)); + test.remove_item(10); + assert(test.array_view() == int[]{ 2, 3 }); + assert(!test.contains(1)); + assert(test.contains(2)); + assert(test.len() == 2); + test.push(0); + test.insert_at(0, 0); + assert(test.array_view() == int[]{ 0, 2, 3, 0 }); + assert(test.index_of(0)! == 0); + assert(test.rindex_of(0)! == 3); + test.remove_item(0); + assert(test.len() == 2); + assert(test.array_view() == int[]{ 2, 3 }); +} + +fn void! compact() +{ + PtrList test; + test.add_array({ null, &test }); + assert(test.compact_count() == 1); + test.push(null); + assert(test.compact_count() == 1); + assert(test.len() == 3); + assert(test.compact() == 2); + assert(test.len() == 1); + assert(test.compact() == 0); +} + +fn void! reverse() +{ + IntList test; + test.reverse(); + test.add_array({ 1, 2 }); + test.push(3); + assert(test.array_view() == int[] { 1, 2, 3}); + test.reverse(); + assert(test.array_view() == int[] { 3, 2, 1 }); + test.push(10); + assert(test.array_view() == int[] { 3, 2, 1, 10 }); + test.reverse(); + assert(test.array_view() == int[] { 10, 1, 2, 3 }); +} + +fn void! remove_if() +{ + IntList test; + usz removed; + + test.add_array({ 1, 11, 2, 10, 20 }); + removed = test.remove_if(&filter); + assert(removed == 3); + assert(test.array_view() == int[]{1, 2}); + + test.clear(); + test.add_array({ 1, 11, 2, 10, 20 }); + removed = test.remove_if(&select); + assert(removed == 2); + assert(test.array_view() == int[]{11, 10, 20}); +} + + +fn void! remove_using_test() +{ + IntList test; + usz removed; + + test.add_array({ 1, 11, 2, 10, 20 }); + removed = test.remove_using_test(fn bool(i, ctx) => *i >= *(int*)ctx, &&10); + assert(removed == 3); + assert(test.array_view() == int[]{1, 2}); + + test.clear(); + test.add_array({ 1, 11, 2, 10, 20 }); + removed = test.remove_using_test(fn bool(i, ctx) => *i < *(int*)ctx, &&10); + assert(removed == 2); + assert(test.array_view() == int[]{11, 10, 20}); +} + +fn void! retain_if() +{ + IntList test; + usz removed; + + test.add_array({ 1, 11, 2, 10, 20 }); + removed = test.retain_if(&select); + assert(removed == 3); + assert(test.array_view() == int[]{1, 2}); + + test.clear(); + test.add_array({ 1, 11, 2, 10, 20 }); + removed = test.retain_if(&filter); + assert(removed == 2); + assert(test.array_view() == int[]{11, 10, 20}); +} + +fn void! retain_using_test() +{ + IntList test; + usz removed; + + test.add_array({ 1, 11, 2, 10, 20 }); + removed = test.remove_using_test(fn bool(i, ctx) => *i >= *(int*)ctx, &&10); + assert(removed == 3); + assert(test.array_view() == int[]{1, 2}); + + test.clear(); + test.add_array({ 1, 11, 2, 10, 20 }); + removed = test.remove_using_test(fn bool(i, ctx) => *i < *(int*)ctx, &&10); + assert(removed == 2); + assert(test.array_view() == int[]{11, 10, 20}); +} + +module elastic_array_test; + +fn bool filter(int* i) +{ + return *i >= 10; +} + +fn bool select(int* i) +{ + return *i < 10; +} \ No newline at end of file