Skip to content

std: introduce orderedRemoveMany #23920

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions lib/std/array_hash_map.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1282,6 +1282,33 @@ pub fn ArrayHashMapUnmanaged(
self.removeByIndex(index, if (store_hash) {} else ctx, .ordered);
}

/// Remove the entries indexed by `sorted_indexes`. The indexes to be
/// removed correspond to state before deletion.
///
/// This operation is O(N).
///
/// Asserts that each index to be removed is in bounds.
///
/// Invalidates key and element pointers beyond the first deleted index.
pub fn orderedRemoveAtMany(self: *Self, gpa: Allocator, sorted_indexes: []const usize) Oom!void {
if (@sizeOf(ByIndexContext) != 0)
@compileError("Cannot infer context " ++ @typeName(Context) ++ ", call orderedRemoveAtContext instead.");
return self.orderedRemoveAtManyContext(gpa, sorted_indexes, undefined);
}

pub fn orderedRemoveAtManyContext(
self: *Self,
gpa: Allocator,
sorted_indexes: []const usize,
ctx: Context,
) Oom!void {
self.pointer_stability.lock();
defer self.pointer_stability.unlock();

self.entries.orderedRemoveMany(sorted_indexes);
try self.reIndexContext(gpa, ctx);
}

/// Create a copy of the hash map which can be modified separately.
/// The copy uses the same context as this instance, but is allocated
/// with the provided allocator.
Expand Down Expand Up @@ -2660,3 +2687,29 @@ pub fn getAutoHashStratFn(comptime K: type, comptime Context: type, comptime str
}
}.hash;
}

test "orderedRemoveAtMany" {
const gpa = testing.allocator;

var map: AutoArrayHashMapUnmanaged(usize, void) = .empty;
defer map.deinit(gpa);

for (0..10) |n| {
try map.put(gpa, n, {});
}

try map.orderedRemoveAtMany(gpa, &.{ 1, 5, 5, 7, 9 });
try testing.expectEqualSlices(usize, &.{ 0, 2, 3, 4, 6, 8 }, map.keys());

try map.orderedRemoveAtMany(gpa, &.{0});
try testing.expectEqualSlices(usize, &.{ 2, 3, 4, 6, 8 }, map.keys());

try map.orderedRemoveAtMany(gpa, &.{});
try testing.expectEqualSlices(usize, &.{ 2, 3, 4, 6, 8 }, map.keys());

try map.orderedRemoveAtMany(gpa, &.{ 1, 2, 3, 4 });
try testing.expectEqualSlices(usize, &.{2}, map.keys());

try map.orderedRemoveAtMany(gpa, &.{0});
try testing.expectEqualSlices(usize, &.{}, map.keys());
}
56 changes: 55 additions & 1 deletion lib/std/array_list.zig
Original file line number Diff line number Diff line change
Expand Up @@ -872,14 +872,42 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig
/// Remove the element at index `i` from the list and return its value.
/// Invalidates pointers to the last element.
/// This operation is O(N).
/// Asserts that the list is not empty.
/// Asserts that the index is in bounds.
pub fn orderedRemove(self: *Self, i: usize) T {
const old_item = self.items[i];
self.replaceRangeAssumeCapacity(i, 1, &.{});
return old_item;
}

/// Remove the elements indexed by `sorted_indexes`. The indexes to be
/// removed correspond to the array list before deletion.
///
/// Asserts:
/// * Each index to be removed is in bounds.
/// * The indexes to be removed are sorted ascending.
///
/// Duplicates in `sorted_indexes` are allowed.
///
/// This operation is O(N).
///
/// Invalidates element pointers beyond the first deleted index.
pub fn orderedRemoveMany(self: *Self, sorted_indexes: []const usize) void {
if (sorted_indexes.len == 0) return;
var shift: usize = 1;
for (sorted_indexes[0 .. sorted_indexes.len - 1], sorted_indexes[1..]) |removed, end| {
if (removed == end) continue; // allows duplicates in `sorted_indexes`
const start = removed + 1;
const len = end - start; // safety checks `sorted_indexes` are sorted
@memmove(self.items[start - shift ..][0..len], self.items[start..][0..len]); // safety checks initial `sorted_indexes` are in range
shift += 1;
}
const start = sorted_indexes[sorted_indexes.len - 1] + 1;
const end = self.items.len;
const len = end - start; // safety checks final `sorted_indexes` are in range
@memmove(self.items[start - shift ..][0..len], self.items[start..][0..len]);
self.items.len = end - shift;
}

/// Removes the element at the specified index and returns it.
/// The empty slot is filled from the end of the list.
/// Invalidates pointers to last element.
Expand Down Expand Up @@ -2248,3 +2276,29 @@ test "return OutOfMemory when capacity would exceed maximum usize integer value"
try testing.expectError(error.OutOfMemory, list.ensureUnusedCapacity(2));
}
}

test "orderedRemoveMany" {
const gpa = testing.allocator;

var list: ArrayListUnmanaged(usize) = .empty;
defer list.deinit(gpa);

for (0..10) |n| {
try list.append(gpa, n);
}

list.orderedRemoveMany(&.{ 1, 5, 5, 7, 9 });
try testing.expectEqualSlices(usize, &.{ 0, 2, 3, 4, 6, 8 }, list.items);

list.orderedRemoveMany(&.{0});
try testing.expectEqualSlices(usize, &.{ 2, 3, 4, 6, 8 }, list.items);

list.orderedRemoveMany(&.{});
try testing.expectEqualSlices(usize, &.{ 2, 3, 4, 6, 8 }, list.items);

list.orderedRemoveMany(&.{ 1, 2, 3, 4 });
try testing.expectEqualSlices(usize, &.{2}, list.items);

list.orderedRemoveMany(&.{0});
try testing.expectEqualSlices(usize, &.{}, list.items);
}
62 changes: 62 additions & 0 deletions lib/std/multi_array_list.zig
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,42 @@ pub fn MultiArrayList(comptime T: type) type {
self.len -= 1;
}

/// Remove the elements indexed by `sorted_indexes`. The indexes to be
/// removed correspond to the array list before deletion.
///
/// Asserts:
/// * Each index to be removed is in bounds.
/// * The indexes to be removed are sorted ascending.
///
/// Duplicates in `sorted_indexes` are allowed.
///
/// This operation is O(N).
///
/// Invalidates element pointers beyond the first deleted index.
pub fn orderedRemoveMany(self: *Self, sorted_indexes: []const usize) void {
if (sorted_indexes.len == 0) return;
const slices = self.slice();
var shift: usize = 1;
for (sorted_indexes[0 .. sorted_indexes.len - 1], sorted_indexes[1..]) |removed, end| {
if (removed == end) continue; // allows duplicates in `sorted_indexes`
const start = removed + 1;
const len = end - start; // safety checks `sorted_indexes` are sorted
inline for (fields, 0..) |_, field_index| {
const field_slice = slices.items(@enumFromInt(field_index));
@memmove(field_slice[start - shift ..][0..len], field_slice[start..][0..len]); // safety checks initial `sorted_indexes` are in range
}
shift += 1;
}
const start = sorted_indexes[sorted_indexes.len - 1] + 1;
const end = self.len;
const len = end - start; // safety checks final `sorted_indexes` are in range
inline for (fields, 0..) |_, field_index| {
const field_slice = slices.items(@enumFromInt(field_index));
@memmove(field_slice[start - shift ..][0..len], field_slice[start..][0..len]);
}
self.len = end - shift;
}

/// Adjust the list's length to `new_len`.
/// Does not initialize added items, if any.
pub fn resize(self: *Self, gpa: Allocator, new_len: usize) !void {
Expand Down Expand Up @@ -1008,3 +1044,29 @@ test "struct with many fields" {
try ManyFields.doTest(testing.allocator, 100);
try ManyFields.doTest(testing.allocator, 200);
}

test "orderedRemoveMany" {
const gpa = testing.allocator;

var list: MultiArrayList(struct { x: usize }) = .empty;
defer list.deinit(gpa);

for (0..10) |n| {
try list.append(gpa, .{ .x = n });
}

list.orderedRemoveMany(&.{ 1, 5, 5, 7, 9 });
try testing.expectEqualSlices(usize, &.{ 0, 2, 3, 4, 6, 8 }, list.items(.x));

list.orderedRemoveMany(&.{0});
try testing.expectEqualSlices(usize, &.{ 2, 3, 4, 6, 8 }, list.items(.x));

list.orderedRemoveMany(&.{});
try testing.expectEqualSlices(usize, &.{ 2, 3, 4, 6, 8 }, list.items(.x));

list.orderedRemoveMany(&.{ 1, 2, 3, 4 });
try testing.expectEqualSlices(usize, &.{2}, list.items(.x));

list.orderedRemoveMany(&.{0});
try testing.expectEqualSlices(usize, &.{}, list.items(.x));
}
Loading