Commit 17f83ace03

mlugg <mlugg@mlugg.co.uk>
2024-03-07 16:49:11
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.
1 parent 2298004
Changed files (1)
lib
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);
 }