Skip to content

Commit 59de7e3

Browse files
committed
std: introduce orderedRemoveMany
This algorithm is non-trivial and makes sense for any data structure that acts as an array list, so I thought it would make sense as a method. I have a real world case for this in a music player application (deleting queue items). Adds the method to: * ArrayList * ArrayHashMap * MultiArrayList
1 parent 282c357 commit 59de7e3

File tree

3 files changed

+170
-1
lines changed

3 files changed

+170
-1
lines changed

lib/std/array_hash_map.zig

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1273,6 +1273,33 @@ pub fn ArrayHashMapUnmanaged(
12731273
self.removeByIndex(index, if (store_hash) {} else ctx, .ordered);
12741274
}
12751275

1276+
/// Remove the entries indexed by `sorted_indexes`. The indexes to be
1277+
/// removed correspond to state before deletion.
1278+
///
1279+
/// This operation is O(N).
1280+
///
1281+
/// Asserts that each index to be removed is in bounds.
1282+
///
1283+
/// Invalidates key and element pointers beyond the first deleted index.
1284+
pub fn orderedRemoveAtMany(self: *Self, gpa: Allocator, sorted_indexes: []const usize) Oom!void {
1285+
if (@sizeOf(ByIndexContext) != 0)
1286+
@compileError("Cannot infer context " ++ @typeName(Context) ++ ", call orderedRemoveAtContext instead.");
1287+
return self.orderedRemoveAtManyContext(gpa, sorted_indexes, undefined);
1288+
}
1289+
1290+
pub fn orderedRemoveAtManyContext(
1291+
self: *Self,
1292+
gpa: Allocator,
1293+
sorted_indexes: []const usize,
1294+
ctx: Context,
1295+
) Oom!void {
1296+
self.pointer_stability.lock();
1297+
defer self.pointer_stability.unlock();
1298+
1299+
self.entries.orderedRemoveMany(sorted_indexes);
1300+
try self.reIndexContext(gpa, ctx);
1301+
}
1302+
12761303
/// Create a copy of the hash map which can be modified separately.
12771304
/// The copy uses the same context as this instance, but is allocated
12781305
/// with the provided allocator.
@@ -2651,3 +2678,29 @@ pub fn getAutoHashStratFn(comptime K: type, comptime Context: type, comptime str
26512678
}
26522679
}.hash;
26532680
}
2681+
2682+
test "orderedRemoveAtMany" {
2683+
const gpa = testing.allocator;
2684+
2685+
var map: AutoArrayHashMapUnmanaged(usize, void) = .empty;
2686+
defer map.deinit(gpa);
2687+
2688+
for (0..10) |n| {
2689+
try map.put(gpa, n, {});
2690+
}
2691+
2692+
try map.orderedRemoveAtMany(gpa, &.{ 1, 5, 5, 7, 9 });
2693+
try testing.expectEqualSlices(usize, &.{ 0, 2, 3, 4, 6, 8 }, map.keys());
2694+
2695+
try map.orderedRemoveAtMany(gpa, &.{0});
2696+
try testing.expectEqualSlices(usize, &.{ 2, 3, 4, 6, 8 }, map.keys());
2697+
2698+
try map.orderedRemoveAtMany(gpa, &.{});
2699+
try testing.expectEqualSlices(usize, &.{ 2, 3, 4, 6, 8 }, map.keys());
2700+
2701+
try map.orderedRemoveAtMany(gpa, &.{ 1, 2, 3, 4 });
2702+
try testing.expectEqualSlices(usize, &.{2}, map.keys());
2703+
2704+
try map.orderedRemoveAtMany(gpa, &.{0});
2705+
try testing.expectEqualSlices(usize, &.{}, map.keys());
2706+
}

lib/std/array_list.zig

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -935,14 +935,42 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig
935935
/// Remove the element at index `i` from the list and return its value.
936936
/// Invalidates pointers to the last element.
937937
/// This operation is O(N).
938-
/// Asserts that the list is not empty.
939938
/// Asserts that the index is in bounds.
940939
pub fn orderedRemove(self: *Self, i: usize) T {
941940
const old_item = self.items[i];
942941
self.replaceRangeAssumeCapacity(i, 1, &.{});
943942
return old_item;
944943
}
945944

945+
/// Remove the elements indexed by `sorted_indexes`. The indexes to be
946+
/// removed correspond to the array list before deletion.
947+
///
948+
/// Asserts:
949+
/// * Each index to be removed is in bounds.
950+
/// * The indexes to be removed are sorted ascending.
951+
///
952+
/// Duplicates in `sorted_indexes` are allowed.
953+
///
954+
/// This operation is O(N).
955+
///
956+
/// Invalidates element pointers beyond the first deleted index.
957+
pub fn orderedRemoveMany(self: *Self, sorted_indexes: []const usize) void {
958+
if (sorted_indexes.len == 0) return;
959+
var shift: usize = 1;
960+
for (sorted_indexes[0 .. sorted_indexes.len - 1], sorted_indexes[1..]) |removed, end| {
961+
if (removed == end) continue; // allows duplicates in `sorted_indexes`
962+
const start = removed + 1;
963+
const len = end - start; // safety checks `sorted_indexes` are sorted
964+
@memmove(self.items[start - shift ..][0..len], self.items[start..][0..len]); // safety checks initial `sorted_indexes` are in range
965+
shift += 1;
966+
}
967+
const start = sorted_indexes[sorted_indexes.len - 1] + 1;
968+
const end = self.items.len;
969+
const len = end - start; // safety checks final `sorted_indexes` are in range
970+
@memmove(self.items[start - shift ..][0..len], self.items[start..][0..len]);
971+
self.items.len = end - shift;
972+
}
973+
946974
/// Removes the element at the specified index and returns it.
947975
/// The empty slot is filled from the end of the list.
948976
/// Invalidates pointers to last element.
@@ -2425,3 +2453,29 @@ test "return OutOfMemory when capacity would exceed maximum usize integer value"
24252453
try testing.expectError(error.OutOfMemory, list.ensureUnusedCapacity(2));
24262454
}
24272455
}
2456+
2457+
test "orderedRemoveMany" {
2458+
const gpa = testing.allocator;
2459+
2460+
var list: ArrayListUnmanaged(usize) = .empty;
2461+
defer list.deinit(gpa);
2462+
2463+
for (0..10) |n| {
2464+
try list.append(gpa, n);
2465+
}
2466+
2467+
list.orderedRemoveMany(&.{ 1, 5, 5, 7, 9 });
2468+
try testing.expectEqualSlices(usize, &.{ 0, 2, 3, 4, 6, 8 }, list.items);
2469+
2470+
list.orderedRemoveMany(&.{0});
2471+
try testing.expectEqualSlices(usize, &.{ 2, 3, 4, 6, 8 }, list.items);
2472+
2473+
list.orderedRemoveMany(&.{});
2474+
try testing.expectEqualSlices(usize, &.{ 2, 3, 4, 6, 8 }, list.items);
2475+
2476+
list.orderedRemoveMany(&.{ 1, 2, 3, 4 });
2477+
try testing.expectEqualSlices(usize, &.{2}, list.items);
2478+
2479+
list.orderedRemoveMany(&.{0});
2480+
try testing.expectEqualSlices(usize, &.{}, list.items);
2481+
}

lib/std/multi_array_list.zig

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,42 @@ pub fn MultiArrayList(comptime T: type) type {
350350
self.len -= 1;
351351
}
352352

353+
/// Remove the elements indexed by `sorted_indexes`. The indexes to be
354+
/// removed correspond to the array list before deletion.
355+
///
356+
/// Asserts:
357+
/// * Each index to be removed is in bounds.
358+
/// * The indexes to be removed are sorted ascending.
359+
///
360+
/// Duplicates in `sorted_indexes` are allowed.
361+
///
362+
/// This operation is O(N).
363+
///
364+
/// Invalidates element pointers beyond the first deleted index.
365+
pub fn orderedRemoveMany(self: *Self, sorted_indexes: []const usize) void {
366+
if (sorted_indexes.len == 0) return;
367+
const slices = self.slice();
368+
var shift: usize = 1;
369+
for (sorted_indexes[0 .. sorted_indexes.len - 1], sorted_indexes[1..]) |removed, end| {
370+
if (removed == end) continue; // allows duplicates in `sorted_indexes`
371+
const start = removed + 1;
372+
const len = end - start; // safety checks `sorted_indexes` are sorted
373+
inline for (fields, 0..) |_, field_index| {
374+
const field_slice = slices.items(@enumFromInt(field_index));
375+
@memmove(field_slice[start - shift ..][0..len], field_slice[start..][0..len]); // safety checks initial `sorted_indexes` are in range
376+
}
377+
shift += 1;
378+
}
379+
const start = sorted_indexes[sorted_indexes.len - 1] + 1;
380+
const end = self.len;
381+
const len = end - start; // safety checks final `sorted_indexes` are in range
382+
inline for (fields, 0..) |_, field_index| {
383+
const field_slice = slices.items(@enumFromInt(field_index));
384+
@memmove(field_slice[start - shift ..][0..len], field_slice[start..][0..len]);
385+
}
386+
self.len = end - shift;
387+
}
388+
353389
/// Adjust the list's length to `new_len`.
354390
/// Does not initialize added items, if any.
355391
pub fn resize(self: *Self, gpa: Allocator, new_len: usize) !void {
@@ -1025,3 +1061,29 @@ test "struct with many fields" {
10251061
try ManyFields.doTest(testing.allocator, 100);
10261062
try ManyFields.doTest(testing.allocator, 200);
10271063
}
1064+
1065+
test "orderedRemoveMany" {
1066+
const gpa = testing.allocator;
1067+
1068+
var list: MultiArrayList(struct { x: usize }) = .empty;
1069+
defer list.deinit(gpa);
1070+
1071+
for (0..10) |n| {
1072+
try list.append(gpa, .{ .x = n });
1073+
}
1074+
1075+
list.orderedRemoveMany(&.{ 1, 5, 5, 7, 9 });
1076+
try testing.expectEqualSlices(usize, &.{ 0, 2, 3, 4, 6, 8 }, list.items(.x));
1077+
1078+
list.orderedRemoveMany(&.{0});
1079+
try testing.expectEqualSlices(usize, &.{ 2, 3, 4, 6, 8 }, list.items(.x));
1080+
1081+
list.orderedRemoveMany(&.{});
1082+
try testing.expectEqualSlices(usize, &.{ 2, 3, 4, 6, 8 }, list.items(.x));
1083+
1084+
list.orderedRemoveMany(&.{ 1, 2, 3, 4 });
1085+
try testing.expectEqualSlices(usize, &.{2}, list.items(.x));
1086+
1087+
list.orderedRemoveMany(&.{0});
1088+
try testing.expectEqualSlices(usize, &.{}, list.items(.x));
1089+
}

0 commit comments

Comments
 (0)