Search code examples
c#dictionarydata-structuresrefactoring

Triple-Bound Dictionary-Like Data Structure?


I'm trying to create a data structure that binds 3 elements together, and where any of the 3 elements can be used as the key.

Up till now, I've been using a bunch of functions that convert from one data element to the corresponding others (i.e. MoveStateToAngle, AngleToMoveState, MoveStateToVector2) but it's extremely ugly and verbose.

Update: The data structure is intended for holding less than 10 entries that will never change.


Solution

  • For such a small number of entries, storing them in a simple array or List<T> should outperform storing them in dictionaries. So here is my suggestion. It assumes that the types MoveState, Angle and Vector2 are already defined:

    public class TriDictionary
    {
        private readonly List<Entry> _entries = new();
    
        public readonly record struct Entry(
            MoveState MoveState, Angle Angle, Vector2 Vector2);
    
        public void Add(MoveState moveState, Angle angle, Vector2 vector2)
        {
            _entries.Add(new(moveState, angle, vector2));
        }
    
        public Entry this[MoveState key] => GetEntry(key, static e => e.MoveState);
        public Entry this[Angle key] => GetEntry(key, static e => e.Angle);
        public Entry this[Vector2 key] => GetEntry(key, static e => e.Vector2);
    
        private Entry GetEntry<TKey>(TKey key, Func<Entry, TKey> keySelector)
        {
            foreach (Entry entry in _entries)
            {
                if (keySelector(entry).Equals(key)) return entry;
            }
            throw new KeyNotFoundException($"Key '{key}' not found.");
        }
    }
    

    Usage example:

    TriDictionary data = new();
    //...
    data.Add(someMoveState, someAngle, someVector2);
    //...
    var angle = data[moveState].Angle;     // MoveStateToAngle
    //...
    var moveState = data[angle].MoveState; // AngleToMoveState
    //...
    var vector2 = data[moveState].Vector2; // MoveStateToVector2
    

    I've avoided using the List<T>.Find method because it requires that the searched key is captured in a closure, resulting in needless allocations. That's the reason for the private generic GetEntry<TKey> method.

    For simplicity the implementation above doesn't enforce the uniqueness of the stored keys.