alist: Add a way to efficiently filter an alist

Unlike linked lists, it is inefficient to remove items from an alist,
particularly if it is large. If most items need to be removed, then the
time-complexity approaches O(n2).

Provide a way to do this efficiently, by working through the alist once
and copying elements down.

Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Simon Glass 2024-10-19 09:21:47 -06:00 committed by Tom Rini
parent 5bd4ead8bd
commit 5dfc1c8078
3 changed files with 123 additions and 0 deletions

View File

@ -274,6 +274,36 @@ bool alist_chk_ptr(const struct alist *lst, const void *ptr);
_pos < alist_end(_lst, typeof(*(_pos))); \
_pos++)
/**
* alist_for_each_filter() - version which sets up a 'from' pointer too
*
* This is used for filtering out information in the list. It works by iterating
* through the list, copying elements down over the top of elements to be
* deleted.
*
* In this example, 'from' iterates through the list from start to end,, 'to'
* also begins at the start, but only increments if the element at 'from' should
* be kept. This provides an O(n) filtering operation. Note that
* alist_update_end() must be called after the loop, to update the count.
*
* alist_for_each_filter(from, to, &lst) {
* if (from->val != 2)
* *to++ = *from;
* }
* alist_update_end(&lst, to);
*/
#define alist_for_each_filter(_pos, _from, _lst) \
for (_pos = _from = alist_start(_lst, typeof(*(_pos))); \
_pos < alist_end(_lst, typeof(*(_pos))); \
_pos++)
/**
* alist_update_end() - Set the element count based on a given pointer
*
* Set the given element as the final one
*/
void alist_update_end(struct alist *lst, const void *end);
/**
* alist_empty() - Empty an alist
*

View File

@ -123,6 +123,14 @@ int alist_calc_index(const struct alist *lst, const void *ptr)
return index;
}
void alist_update_end(struct alist *lst, const void *ptr)
{
int index;
index = alist_calc_index(lst, ptr);
lst->count = index == -1 ? 0 : index;
}
bool alist_chk_ptr(const struct alist *lst, const void *ptr)
{
int index = alist_calc_index(lst, ptr);

View File

@ -408,3 +408,88 @@ static int lib_test_alist_empty(struct unit_test_state *uts)
return 0;
}
LIB_TEST(lib_test_alist_empty, 0);
static int lib_test_alist_filter(struct unit_test_state *uts)
{
struct my_struct *from, *to, *ptr;
struct my_struct data;
struct alist lst;
ulong start;
int count;
start = ut_check_free();
ut_assert(alist_init_struct(&lst, struct my_struct));
data.val = 1;
data.other_val = 0;
alist_add(&lst, data);
data.val = 2;
alist_add(&lst, data);
data.val = 3;
alist_add(&lst, data);
ptr = lst.data;
/* filter out all values except 2 */
alist_for_each_filter(from, to, &lst) {
if (from->val != 2)
*to++ = *from;
}
alist_update_end(&lst, to);
ut_asserteq(2, lst.count);
ut_assertnonnull(lst.data);
ut_asserteq(1, alist_get(&lst, 0, struct my_struct)->val);
ut_asserteq(3, alist_get(&lst, 1, struct my_struct)->val);
ut_asserteq_ptr(ptr + 3, from);
ut_asserteq_ptr(ptr + 2, to);
/* filter out nothing */
alist_for_each_filter(from, to, &lst) {
if (from->val != 2)
*to++ = *from;
}
alist_update_end(&lst, to);
ut_asserteq_ptr(ptr + 2, from);
ut_asserteq_ptr(ptr + 2, to);
ut_asserteq(2, lst.count);
ut_assertnonnull(lst.data);
ut_asserteq(1, alist_get(&lst, 0, struct my_struct)->val);
ut_asserteq(3, alist_get(&lst, 1, struct my_struct)->val);
/* filter out everything */
alist_for_each_filter(from, to, &lst) {
if (from->val == 2)
*to++ = *from;
}
alist_update_end(&lst, to);
ut_asserteq_ptr(ptr + 2, from);
ut_asserteq_ptr(ptr, to);
/* filter out everything (nop) */
count = 0;
alist_for_each_filter(from, to, &lst) {
if (from->val == 2)
*to++ = *from;
count++;
}
alist_update_end(&lst, to);
ut_asserteq_ptr(ptr, from);
ut_asserteq_ptr(ptr, to);
ut_asserteq(0, count);
ut_asserteq(0, lst.count);
ut_assertnonnull(lst.data);
alist_uninit(&lst);
/* Check for memory leaks */
ut_assertok(ut_check_delta(start));
return 0;
}
LIB_TEST(lib_test_alist_filter, 0);