| author | |
| committer | |
| log | 59de7e3a5725f0c4eeb64bc464db85e019af7446 |
| tree | 02f14fd8a1601993ea41473f29ed4ef05f20e122 |
| parent | 282c3575b16499518b19ee9b1a8364c9379dab94 |
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
* MultiArrayList3 files changed, 170 insertions(+), 1 deletions(-)
lib/std/array_hash_map.zig+53| ... | @@ -1273,6 +1273,33 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -1273,6 +1273,33 @@ pub fn ArrayHashMapUnmanaged( |
| 1273 | self.removeByIndex(index, if (store_hash) {} else ctx, .ordered); | 1273 | self.removeByIndex(index, if (store_hash) {} else ctx, .ordered); |
| 1274 | } | 1274 | } |
| 1275 | 1275 | ||
| 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 | |||
| 1276 | /// Create a copy of the hash map which can be modified separately. | 1303 | /// Create a copy of the hash map which can be modified separately. |
| 1277 | /// The copy uses the same context as this instance, but is allocated | 1304 | /// The copy uses the same context as this instance, but is allocated |
| 1278 | /// with the provided allocator. | 1305 | /// with the provided allocator. |
| ... | @@ -2651,3 +2678,29 @@ pub fn getAutoHashStratFn(comptime K: type, comptime Context: type, comptime str | ... | @@ -2651,3 +2678,29 @@ pub fn getAutoHashStratFn(comptime K: type, comptime Context: type, comptime str |
| 2651 | } | 2678 | } |
| 2652 | }.hash; | 2679 | }.hash; |
| 2653 | } | 2680 | } |
| 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+55-1| ... | @@ -935,7 +935,6 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig | ... | @@ -935,7 +935,6 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig |
| 935 | /// Remove the element at index `i` from the list and return its value. | 935 | /// Remove the element at index `i` from the list and return its value. |
| 936 | /// Invalidates pointers to the last element. | 936 | /// Invalidates pointers to the last element. |
| 937 | /// This operation is O(N). | 937 | /// This operation is O(N). |
| 938 | /// Asserts that the list is not empty. | ||
| 939 | /// Asserts that the index is in bounds. | 938 | /// Asserts that the index is in bounds. |
| 940 | pub fn orderedRemove(self: *Self, i: usize) T { | 939 | pub fn orderedRemove(self: *Self, i: usize) T { |
| 941 | const old_item = self.items[i]; | 940 | const old_item = self.items[i]; |
| ... | @@ -943,6 +942,35 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig | ... | @@ -943,6 +942,35 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig |
| 943 | return old_item; | 942 | return old_item; |
| 944 | } | 943 | } |
| 945 | 944 | ||
| 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 | |||
| 946 | /// Removes the element at the specified index and returns it. | 974 | /// Removes the element at the specified index and returns it. |
| 947 | /// The empty slot is filled from the end of the list. | 975 | /// The empty slot is filled from the end of the list. |
| 948 | /// Invalidates pointers to last element. | 976 | /// Invalidates pointers to last element. |
| ... | @@ -2425,3 +2453,29 @@ test "return OutOfMemory when capacity would exceed maximum usize integer value" | ... | @@ -2425,3 +2453,29 @@ test "return OutOfMemory when capacity would exceed maximum usize integer value" |
| 2425 | try testing.expectError(error.OutOfMemory, list.ensureUnusedCapacity(2)); | 2453 | try testing.expectError(error.OutOfMemory, list.ensureUnusedCapacity(2)); |
| 2426 | } | 2454 | } |
| 2427 | } | 2455 | } |
| 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+62| ... | @@ -350,6 +350,42 @@ pub fn MultiArrayList(comptime T: type) type { | ... | @@ -350,6 +350,42 @@ pub fn MultiArrayList(comptime T: type) type { |
| 350 | self.len -= 1; | 350 | self.len -= 1; |
| 351 | } | 351 | } |
| 352 | 352 | ||
| 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 | |||
| 353 | /// Adjust the list's length to `new_len`. | 389 | /// Adjust the list's length to `new_len`. |
| 354 | /// Does not initialize added items, if any. | 390 | /// Does not initialize added items, if any. |
| 355 | pub fn resize(self: *Self, gpa: Allocator, new_len: usize) !void { | 391 | pub fn resize(self: *Self, gpa: Allocator, new_len: usize) !void { |
| ... | @@ -1025,3 +1061,29 @@ test "struct with many fields" { | ... | @@ -1025,3 +1061,29 @@ test "struct with many fields" { |
| 1025 | try ManyFields.doTest(testing.allocator, 100); | 1061 | try ManyFields.doTest(testing.allocator, 100); |
| 1026 | try ManyFields.doTest(testing.allocator, 200); | 1062 | try ManyFields.doTest(testing.allocator, 200); |
| 1027 | } | 1063 | } |
| 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 | } |