zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit 17f83ace03a3bcb4f42958b43527792c186cd498 (tree)
parent 229800482d28c9d55abf116cb315c78adddabe11
Author: mlugg <mlugg@mlugg.co.uk>
Date:   Thu,  7 Mar 2024 15:49:11 +0000

std.enums: remove IndexedMap, IndexedArray, IndexedSet

Searching GitHub indicated that the only use of these types in the wild is
support in getty-zig, and testing for that support. This eliminates 3 more uses
of usingnamespace from the standard library, and removes some unnecessarily
complex generic code.

Diffstat:
Mlib/std/enums.zig | 1534+++++++++++++++++++++++++++++++++++++------------------------------------------
1 file changed, 715 insertions(+), 819 deletions(-)

diff --git a/lib/std/enums.zig b/lib/std/enums.zig @@ -241,957 +241,794 @@ test nameCast { /// to dense indices. This type does no dynamic allocation and /// can be copied by value. pub fn EnumSet(comptime E: type) type { - const mixin = struct { - fn EnumSetExt(comptime Self: type) type { - const Indexer = Self.Indexer; - return struct { - /// Initializes the set using a struct of bools - pub fn init(init_values: EnumFieldStruct(E, bool, false)) Self { - var result = Self{}; - comptime var i: usize = 0; - inline while (i < Self.len) : (i += 1) { - const key = comptime Indexer.keyForIndex(i); - const tag = comptime @tagName(key); - if (@field(init_values, tag)) { - result.bits.set(i); - } - } - return result; - } - }; - } - }; - return IndexedSet(EnumIndexer(E), mixin.EnumSetExt); -} + return struct { + const Self = @This(); -/// A map keyed by an enum, backed by a bitfield and a dense array. -/// If the enum is not dense, a mapping will be constructed from -/// enum values to dense indices. This type does no dynamic -/// allocation and can be copied by value. -pub fn EnumMap(comptime E: type, comptime V: type) type { - const mixin = struct { - fn EnumMapExt(comptime Self: type) type { - const Indexer = Self.Indexer; - return struct { - /// Initializes the map using a sparse struct of optionals - pub fn init(init_values: EnumFieldStruct(E, ?V, @as(?V, null))) Self { - var result = Self{}; - comptime var i: usize = 0; - inline while (i < Self.len) : (i += 1) { - const key = comptime Indexer.keyForIndex(i); - const tag = comptime @tagName(key); - if (@field(init_values, tag)) |*v| { - result.bits.set(i); - result.values[i] = v.*; - } - } - return result; - } - /// Initializes a full mapping with all keys set to value. - /// Consider using EnumArray instead if the map will remain full. - pub fn initFull(value: V) Self { - var result = Self{ - .bits = Self.BitSet.initFull(), - .values = undefined, - }; - @memset(&result.values, value); - return result; - } - /// Initializes a full mapping with supplied values. - /// Consider using EnumArray instead if the map will remain full. - pub fn initFullWith(init_values: EnumFieldStruct(E, V, @as(?V, null))) Self { - return initFullWithDefault(@as(?V, null), init_values); - } - /// Initializes a full mapping with a provided default. - /// Consider using EnumArray instead if the map will remain full. - pub fn initFullWithDefault(comptime default: ?V, init_values: EnumFieldStruct(E, V, default)) Self { - var result = Self{ - .bits = Self.BitSet.initFull(), - .values = undefined, - }; - comptime var i: usize = 0; - inline while (i < Self.len) : (i += 1) { - const key = comptime Indexer.keyForIndex(i); - const tag = comptime @tagName(key); - result.values[i] = @field(init_values, tag); - } - return result; - } - }; - } - }; - return IndexedMap(EnumIndexer(E), V, mixin.EnumMapExt); -} + /// The indexing rules for converting between keys and indices. + pub const Indexer = EnumIndexer(E); + /// The element type for this set. + pub const Key = Indexer.Key; -/// A multiset of enum elements up to a count of usize. Backed -/// by an EnumArray. This type does no dynamic allocation and can -/// be copied by value. -pub fn EnumMultiset(comptime E: type) type { - return BoundedEnumMultiset(E, usize); -} + const BitSet = std.StaticBitSet(Indexer.count); -/// A multiset of enum elements up to CountSize. Backed by an -/// EnumArray. This type does no dynamic allocation and can be -/// copied by value. -pub fn BoundedEnumMultiset(comptime E: type, comptime CountSize: type) type { - return struct { - const Self = @This(); + /// The maximum number of items in this set. + pub const len = Indexer.count; - counts: EnumArray(E, CountSize), + bits: BitSet = BitSet.initEmpty(), - /// Initializes the multiset using a struct of counts. - pub fn init(init_counts: EnumFieldStruct(E, CountSize, 0)) Self { - var self = initWithCount(0); - inline for (@typeInfo(E).Enum.fields) |field| { - const c = @field(init_counts, field.name); - const key = @as(E, @enumFromInt(field.value)); - self.counts.set(key, c); + /// Initializes the set using a struct of bools + pub fn init(init_values: EnumFieldStruct(E, bool, false)) Self { + var result: Self = .{}; + inline for (0..Self.len) |i| { + const key = comptime Indexer.keyForIndex(i); + const tag = @tagName(key); + if (@field(init_values, tag)) { + result.bits.set(i); + } } - return self; + return result; } - /// Initializes the multiset with a count of zero. + /// Returns a set containing no keys. pub fn initEmpty() Self { - return initWithCount(0); + return .{ .bits = BitSet.initEmpty() }; } - /// Initializes the multiset with all keys at the - /// same count. - pub fn initWithCount(comptime c: CountSize) Self { - return .{ - .counts = EnumArray(E, CountSize).initDefault(c, .{}), - }; + /// Returns a set containing all possible keys. + pub fn initFull() Self { + return .{ .bits = BitSet.initFull() }; } - /// Returns the total number of key counts in the multiset. - pub fn count(self: Self) usize { - var sum: usize = 0; - for (self.counts.values) |c| { - sum += c; - } - return sum; + /// Returns a set containing multiple keys. + pub fn initMany(keys: []const Key) Self { + var set = initEmpty(); + for (keys) |key| set.insert(key); + return set; } - /// Checks if at least one key in multiset. - pub fn contains(self: Self, key: E) bool { - return self.counts.get(key) > 0; + /// Returns a set containing a single key. + pub fn initOne(key: Key) Self { + return initMany(&[_]Key{key}); } - /// Removes all instance of a key from multiset. Same as - /// setCount(key, 0). - pub fn removeAll(self: *Self, key: E) void { - return self.counts.set(key, 0); + /// Returns the number of keys in the set. + pub fn count(self: Self) usize { + return self.bits.count(); } - /// Increases the key count by given amount. Caller asserts - /// operation will not overflow. - pub fn addAssertSafe(self: *Self, key: E, c: CountSize) void { - self.counts.getPtr(key).* += c; + /// Checks if a key is in the set. + pub fn contains(self: Self, key: Key) bool { + return self.bits.isSet(Indexer.indexOf(key)); } - /// Increases the key count by given amount. - pub fn add(self: *Self, key: E, c: CountSize) error{Overflow}!void { - self.counts.set(key, try std.math.add(CountSize, self.counts.get(key), c)); + /// Puts a key in the set. + pub fn insert(self: *Self, key: Key) void { + self.bits.set(Indexer.indexOf(key)); } - /// Decreases the key count by given amount. If amount is - /// greater than the number of keys in multset, then key count - /// will be set to zero. - pub fn remove(self: *Self, key: E, c: CountSize) void { - self.counts.getPtr(key).* -= @min(self.getCount(key), c); + /// Removes a key from the set. + pub fn remove(self: *Self, key: Key) void { + self.bits.unset(Indexer.indexOf(key)); } - /// Returns the count for a key. - pub fn getCount(self: Self, key: E) CountSize { - return self.counts.get(key); + /// Changes the presence of a key in the set to match the passed bool. + pub fn setPresent(self: *Self, key: Key, present: bool) void { + self.bits.setValue(Indexer.indexOf(key), present); } - /// Set the count for a key. - pub fn setCount(self: *Self, key: E, c: CountSize) void { - self.counts.set(key, c); + /// Toggles the presence of a key in the set. If the key is in + /// the set, removes it. Otherwise adds it. + pub fn toggle(self: *Self, key: Key) void { + self.bits.toggle(Indexer.indexOf(key)); } - /// Increases the all key counts by given multiset. Caller - /// asserts operation will not overflow any key. - pub fn addSetAssertSafe(self: *Self, other: Self) void { - inline for (@typeInfo(E).Enum.fields) |field| { - const key = @as(E, @enumFromInt(field.value)); - self.addAssertSafe(key, other.getCount(key)); - } + /// Toggles the presence of all keys in the passed set. + pub fn toggleSet(self: *Self, other: Self) void { + self.bits.toggleSet(other.bits); } - /// Increases the all key counts by given multiset. - pub fn addSet(self: *Self, other: Self) error{Overflow}!void { - inline for (@typeInfo(E).Enum.fields) |field| { - const key = @as(E, @enumFromInt(field.value)); - try self.add(key, other.getCount(key)); - } + /// Toggles all possible keys in the set. + pub fn toggleAll(self: *Self) void { + self.bits.toggleAll(); } - /// Decreases the all key counts by given multiset. If - /// the given multiset has more key counts than this, - /// then that key will have a key count of zero. - pub fn removeSet(self: *Self, other: Self) void { - inline for (@typeInfo(E).Enum.fields) |field| { - const key = @as(E, @enumFromInt(field.value)); - self.remove(key, other.getCount(key)); - } + /// Adds all keys in the passed set to this set. + pub fn setUnion(self: *Self, other: Self) void { + self.bits.setUnion(other.bits); } - /// Returns true iff all key counts are the same as - /// given multiset. + /// Removes all keys which are not in the passed set. + pub fn setIntersection(self: *Self, other: Self) void { + self.bits.setIntersection(other.bits); + } + + /// Returns true iff both sets have the same keys. pub fn eql(self: Self, other: Self) bool { - inline for (@typeInfo(E).Enum.fields) |field| { - const key = @as(E, @enumFromInt(field.value)); - if (self.getCount(key) != other.getCount(key)) { - return false; - } - } - return true; + return self.bits.eql(other.bits); } - /// Returns true iff all key counts less than or - /// equal to the given multiset. + /// Returns true iff all the keys in this set are + /// in the other set. The other set may have keys + /// not found in this set. pub fn subsetOf(self: Self, other: Self) bool { - inline for (@typeInfo(E).Enum.fields) |field| { - const key = @as(E, @enumFromInt(field.value)); - if (self.getCount(key) > other.getCount(key)) { - return false; - } - } - return true; + return self.bits.subsetOf(other.bits); } - /// Returns true iff all key counts greater than or - /// equal to the given multiset. + /// Returns true iff this set contains all the keys + /// in the other set. This set may have keys not + /// found in the other set. pub fn supersetOf(self: Self, other: Self) bool { - inline for (@typeInfo(E).Enum.fields) |field| { - const key = @as(E, @enumFromInt(field.value)); - if (self.getCount(key) < other.getCount(key)) { - return false; - } - } - return true; + return self.bits.supersetOf(other.bits); } - /// Returns a multiset with the total key count of this - /// multiset and the other multiset. Caller asserts - /// operation will not overflow any key. - pub fn plusAssertSafe(self: Self, other: Self) Self { - var result = self; - result.addSetAssertSafe(other); - return result; + /// Returns a set with all the keys not in this set. + pub fn complement(self: Self) Self { + return .{ .bits = self.bits.complement() }; } - /// Returns a multiset with the total key count of this - /// multiset and the other multiset. - pub fn plus(self: Self, other: Self) error{Overflow}!Self { - var result = self; - try result.addSet(other); - return result; + /// Returns a set with keys that are in either this + /// set or the other set. + pub fn unionWith(self: Self, other: Self) Self { + return .{ .bits = self.bits.unionWith(other.bits) }; } - /// Returns a multiset with the key count of this - /// multiset minus the corresponding key count in the - /// other multiset. If the other multiset contains - /// more key count than this set, that key will have - /// a count of zero. - pub fn minus(self: Self, other: Self) Self { - var result = self; - result.removeSet(other); - return result; + /// Returns a set with keys that are in both this + /// set and the other set. + pub fn intersectWith(self: Self, other: Self) Self { + return .{ .bits = self.bits.intersectWith(other.bits) }; } - pub const Entry = EnumArray(E, CountSize).Entry; - pub const Iterator = EnumArray(E, CountSize).Iterator; - - /// Returns an iterator over this multiset. Keys with zero - /// counts are included. Modifications to the set during - /// iteration may or may not be observed by the iterator, - /// but will not invalidate it. - pub fn iterator(self: *Self) Iterator { - return self.counts.iterator(); + /// Returns a set with keys that are in either this + /// set or the other set, but not both. + pub fn xorWith(self: Self, other: Self) Self { + return .{ .bits = self.bits.xorWith(other.bits) }; } - }; -} -test EnumMultiset { - const Ball = enum { red, green, blue }; + /// Returns a set with keys that are in this set + /// except for keys in the other set. + pub fn differenceWith(self: Self, other: Self) Self { + return .{ .bits = self.bits.differenceWith(other.bits) }; + } - const empty = EnumMultiset(Ball).initEmpty(); - const r0_g1_b2 = EnumMultiset(Ball).init(.{ - .red = 0, - .green = 1, - .blue = 2, - }); - const ten_of_each = EnumMultiset(Ball).initWithCount(10); - - try testing.expectEqual(empty.count(), 0); - try testing.expectEqual(r0_g1_b2.count(), 3); - try testing.expectEqual(ten_of_each.count(), 30); - - try testing.expect(!empty.contains(.red)); - try testing.expect(!empty.contains(.green)); - try testing.expect(!empty.contains(.blue)); - - try testing.expect(!r0_g1_b2.contains(.red)); - try testing.expect(r0_g1_b2.contains(.green)); - try testing.expect(r0_g1_b2.contains(.blue)); - - try testing.expect(ten_of_each.contains(.red)); - try testing.expect(ten_of_each.contains(.green)); - try testing.expect(ten_of_each.contains(.blue)); - - { - var copy = ten_of_each; - copy.removeAll(.red); - try testing.expect(!copy.contains(.red)); - - // removeAll second time does nothing - copy.removeAll(.red); - try testing.expect(!copy.contains(.red)); - } + /// Returns an iterator over this set, which iterates in + /// index order. Modifications to the set during iteration + /// may or may not be observed by the iterator, but will + /// not invalidate it. + pub fn iterator(self: *const Self) Iterator { + return .{ .inner = self.bits.iterator(.{}) }; + } - { - var copy = ten_of_each; - copy.addAssertSafe(.red, 6); - try testing.expectEqual(copy.getCount(.red), 16); - } + pub const Iterator = struct { + inner: BitSet.Iterator(.{}), - { - var copy = ten_of_each; - try copy.add(.red, 6); - try testing.expectEqual(copy.getCount(.red), 16); + pub fn next(self: *Iterator) ?Key { + return if (self.inner.next()) |index| + Indexer.keyForIndex(index) + else + null; + } + }; + }; +} - try testing.expectError(error.Overflow, copy.add(.red, std.math.maxInt(usize))); - } +/// A map keyed by an enum, backed by a bitfield and a dense array. +/// If the enum is not dense, a mapping will be constructed from +/// enum values to dense indices. This type does no dynamic +/// allocation and can be copied by value. +pub fn EnumMap(comptime E: type, comptime V: type) type { + return struct { + const Self = @This(); - { - var copy = ten_of_each; - copy.remove(.red, 4); - try testing.expectEqual(copy.getCount(.red), 6); + /// The index mapping for this map + pub const Indexer = EnumIndexer(E); + /// The key type used to index this map + pub const Key = Indexer.Key; + /// The value type stored in this map + pub const Value = V; + /// The number of possible keys in the map + pub const len = Indexer.count; - // subtracting more it contains does not underflow - copy.remove(.green, 14); - try testing.expectEqual(copy.getCount(.green), 0); - } + const BitSet = std.StaticBitSet(Indexer.count); - try testing.expectEqual(empty.getCount(.green), 0); - try testing.expectEqual(r0_g1_b2.getCount(.green), 1); - try testing.expectEqual(ten_of_each.getCount(.green), 10); + /// Bits determining whether items are in the map + bits: BitSet = BitSet.initEmpty(), + /// Values of items in the map. If the associated + /// bit is zero, the value is undefined. + values: [Indexer.count]Value = undefined, - { - var copy = empty; - copy.setCount(.red, 6); - try testing.expectEqual(copy.getCount(.red), 6); - } + /// Initializes the map using a sparse struct of optionals + pub fn init(init_values: EnumFieldStruct(E, ?Value, null)) Self { + var result: Self = .{}; + inline for (0..Self.len) |i| { + const key = comptime Indexer.keyForIndex(i); + const tag = @tagName(key); + if (@field(init_values, tag)) |*v| { + result.bits.set(i); + result.values[i] = v.*; + } + } + } - { - var copy = r0_g1_b2; - copy.addSetAssertSafe(ten_of_each); - try testing.expectEqual(copy.getCount(.red), 10); - try testing.expectEqual(copy.getCount(.green), 11); - try testing.expectEqual(copy.getCount(.blue), 12); - } + /// Initializes a full mapping with all keys set to value. + /// Consider using EnumArray instead if the map will remain full. + pub fn initFull(value: Value) Self { + var result: Self = .{ + .bits = Self.BitSet.initFull(), + .values = undefined, + }; + @memset(&result.values, value); + return result; + } - { - var copy = r0_g1_b2; - try copy.addSet(ten_of_each); - try testing.expectEqual(copy.getCount(.red), 10); - try testing.expectEqual(copy.getCount(.green), 11); - try testing.expectEqual(copy.getCount(.blue), 12); + /// Initializes a full mapping with supplied values. + /// Consider using EnumArray instead if the map will remain full. + pub fn initFullWith(init_values: EnumFieldStruct(E, Value, null)) Self { + return initFullWithDefault(null, init_values); + } - const full = EnumMultiset(Ball).initWithCount(std.math.maxInt(usize)); - try testing.expectError(error.Overflow, copy.addSet(full)); - } + /// Initializes a full mapping with a provided default. + /// Consider using EnumArray instead if the map will remain full. + pub fn initFullWithDefault(comptime default: ?Value, init_values: EnumFieldStruct(E, Value, default)) Self { + var result: Self = .{ + .bits = Self.BitSet.initFull(), + .values = undefined, + }; + inline for (0..Self.len) |i| { + const key = comptime Indexer.keyForIndex(i); + const tag = @tagName(key); + result.values[i] = @field(init_values, tag); + } + return result; + } - { - var copy = ten_of_each; - copy.removeSet(r0_g1_b2); - try testing.expectEqual(copy.getCount(.red), 10); - try testing.expectEqual(copy.getCount(.green), 9); - try testing.expectEqual(copy.getCount(.blue), 8); + /// The number of items in the map. + pub fn count(self: Self) usize { + return self.bits.count(); + } - copy.removeSet(ten_of_each); - try testing.expectEqual(copy.getCount(.red), 0); - try testing.expectEqual(copy.getCount(.green), 0); - try testing.expectEqual(copy.getCount(.blue), 0); - } + /// Checks if the map contains an item. + pub fn contains(self: Self, key: Key) bool { + return self.bits.isSet(Indexer.indexOf(key)); + } - try testing.expect(empty.eql(empty)); - try testing.expect(r0_g1_b2.eql(r0_g1_b2)); - try testing.expect(ten_of_each.eql(ten_of_each)); - try testing.expect(!empty.eql(r0_g1_b2)); - try testing.expect(!r0_g1_b2.eql(ten_of_each)); - try testing.expect(!ten_of_each.eql(empty)); + /// Gets the value associated with a key. + /// If the key is not in the map, returns null. + pub fn get(self: Self, key: Key) ?Value { + const index = Indexer.indexOf(key); + return if (self.bits.isSet(index)) self.values[index] else null; + } - try testing.expect(empty.subsetOf(empty)); - try testing.expect(r0_g1_b2.subsetOf(r0_g1_b2)); - try testing.expect(empty.subsetOf(r0_g1_b2)); - try testing.expect(r0_g1_b2.subsetOf(ten_of_each)); - try testing.expect(!ten_of_each.subsetOf(r0_g1_b2)); - try testing.expect(!r0_g1_b2.subsetOf(empty)); + /// Gets the value associated with a key, which must + /// exist in the map. + pub fn getAssertContains(self: Self, key: Key) Value { + const index = Indexer.indexOf(key); + assert(self.bits.isSet(index)); + return self.values[index]; + } - try testing.expect(empty.supersetOf(empty)); - try testing.expect(r0_g1_b2.supersetOf(r0_g1_b2)); - try testing.expect(r0_g1_b2.supersetOf(empty)); - try testing.expect(ten_of_each.supersetOf(r0_g1_b2)); - try testing.expect(!r0_g1_b2.supersetOf(ten_of_each)); - try testing.expect(!empty.supersetOf(r0_g1_b2)); + /// Gets the address of the value associated with a key. + /// If the key is not in the map, returns null. + pub fn getPtr(self: *Self, key: Key) ?*Value { + const index = Indexer.indexOf(key); + return if (self.bits.isSet(index)) &self.values[index] else null; + } - { - // with multisets it could be the case where two - // multisets are neither subset nor superset of each - // other. + /// Gets the address of the const value associated with a key. + /// If the key is not in the map, returns null. + pub fn getPtrConst(self: *const Self, key: Key) ?*const Value { + const index = Indexer.indexOf(key); + return if (self.bits.isSet(index)) &self.values[index] else null; + } - const r10 = EnumMultiset(Ball).init(.{ - .red = 10, - }); - const b10 = EnumMultiset(Ball).init(.{ - .blue = 10, - }); + /// Gets the address of the value associated with a key. + /// The key must be present in the map. + pub fn getPtrAssertContains(self: *Self, key: Key) *Value { + const index = Indexer.indexOf(key); + assert(self.bits.isSet(index)); + return &self.values[index]; + } - try testing.expect(!r10.subsetOf(b10)); - try testing.expect(!b10.subsetOf(r10)); - try testing.expect(!r10.supersetOf(b10)); - try testing.expect(!b10.supersetOf(r10)); - } + /// Gets the address of the const value associated with a key. + /// The key must be present in the map. + pub fn getPtrConstAssertContains(self: *const Self, key: Key) *const Value { + const index = Indexer.indexOf(key); + assert(self.bits.isSet(index)); + return &self.values[index]; + } - { - const result = r0_g1_b2.plusAssertSafe(ten_of_each); - try testing.expectEqual(result.getCount(.red), 10); - try testing.expectEqual(result.getCount(.green), 11); - try testing.expectEqual(result.getCount(.blue), 12); - } + /// Adds the key to the map with the supplied value. + /// If the key is already in the map, overwrites the value. + pub fn put(self: *Self, key: Key, value: Value) void { + const index = Indexer.indexOf(key); + self.bits.set(index); + self.values[index] = value; + } - { - const result = try r0_g1_b2.plus(ten_of_each); - try testing.expectEqual(result.getCount(.red), 10); - try testing.expectEqual(result.getCount(.green), 11); - try testing.expectEqual(result.getCount(.blue), 12); + /// Adds the key to the map with an undefined value. + /// If the key is already in the map, the value becomes undefined. + /// A pointer to the value is returned, which should be + /// used to initialize the value. + pub fn putUninitialized(self: *Self, key: Key) *Value { + const index = Indexer.indexOf(key); + self.bits.set(index); + self.values[index] = undefined; + return &self.values[index]; + } - const full = EnumMultiset(Ball).initWithCount(std.math.maxInt(usize)); - try testing.expectError(error.Overflow, result.plus(full)); - } + /// Sets the value associated with the key in the map, + /// and returns the old value. If the key was not in + /// the map, returns null. + pub fn fetchPut(self: *Self, key: Key, value: Value) ?Value { + const index = Indexer.indexOf(key); + const result: ?Value = if (self.bits.isSet(index)) self.values[index] else null; + self.bits.set(index); + self.values[index] = value; + return result; + } - { - const result = ten_of_each.minus(r0_g1_b2); - try testing.expectEqual(result.getCount(.red), 10); - try testing.expectEqual(result.getCount(.green), 9); - try testing.expectEqual(result.getCount(.blue), 8); - } + /// Removes a key from the map. If the key was not in the map, + /// does nothing. + pub fn remove(self: *Self, key: Key) void { + const index = Indexer.indexOf(key); + self.bits.unset(index); + self.values[index] = undefined; + } - { - const result = ten_of_each.minus(r0_g1_b2).minus(ten_of_each); - try testing.expectEqual(result.getCount(.red), 0); - try testing.expectEqual(result.getCount(.green), 0); - try testing.expectEqual(result.getCount(.blue), 0); - } + /// Removes a key from the map, and returns the old value. + /// If the key was not in the map, returns null. + pub fn fetchRemove(self: *Self, key: Key) ?Value { + const index = Indexer.indexOf(key); + const result: ?Value = if (self.bits.isSet(index)) self.values[index] else null; + self.bits.unset(index); + self.values[index] = undefined; + return result; + } - { - var copy = empty; - var it = copy.iterator(); - var entry = it.next().?; - try testing.expectEqual(entry.key, .red); - try testing.expectEqual(entry.value.*, 0); - entry = it.next().?; - try testing.expectEqual(entry.key, .green); - try testing.expectEqual(entry.value.*, 0); - entry = it.next().?; - try testing.expectEqual(entry.key, .blue); - try testing.expectEqual(entry.value.*, 0); - try testing.expectEqual(it.next(), null); - } + /// Returns an iterator over the map, which visits items in index order. + /// Modifications to the underlying map may or may not be observed by + /// the iterator, but will not invalidate it. + pub fn iterator(self: *Self) Iterator { + return .{ + .inner = self.bits.iterator(.{}), + .values = &self.values, + }; + } - { - var copy = r0_g1_b2; - var it = copy.iterator(); - var entry = it.next().?; - try testing.expectEqual(entry.key, .red); - try testing.expectEqual(entry.value.*, 0); - entry = it.next().?; - try testing.expectEqual(entry.key, .green); - try testing.expectEqual(entry.value.*, 1); - entry = it.next().?; - try testing.expectEqual(entry.key, .blue); - try testing.expectEqual(entry.value.*, 2); - try testing.expectEqual(it.next(), null); - } -} + /// An entry in the map. + pub const Entry = struct { + /// The key associated with this entry. + /// Modifying this key will not change the map. + key: Key, -/// An array keyed by an enum, backed by a dense array. -/// If the enum is not dense, a mapping will be constructed from -/// enum values to dense indices. This type does no dynamic -/// allocation and can be copied by value. -pub fn EnumArray(comptime E: type, comptime V: type) type { - const mixin = struct { - fn EnumArrayExt(comptime Self: type) type { - const Indexer = Self.Indexer; - return struct { - /// Initializes all values in the enum array - pub fn init(init_values: EnumFieldStruct(E, V, @as(?V, null))) Self { - return initDefault(@as(?V, null), init_values); - } + /// A pointer to the value in the map associated + /// with this key. Modifications through this + /// pointer will modify the underlying data. + value: *Value, + }; + + pub const Iterator = struct { + inner: BitSet.Iterator(.{}), + values: *[Indexer.count]Value, - /// Initializes values in the enum array, with the specified default. - pub fn initDefault(comptime default: ?V, init_values: EnumFieldStruct(E, V, default)) Self { - var result = Self{ .values = undefined }; - comptime var i: usize = 0; - inline while (i < Self.len) : (i += 1) { - const key = comptime Indexer.keyForIndex(i); - const tag = @tagName(key); - result.values[i] = @field(init_values, tag); + pub fn next(self: *Iterator) ?Entry { + return if (self.inner.next()) |index| + Entry{ + .key = Indexer.keyForIndex(index), + .value = &self.values[index], } - return result; - } - }; - } + else + null; + } + }; }; - return IndexedArray(EnumIndexer(E), V, mixin.EnumArrayExt); } -fn NoExtension(comptime Self: type) type { - _ = Self; - return NoExt; +/// A multiset of enum elements up to a count of usize. Backed +/// by an EnumArray. This type does no dynamic allocation and can +/// be copied by value. +pub fn EnumMultiset(comptime E: type) type { + return BoundedEnumMultiset(E, usize); } -const NoExt = struct {}; -/// A set type with an Indexer mapping from keys to indices. -/// Presence or absence is stored as a dense bitfield. This -/// type does no allocation and can be copied by value. -pub fn IndexedSet(comptime I: type, comptime Ext: ?fn (type) type) type { - comptime ensureIndexer(I); +/// A multiset of enum elements up to CountSize. Backed by an +/// EnumArray. This type does no dynamic allocation and can be +/// copied by value. +pub fn BoundedEnumMultiset(comptime E: type, comptime CountSize: type) type { return struct { const Self = @This(); - pub usingnamespace (Ext orelse NoExtension)(Self); - - /// The indexing rules for converting between keys and indices. - pub const Indexer = I; - /// The element type for this set. - pub const Key = Indexer.Key; - - const BitSet = std.StaticBitSet(Indexer.count); - - /// The maximum number of items in this set. - pub const len = Indexer.count; - - bits: BitSet = BitSet.initEmpty(), + counts: EnumArray(E, CountSize), - /// Returns a set containing no keys. - pub fn initEmpty() Self { - return .{ .bits = BitSet.initEmpty() }; + /// Initializes the multiset using a struct of counts. + pub fn init(init_counts: EnumFieldStruct(E, CountSize, 0)) Self { + var self = initWithCount(0); + inline for (@typeInfo(E).Enum.fields) |field| { + const c = @field(init_counts, field.name); + const key = @as(E, @enumFromInt(field.value)); + self.counts.set(key, c); + } + return self; } - /// Returns a set containing all possible keys. - pub fn initFull() Self { - return .{ .bits = BitSet.initFull() }; + /// Initializes the multiset with a count of zero. + pub fn initEmpty() Self { + return initWithCount(0); } - /// Returns a set containing multiple keys. - pub fn initMany(keys: []const Key) Self { - var set = initEmpty(); - for (keys) |key| set.insert(key); - return set; + /// Initializes the multiset with all keys at the + /// same count. + pub fn initWithCount(comptime c: CountSize) Self { + return .{ + .counts = EnumArray(E, CountSize).initDefault(c, .{}), + }; } - /// Returns a set containing a single key. - pub fn initOne(key: Key) Self { - return initMany(&[_]Key{key}); + /// Returns the total number of key counts in the multiset. + pub fn count(self: Self) usize { + var sum: usize = 0; + for (self.counts.values) |c| { + sum += c; + } + return sum; } - /// Returns the number of keys in the set. - pub fn count(self: Self) usize { - return self.bits.count(); + /// Checks if at least one key in multiset. + pub fn contains(self: Self, key: E) bool { + return self.counts.get(key) > 0; } - /// Checks if a key is in the set. - pub fn contains(self: Self, key: Key) bool { - return self.bits.isSet(Indexer.indexOf(key)); + /// Removes all instance of a key from multiset. Same as + /// setCount(key, 0). + pub fn removeAll(self: *Self, key: E) void { + return self.counts.set(key, 0); } - /// Puts a key in the set. - pub fn insert(self: *Self, key: Key) void { - self.bits.set(Indexer.indexOf(key)); + /// Increases the key count by given amount. Caller asserts + /// operation will not overflow. + pub fn addAssertSafe(self: *Self, key: E, c: CountSize) void { + self.counts.getPtr(key).* += c; } - /// Removes a key from the set. - pub fn remove(self: *Self, key: Key) void { - self.bits.unset(Indexer.indexOf(key)); + /// Increases the key count by given amount. + pub fn add(self: *Self, key: E, c: CountSize) error{Overflow}!void { + self.counts.set(key, try std.math.add(CountSize, self.counts.get(key), c)); } - /// Changes the presence of a key in the set to match the passed bool. - pub fn setPresent(self: *Self, key: Key, present: bool) void { - self.bits.setValue(Indexer.indexOf(key), present); + /// Decreases the key count by given amount. If amount is + /// greater than the number of keys in multset, then key count + /// will be set to zero. + pub fn remove(self: *Self, key: E, c: CountSize) void { + self.counts.getPtr(key).* -= @min(self.getCount(key), c); } - /// Toggles the presence of a key in the set. If the key is in - /// the set, removes it. Otherwise adds it. - pub fn toggle(self: *Self, key: Key) void { - self.bits.toggle(Indexer.indexOf(key)); + /// Returns the count for a key. + pub fn getCount(self: Self, key: E) CountSize { + return self.counts.get(key); } - /// Toggles the presence of all keys in the passed set. - pub fn toggleSet(self: *Self, other: Self) void { - self.bits.toggleSet(other.bits); + /// Set the count for a key. + pub fn setCount(self: *Self, key: E, c: CountSize) void { + self.counts.set(key, c); } - /// Toggles all possible keys in the set. - pub fn toggleAll(self: *Self) void { - self.bits.toggleAll(); + /// Increases the all key counts by given multiset. Caller + /// asserts operation will not overflow any key. + pub fn addSetAssertSafe(self: *Self, other: Self) void { + inline for (@typeInfo(E).Enum.fields) |field| { + const key = @as(E, @enumFromInt(field.value)); + self.addAssertSafe(key, other.getCount(key)); + } } - /// Adds all keys in the passed set to this set. - pub fn setUnion(self: *Self, other: Self) void { - self.bits.setUnion(other.bits); + /// Increases the all key counts by given multiset. + pub fn addSet(self: *Self, other: Self) error{Overflow}!void { + inline for (@typeInfo(E).Enum.fields) |field| { + const key = @as(E, @enumFromInt(field.value)); + try self.add(key, other.getCount(key)); + } } - /// Removes all keys which are not in the passed set. - pub fn setIntersection(self: *Self, other: Self) void { - self.bits.setIntersection(other.bits); + /// Decreases the all key counts by given multiset. If + /// the given multiset has more key counts than this, + /// then that key will have a key count of zero. + pub fn removeSet(self: *Self, other: Self) void { + inline for (@typeInfo(E).Enum.fields) |field| { + const key = @as(E, @enumFromInt(field.value)); + self.remove(key, other.getCount(key)); + } } - /// Returns true iff both sets have the same keys. + /// Returns true iff all key counts are the same as + /// given multiset. pub fn eql(self: Self, other: Self) bool { - return self.bits.eql(other.bits); + inline for (@typeInfo(E).Enum.fields) |field| { + const key = @as(E, @enumFromInt(field.value)); + if (self.getCount(key) != other.getCount(key)) { + return false; + } + } + return true; } - /// Returns true iff all the keys in this set are - /// in the other set. The other set may have keys - /// not found in this set. + /// Returns true iff all key counts less than or + /// equal to the given multiset. pub fn subsetOf(self: Self, other: Self) bool { - return self.bits.subsetOf(other.bits); + inline for (@typeInfo(E).Enum.fields) |field| { + const key = @as(E, @enumFromInt(field.value)); + if (self.getCount(key) > other.getCount(key)) { + return false; + } + } + return true; } - /// Returns true iff this set contains all the keys - /// in the other set. This set may have keys not - /// found in the other set. + /// Returns true iff all key counts greater than or + /// equal to the given multiset. pub fn supersetOf(self: Self, other: Self) bool { - return self.bits.supersetOf(other.bits); - } - - /// Returns a set with all the keys not in this set. - pub fn complement(self: Self) Self { - return .{ .bits = self.bits.complement() }; - } - - /// Returns a set with keys that are in either this - /// set or the other set. - pub fn unionWith(self: Self, other: Self) Self { - return .{ .bits = self.bits.unionWith(other.bits) }; - } - - /// Returns a set with keys that are in both this - /// set and the other set. - pub fn intersectWith(self: Self, other: Self) Self { - return .{ .bits = self.bits.intersectWith(other.bits) }; + inline for (@typeInfo(E).Enum.fields) |field| { + const key = @as(E, @enumFromInt(field.value)); + if (self.getCount(key) < other.getCount(key)) { + return false; + } + } + return true; } - /// Returns a set with keys that are in either this - /// set or the other set, but not both. - pub fn xorWith(self: Self, other: Self) Self { - return .{ .bits = self.bits.xorWith(other.bits) }; + /// Returns a multiset with the total key count of this + /// multiset and the other multiset. Caller asserts + /// operation will not overflow any key. + pub fn plusAssertSafe(self: Self, other: Self) Self { + var result = self; + result.addSetAssertSafe(other); + return result; } - /// Returns a set with keys that are in this set - /// except for keys in the other set. - pub fn differenceWith(self: Self, other: Self) Self { - return .{ .bits = self.bits.differenceWith(other.bits) }; + /// Returns a multiset with the total key count of this + /// multiset and the other multiset. + pub fn plus(self: Self, other: Self) error{Overflow}!Self { + var result = self; + try result.addSet(other); + return result; } - /// Returns an iterator over this set, which iterates in - /// index order. Modifications to the set during iteration - /// may or may not be observed by the iterator, but will - /// not invalidate it. - pub fn iterator(self: *const Self) Iterator { - return .{ .inner = self.bits.iterator(.{}) }; + /// Returns a multiset with the key count of this + /// multiset minus the corresponding key count in the + /// other multiset. If the other multiset contains + /// more key count than this set, that key will have + /// a count of zero. + pub fn minus(self: Self, other: Self) Self { + var result = self; + result.removeSet(other); + return result; } - pub const Iterator = struct { - inner: BitSet.Iterator(.{}), + pub const Entry = EnumArray(E, CountSize).Entry; + pub const Iterator = EnumArray(E, CountSize).Iterator; - pub fn next(self: *Iterator) ?Key { - return if (self.inner.next()) |index| - Indexer.keyForIndex(index) - else - null; - } - }; + /// Returns an iterator over this multiset. Keys with zero + /// counts are included. Modifications to the set during + /// iteration may or may not be observed by the iterator, + /// but will not invalidate it. + pub fn iterator(self: *Self) Iterator { + return self.counts.iterator(); + } }; } -test "pure EnumSet fns" { - const Suit = enum { spades, hearts, clubs, diamonds }; - - const empty = EnumSet(Suit).initEmpty(); - const full = EnumSet(Suit).initFull(); - const black = EnumSet(Suit).initMany(&[_]Suit{ .spades, .clubs }); - const red = EnumSet(Suit).initMany(&[_]Suit{ .hearts, .diamonds }); - - try testing.expect(empty.eql(empty)); - try testing.expect(full.eql(full)); - try testing.expect(!empty.eql(full)); - try testing.expect(!full.eql(empty)); - try testing.expect(!empty.eql(black)); - try testing.expect(!full.eql(red)); - try testing.expect(!red.eql(empty)); - try testing.expect(!black.eql(full)); - - try testing.expect(empty.subsetOf(empty)); - try testing.expect(empty.subsetOf(full)); - try testing.expect(full.subsetOf(full)); - try testing.expect(!black.subsetOf(red)); - try testing.expect(!red.subsetOf(black)); - - try testing.expect(full.supersetOf(full)); - try testing.expect(full.supersetOf(empty)); - try testing.expect(empty.supersetOf(empty)); - try testing.expect(!black.supersetOf(red)); - try testing.expect(!red.supersetOf(black)); - - try testing.expect(empty.complement().eql(full)); - try testing.expect(full.complement().eql(empty)); - try testing.expect(black.complement().eql(red)); - try testing.expect(red.complement().eql(black)); +test EnumMultiset { + const Ball = enum { red, green, blue }; - try testing.expect(empty.unionWith(empty).eql(empty)); - try testing.expect(empty.unionWith(full).eql(full)); - try testing.expect(full.unionWith(full).eql(full)); - try testing.expect(full.unionWith(empty).eql(full)); - try testing.expect(black.unionWith(red).eql(full)); - try testing.expect(red.unionWith(black).eql(full)); + const empty = EnumMultiset(Ball).initEmpty(); + const r0_g1_b2 = EnumMultiset(Ball).init(.{ + .red = 0, + .green = 1, + .blue = 2, + }); + const ten_of_each = EnumMultiset(Ball).initWithCount(10); - try testing.expect(empty.intersectWith(empty).eql(empty)); - try testing.expect(empty.intersectWith(full).eql(empty)); - try testing.expect(full.intersectWith(full).eql(full)); - try testing.expect(full.intersectWith(empty).eql(empty)); - try testing.expect(black.intersectWith(red).eql(empty)); - try testing.expect(red.intersectWith(black).eql(empty)); + try testing.expectEqual(empty.count(), 0); + try testing.expectEqual(r0_g1_b2.count(), 3); + try testing.expectEqual(ten_of_each.count(), 30); - try testing.expect(empty.xorWith(empty).eql(empty)); - try testing.expect(empty.xorWith(full).eql(full)); - try testing.expect(full.xorWith(full).eql(empty)); - try testing.expect(full.xorWith(empty).eql(full)); - try testing.expect(black.xorWith(red).eql(full)); - try testing.expect(red.xorWith(black).eql(full)); + try testing.expect(!empty.contains(.red)); + try testing.expect(!empty.contains(.green)); + try testing.expect(!empty.contains(.blue)); - try testing.expect(empty.differenceWith(empty).eql(empty)); - try testing.expect(empty.differenceWith(full).eql(empty)); - try testing.expect(full.differenceWith(full).eql(empty)); - try testing.expect(full.differenceWith(empty).eql(full)); - try testing.expect(full.differenceWith(red).eql(black)); - try testing.expect(full.differenceWith(black).eql(red)); -} + try testing.expect(!r0_g1_b2.contains(.red)); + try testing.expect(r0_g1_b2.contains(.green)); + try testing.expect(r0_g1_b2.contains(.blue)); -test "EnumSet empty" { - const E = enum {}; - const empty = EnumSet(E).initEmpty(); - const full = EnumSet(E).initFull(); + try testing.expect(ten_of_each.contains(.red)); + try testing.expect(ten_of_each.contains(.green)); + try testing.expect(ten_of_each.contains(.blue)); - try std.testing.expect(empty.eql(full)); - try std.testing.expect(empty.complement().eql(full)); - try std.testing.expect(empty.complement().eql(full.complement())); - try std.testing.expect(empty.eql(full.complement())); -} + { + var copy = ten_of_each; + copy.removeAll(.red); + try testing.expect(!copy.contains(.red)); -test "EnumSet const iterator" { - const Direction = enum { up, down, left, right }; - const diag_move = init: { - var move = EnumSet(Direction).initEmpty(); - move.insert(.right); - move.insert(.up); - break :init move; - }; + // removeAll second time does nothing + copy.removeAll(.red); + try testing.expect(!copy.contains(.red)); + } - var result = EnumSet(Direction).initEmpty(); - var it = diag_move.iterator(); - while (it.next()) |dir| { - result.insert(dir); + { + var copy = ten_of_each; + copy.addAssertSafe(.red, 6); + try testing.expectEqual(copy.getCount(.red), 16); } - try testing.expect(result.eql(diag_move)); -} + { + var copy = ten_of_each; + try copy.add(.red, 6); + try testing.expectEqual(copy.getCount(.red), 16); -/// A map from keys to values, using an index lookup. Uses a -/// bitfield to track presence and a dense array of values. -/// This type does no allocation and can be copied by value. -pub fn IndexedMap(comptime I: type, comptime V: type, comptime Ext: ?fn (type) type) type { - comptime ensureIndexer(I); - return struct { - const Self = @This(); + try testing.expectError(error.Overflow, copy.add(.red, std.math.maxInt(usize))); + } - pub usingnamespace (Ext orelse NoExtension)(Self); + { + var copy = ten_of_each; + copy.remove(.red, 4); + try testing.expectEqual(copy.getCount(.red), 6); - /// The index mapping for this map - pub const Indexer = I; - /// The key type used to index this map - pub const Key = Indexer.Key; - /// The value type stored in this map - pub const Value = V; - /// The number of possible keys in the map - pub const len = Indexer.count; + // subtracting more it contains does not underflow + copy.remove(.green, 14); + try testing.expectEqual(copy.getCount(.green), 0); + } - const BitSet = std.StaticBitSet(Indexer.count); + try testing.expectEqual(empty.getCount(.green), 0); + try testing.expectEqual(r0_g1_b2.getCount(.green), 1); + try testing.expectEqual(ten_of_each.getCount(.green), 10); - /// Bits determining whether items are in the map - bits: BitSet = BitSet.initEmpty(), - /// Values of items in the map. If the associated - /// bit is zero, the value is undefined. - values: [Indexer.count]Value = undefined, + { + var copy = empty; + copy.setCount(.red, 6); + try testing.expectEqual(copy.getCount(.red), 6); + } - /// The number of items in the map. - pub fn count(self: Self) usize { - return self.bits.count(); - } + { + var copy = r0_g1_b2; + copy.addSetAssertSafe(ten_of_each); + try testing.expectEqual(copy.getCount(.red), 10); + try testing.expectEqual(copy.getCount(.green), 11); + try testing.expectEqual(copy.getCount(.blue), 12); + } - /// Checks if the map contains an item. - pub fn contains(self: Self, key: Key) bool { - return self.bits.isSet(Indexer.indexOf(key)); - } + { + var copy = r0_g1_b2; + try copy.addSet(ten_of_each); + try testing.expectEqual(copy.getCount(.red), 10); + try testing.expectEqual(copy.getCount(.green), 11); + try testing.expectEqual(copy.getCount(.blue), 12); - /// Gets the value associated with a key. - /// If the key is not in the map, returns null. - pub fn get(self: Self, key: Key) ?Value { - const index = Indexer.indexOf(key); - return if (self.bits.isSet(index)) self.values[index] else null; - } + const full = EnumMultiset(Ball).initWithCount(std.math.maxInt(usize)); + try testing.expectError(error.Overflow, copy.addSet(full)); + } - /// Gets the value associated with a key, which must - /// exist in the map. - pub fn getAssertContains(self: Self, key: Key) Value { - const index = Indexer.indexOf(key); - assert(self.bits.isSet(index)); - return self.values[index]; - } + { + var copy = ten_of_each; + copy.removeSet(r0_g1_b2); + try testing.expectEqual(copy.getCount(.red), 10); + try testing.expectEqual(copy.getCount(.green), 9); + try testing.expectEqual(copy.getCount(.blue), 8); - /// Gets the address of the value associated with a key. - /// If the key is not in the map, returns null. - pub fn getPtr(self: *Self, key: Key) ?*Value { - const index = Indexer.indexOf(key); - return if (self.bits.isSet(index)) &self.values[index] else null; - } + copy.removeSet(ten_of_each); + try testing.expectEqual(copy.getCount(.red), 0); + try testing.expectEqual(copy.getCount(.green), 0); + try testing.expectEqual(copy.getCount(.blue), 0); + } - /// Gets the address of the const value associated with a key. - /// If the key is not in the map, returns null. - pub fn getPtrConst(self: *const Self, key: Key) ?*const Value { - const index = Indexer.indexOf(key); - return if (self.bits.isSet(index)) &self.values[index] else null; - } + try testing.expect(empty.eql(empty)); + try testing.expect(r0_g1_b2.eql(r0_g1_b2)); + try testing.expect(ten_of_each.eql(ten_of_each)); + try testing.expect(!empty.eql(r0_g1_b2)); + try testing.expect(!r0_g1_b2.eql(ten_of_each)); + try testing.expect(!ten_of_each.eql(empty)); - /// Gets the address of the value associated with a key. - /// The key must be present in the map. - pub fn getPtrAssertContains(self: *Self, key: Key) *Value { - const index = Indexer.indexOf(key); - assert(self.bits.isSet(index)); - return &self.values[index]; - } + try testing.expect(empty.subsetOf(empty)); + try testing.expect(r0_g1_b2.subsetOf(r0_g1_b2)); + try testing.expect(empty.subsetOf(r0_g1_b2)); + try testing.expect(r0_g1_b2.subsetOf(ten_of_each)); + try testing.expect(!ten_of_each.subsetOf(r0_g1_b2)); + try testing.expect(!r0_g1_b2.subsetOf(empty)); - /// Gets the address of the const value associated with a key. - /// The key must be present in the map. - pub fn getPtrConstAssertContains(self: *const Self, key: Key) *const Value { - const index = Indexer.indexOf(key); - assert(self.bits.isSet(index)); - return &self.values[index]; - } + try testing.expect(empty.supersetOf(empty)); + try testing.expect(r0_g1_b2.supersetOf(r0_g1_b2)); + try testing.expect(r0_g1_b2.supersetOf(empty)); + try testing.expect(ten_of_each.supersetOf(r0_g1_b2)); + try testing.expect(!r0_g1_b2.supersetOf(ten_of_each)); + try testing.expect(!empty.supersetOf(r0_g1_b2)); - /// Adds the key to the map with the supplied value. - /// If the key is already in the map, overwrites the value. - pub fn put(self: *Self, key: Key, value: Value) void { - const index = Indexer.indexOf(key); - self.bits.set(index); - self.values[index] = value; - } + { + // with multisets it could be the case where two + // multisets are neither subset nor superset of each + // other. - /// Adds the key to the map with an undefined value. - /// If the key is already in the map, the value becomes undefined. - /// A pointer to the value is returned, which should be - /// used to initialize the value. - pub fn putUninitialized(self: *Self, key: Key) *Value { - const index = Indexer.indexOf(key); - self.bits.set(index); - self.values[index] = undefined; - return &self.values[index]; - } + const r10 = EnumMultiset(Ball).init(.{ + .red = 10, + }); + const b10 = EnumMultiset(Ball).init(.{ + .blue = 10, + }); - /// Sets the value associated with the key in the map, - /// and returns the old value. If the key was not in - /// the map, returns null. - pub fn fetchPut(self: *Self, key: Key, value: Value) ?Value { - const index = Indexer.indexOf(key); - const result: ?Value = if (self.bits.isSet(index)) self.values[index] else null; - self.bits.set(index); - self.values[index] = value; - return result; - } + try testing.expect(!r10.subsetOf(b10)); + try testing.expect(!b10.subsetOf(r10)); + try testing.expect(!r10.supersetOf(b10)); + try testing.expect(!b10.supersetOf(r10)); + } - /// Removes a key from the map. If the key was not in the map, - /// does nothing. - pub fn remove(self: *Self, key: Key) void { - const index = Indexer.indexOf(key); - self.bits.unset(index); - self.values[index] = undefined; - } + { + const result = r0_g1_b2.plusAssertSafe(ten_of_each); + try testing.expectEqual(result.getCount(.red), 10); + try testing.expectEqual(result.getCount(.green), 11); + try testing.expectEqual(result.getCount(.blue), 12); + } - /// Removes a key from the map, and returns the old value. - /// If the key was not in the map, returns null. - pub fn fetchRemove(self: *Self, key: Key) ?Value { - const index = Indexer.indexOf(key); - const result: ?Value = if (self.bits.isSet(index)) self.values[index] else null; - self.bits.unset(index); - self.values[index] = undefined; - return result; - } + { + const result = try r0_g1_b2.plus(ten_of_each); + try testing.expectEqual(result.getCount(.red), 10); + try testing.expectEqual(result.getCount(.green), 11); + try testing.expectEqual(result.getCount(.blue), 12); - /// Returns an iterator over the map, which visits items in index order. - /// Modifications to the underlying map may or may not be observed by - /// the iterator, but will not invalidate it. - pub fn iterator(self: *Self) Iterator { - return .{ - .inner = self.bits.iterator(.{}), - .values = &self.values, - }; - } + const full = EnumMultiset(Ball).initWithCount(std.math.maxInt(usize)); + try testing.expectError(error.Overflow, result.plus(full)); + } - /// An entry in the map. - pub const Entry = struct { - /// The key associated with this entry. - /// Modifying this key will not change the map. - key: Key, + { + const result = ten_of_each.minus(r0_g1_b2); + try testing.expectEqual(result.getCount(.red), 10); + try testing.expectEqual(result.getCount(.green), 9); + try testing.expectEqual(result.getCount(.blue), 8); + } - /// A pointer to the value in the map associated - /// with this key. Modifications through this - /// pointer will modify the underlying data. - value: *Value, - }; + { + const result = ten_of_each.minus(r0_g1_b2).minus(ten_of_each); + try testing.expectEqual(result.getCount(.red), 0); + try testing.expectEqual(result.getCount(.green), 0); + try testing.expectEqual(result.getCount(.blue), 0); + } - pub const Iterator = struct { - inner: BitSet.Iterator(.{}), - values: *[Indexer.count]Value, + { + var copy = empty; + var it = copy.iterator(); + var entry = it.next().?; + try testing.expectEqual(entry.key, .red); + try testing.expectEqual(entry.value.*, 0); + entry = it.next().?; + try testing.expectEqual(entry.key, .green); + try testing.expectEqual(entry.value.*, 0); + entry = it.next().?; + try testing.expectEqual(entry.key, .blue); + try testing.expectEqual(entry.value.*, 0); + try testing.expectEqual(it.next(), null); + } - pub fn next(self: *Iterator) ?Entry { - return if (self.inner.next()) |index| - Entry{ - .key = Indexer.keyForIndex(index), - .value = &self.values[index], - } - else - null; - } - }; - }; + { + var copy = r0_g1_b2; + var it = copy.iterator(); + var entry = it.next().?; + try testing.expectEqual(entry.key, .red); + try testing.expectEqual(entry.value.*, 0); + entry = it.next().?; + try testing.expectEqual(entry.key, .green); + try testing.expectEqual(entry.value.*, 1); + entry = it.next().?; + try testing.expectEqual(entry.key, .blue); + try testing.expectEqual(entry.value.*, 2); + try testing.expectEqual(it.next(), null); + } } -/// A dense array of values, using an indexed lookup. -/// This type does no allocation and can be copied by value. -pub fn IndexedArray(comptime I: type, comptime V: type, comptime Ext: ?fn (type) type) type { - comptime ensureIndexer(I); +/// An array keyed by an enum, backed by a dense array. +/// If the enum is not dense, a mapping will be constructed from +/// enum values to dense indices. This type does no dynamic +/// allocation and can be copied by value. +pub fn EnumArray(comptime E: type, comptime V: type) type { return struct { const Self = @This(); - pub usingnamespace (Ext orelse NoExtension)(Self); - /// The index mapping for this map - pub const Indexer = I; + pub const Indexer = EnumIndexer(E); /// The key type used to index this map pub const Key = Indexer.Key; /// The value type stored in this map @@ -1201,6 +1038,21 @@ pub fn IndexedArray(comptime I: type, comptime V: type, comptime Ext: ?fn (type) values: [Indexer.count]Value, + pub fn init(init_values: EnumFieldStruct(E, Value, null)) Self { + return initDefault(null, init_values); + } + + /// Initializes values in the enum array, with the specified default. + pub fn initDefault(comptime default: ?Value, init_values: EnumFieldStruct(E, Value, default)) Self { + var result: Self = .{ .values = undefined }; + inline for (0..Self.len) |i| { + const key = comptime Indexer.keyForIndex(i); + const tag = @tagName(key); + result.values[i] = @field(init_values, tag); + } + return result; + } + pub fn initUndefined() Self { return Self{ .values = undefined }; } @@ -1269,46 +1121,96 @@ pub fn IndexedArray(comptime I: type, comptime V: type, comptime Ext: ?fn (type) }; } -/// Verifies that a type is a valid Indexer, providing a helpful -/// compile error if not. An Indexer maps a comptime-known set -/// of keys to a dense set of zero-based indices. -/// The indexer interface must look like this: -/// ``` -/// struct { -/// /// The key type which this indexer converts to indices -/// pub const Key: type, -/// /// The number of indexes in the dense mapping -/// pub const count: comptime_int, -/// /// Converts from a key to an index -/// pub fn indexOf(Key) usize; -/// /// Converts from an index to a key -/// pub fn keyForIndex(usize) Key; -/// } -/// ``` -pub fn ensureIndexer(comptime T: type) void { - comptime { - if (!@hasDecl(T, "Key")) @compileError("Indexer must have decl Key: type."); - if (@TypeOf(T.Key) != type) @compileError("Indexer.Key must be a type."); - if (!@hasDecl(T, "count")) @compileError("Indexer must have decl count: comptime_int."); - if (@TypeOf(T.count) != comptime_int) @compileError("Indexer.count must be a comptime_int."); - if (!@hasDecl(T, "indexOf")) @compileError("Indexer.indexOf must be a fn (Key) usize."); - if (@TypeOf(T.indexOf) != fn (T.Key) usize) @compileError("Indexer must have decl indexOf: fn (Key) usize."); - if (!@hasDecl(T, "keyForIndex")) @compileError("Indexer must have decl keyForIndex: fn (usize) Key."); - if (@TypeOf(T.keyForIndex) != fn (usize) T.Key) @compileError("Indexer.keyForIndex must be a fn (usize) Key."); - } +test "pure EnumSet fns" { + const Suit = enum { spades, hearts, clubs, diamonds }; + + const empty = EnumSet(Suit).initEmpty(); + const full = EnumSet(Suit).initFull(); + const black = EnumSet(Suit).initMany(&[_]Suit{ .spades, .clubs }); + const red = EnumSet(Suit).initMany(&[_]Suit{ .hearts, .diamonds }); + + try testing.expect(empty.eql(empty)); + try testing.expect(full.eql(full)); + try testing.expect(!empty.eql(full)); + try testing.expect(!full.eql(empty)); + try testing.expect(!empty.eql(black)); + try testing.expect(!full.eql(red)); + try testing.expect(!red.eql(empty)); + try testing.expect(!black.eql(full)); + + try testing.expect(empty.subsetOf(empty)); + try testing.expect(empty.subsetOf(full)); + try testing.expect(full.subsetOf(full)); + try testing.expect(!black.subsetOf(red)); + try testing.expect(!red.subsetOf(black)); + + try testing.expect(full.supersetOf(full)); + try testing.expect(full.supersetOf(empty)); + try testing.expect(empty.supersetOf(empty)); + try testing.expect(!black.supersetOf(red)); + try testing.expect(!red.supersetOf(black)); + + try testing.expect(empty.complement().eql(full)); + try testing.expect(full.complement().eql(empty)); + try testing.expect(black.complement().eql(red)); + try testing.expect(red.complement().eql(black)); + + try testing.expect(empty.unionWith(empty).eql(empty)); + try testing.expect(empty.unionWith(full).eql(full)); + try testing.expect(full.unionWith(full).eql(full)); + try testing.expect(full.unionWith(empty).eql(full)); + try testing.expect(black.unionWith(red).eql(full)); + try testing.expect(red.unionWith(black).eql(full)); + + try testing.expect(empty.intersectWith(empty).eql(empty)); + try testing.expect(empty.intersectWith(full).eql(empty)); + try testing.expect(full.intersectWith(full).eql(full)); + try testing.expect(full.intersectWith(empty).eql(empty)); + try testing.expect(black.intersectWith(red).eql(empty)); + try testing.expect(red.intersectWith(black).eql(empty)); + + try testing.expect(empty.xorWith(empty).eql(empty)); + try testing.expect(empty.xorWith(full).eql(full)); + try testing.expect(full.xorWith(full).eql(empty)); + try testing.expect(full.xorWith(empty).eql(full)); + try testing.expect(black.xorWith(red).eql(full)); + try testing.expect(red.xorWith(black).eql(full)); + + try testing.expect(empty.differenceWith(empty).eql(empty)); + try testing.expect(empty.differenceWith(full).eql(empty)); + try testing.expect(full.differenceWith(full).eql(empty)); + try testing.expect(full.differenceWith(empty).eql(full)); + try testing.expect(full.differenceWith(red).eql(black)); + try testing.expect(full.differenceWith(black).eql(red)); } -test ensureIndexer { - ensureIndexer(struct { - pub const Key = u32; - pub const count: comptime_int = 8; - pub fn indexOf(k: Key) usize { - return @as(usize, @intCast(k)); - } - pub fn keyForIndex(index: usize) Key { - return @as(Key, @intCast(index)); - } - }); +test "EnumSet empty" { + const E = enum {}; + const empty = EnumSet(E).initEmpty(); + const full = EnumSet(E).initFull(); + + try std.testing.expect(empty.eql(full)); + try std.testing.expect(empty.complement().eql(full)); + try std.testing.expect(empty.complement().eql(full.complement())); + try std.testing.expect(empty.eql(full.complement())); +} + +test "EnumSet const iterator" { + const Direction = enum { up, down, left, right }; + const diag_move = init: { + var move = EnumSet(Direction).initEmpty(); + move.insert(.right); + move.insert(.up); + break :init move; + }; + + var result = EnumSet(Direction).initEmpty(); + var it = diag_move.iterator(); + while (it.next()) |dir| { + result.insert(dir); + } + + try testing.expect(result.eql(diag_move)); } pub fn EnumIndexer(comptime E: type) type { @@ -1438,7 +1340,6 @@ test "EnumIndexer non-exhaustive" { _, }; const Indexer = EnumIndexer(E); - ensureIndexer(Indexer); const min_tag: E = @enumFromInt(std.math.minInt(BackingInt)); const max_tag: E = @enumFromInt(std.math.maxInt(BackingInt)); @@ -1466,7 +1367,6 @@ test "EnumIndexer non-exhaustive" { test "EnumIndexer dense zeroed" { const E = enum(u2) { b = 1, a = 0, c = 2 }; const Indexer = EnumIndexer(E); - ensureIndexer(Indexer); try testing.expectEqual(E, Indexer.Key); try testing.expectEqual(3, Indexer.count); @@ -1482,7 +1382,6 @@ test "EnumIndexer dense zeroed" { test "EnumIndexer dense positive" { const E = enum(u4) { c = 6, a = 4, b = 5 }; const Indexer = EnumIndexer(E); - ensureIndexer(Indexer); try testing.expectEqual(E, Indexer.Key); try testing.expectEqual(3, Indexer.count); @@ -1498,7 +1397,6 @@ test "EnumIndexer dense positive" { test "EnumIndexer dense negative" { const E = enum(i4) { a = -6, c = -4, b = -5 }; const Indexer = EnumIndexer(E); - ensureIndexer(Indexer); try testing.expectEqual(E, Indexer.Key); try testing.expectEqual(3, Indexer.count); @@ -1514,7 +1412,6 @@ test "EnumIndexer dense negative" { test "EnumIndexer sparse" { const E = enum(i4) { a = -2, c = 6, b = 4 }; const Indexer = EnumIndexer(E); - ensureIndexer(Indexer); try testing.expectEqual(E, Indexer.Key); try testing.expectEqual(3, Indexer.count); @@ -1530,7 +1427,6 @@ test "EnumIndexer sparse" { test "EnumIndexer empty" { const E = enum {}; const Indexer = EnumIndexer(E); - ensureIndexer(Indexer); try testing.expectEqual(E, Indexer.Key); try testing.expectEqual(0, Indexer.count); }