Zweiwege- / bidirektionales Wörterbuch in C #?

84

Ich möchte Wörter in einem Wörterbuch folgendermaßen speichern:

Ich kann Wortcode für Wort erhalten: dict["SomeWord"]-> 123und Wort für Wortcode: dict[123]->"SomeWord"

Ist es echt? Ein Weg, dies zu tun, sind natürlich zwei Wörterbücher: Dictionary<string,int>und Dictionary<int,string>aber gibt es einen anderen Weg?

Neir0
quelle
2
Es gibt keinen Standarddatentyp (ab .NET 4), der O (1)
Auch nicht, dass eine bidirektionale Karte (Schlüsselwort?) Zusätzliche Einschränkungen auferlegt, es sei denn, eine bidirektionale Karte ...

Antworten:

105

Ich habe ein paar kurze Kurse geschrieben, in denen Sie tun können, was Sie wollen. Sie müssten es wahrscheinlich um weitere Funktionen erweitern, aber es ist ein guter Ausgangspunkt.

Die Verwendung des Codes sieht folgendermaßen aus:

var map = new Map<int, string>();

map.Add(42, "Hello");

Console.WriteLine(map.Forward[42]);
// Outputs "Hello"

Console.WriteLine(map.Reverse["Hello"]);
//Outputs 42

Hier ist die Definition:

public class Map<T1, T2>
{
    private Dictionary<T1, T2> _forward = new Dictionary<T1, T2>();
    private Dictionary<T2, T1> _reverse = new Dictionary<T2, T1>();

    public Map()
    {
        this.Forward = new Indexer<T1, T2>(_forward);
        this.Reverse = new Indexer<T2, T1>(_reverse);
    }

    public class Indexer<T3, T4>
    {
        private Dictionary<T3, T4> _dictionary;
        public Indexer(Dictionary<T3, T4> dictionary)
        {
            _dictionary = dictionary;
        }
        public T4 this[T3 index]
        {
            get { return _dictionary[index]; }
            set { _dictionary[index] = value; }
        }
    }

    public void Add(T1 t1, T2 t2)
    {
        _forward.Add(t1, t2);
        _reverse.Add(t2, t1);
    }

    public Indexer<T1, T2> Forward { get; private set; }
    public Indexer<T2, T1> Reverse { get; private set; }
}
Rätselhaftigkeit
quelle
2
@ Pedro77 - Das tut es jetzt. ;-)
Rätselhaftigkeit
2
@ Pedro77 - Ich war nur frech, als ich meinte, meine Klasse sei die neue "Karten" -Lösung.
Rätselhaftigkeit
11
Dadurch werden Klasseninvarianten für Ausnahmen nicht beibehalten. Es ist möglich _forward.Add, erfolgreich zu sein und _reverse.Addzu scheitern, sodass Sie ein teilweise hinzugefügtes Paar haben.
5
@hvd - Wie ich schon sagte - es ist eine schnell zusammengestellte Klasse.
Rätselhaftigkeit
3
@AaA Es ändert nicht die ForwardDictionary-Eigenschaft selbst (die es hat private set;), sondern den Wert in diesem Dictionary über die Indexer-Eigenschaft der Indexer-Klasse, die es an das Dictionary übergibt. public T4 this[T3 index] { get { return _dictionary[index]; } set { _dictionary[index] = value; } }Das bricht also die Vorwärts- / Rückwärtssuche.
Jeroen van Langen
25

Leider benötigen Sie zwei Wörterbücher, eines für jede Richtung. Mit LINQ können Sie das inverse Wörterbuch jedoch problemlos abrufen:

Dictionary<T1, T2> dict = new Dictionary<T1, T2>();
Dictionary<T2, T1> dictInverse = dict.ToDictionary((i) => i.Value, (i) => i.Key);
Hasan Baidoun
quelle
10

Erweiterung des Enigmativity-Codes durch Hinzufügen der Methode initializes und Contains.

public class Map<T1, T2> : IEnumerable<KeyValuePair<T1, T2>>
{
    private readonly Dictionary<T1, T2> _forward = new Dictionary<T1, T2>();
    private readonly Dictionary<T2, T1> _reverse = new Dictionary<T2, T1>();

    public Map()
    {
        Forward = new Indexer<T1, T2>(_forward);
        Reverse = new Indexer<T2, T1>(_reverse);
    }

    public Indexer<T1, T2> Forward { get; private set; }
    public Indexer<T2, T1> Reverse { get; private set; }

    public void Add(T1 t1, T2 t2)
    {
        _forward.Add(t1, t2);
        _reverse.Add(t2, t1);
    }

    public void Remove(T1 t1)
    {
        T2 revKey = Forward[t1];
        _forward.Remove(t1);
        _reverse.Remove(revKey);
    }
    
    public void Remove(T2 t2)
    {
        T1 forwardKey = Reverse[t2];
        _reverse.Remove(t2);
        _forward.Remove(forwardKey);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public IEnumerator<KeyValuePair<T1, T2>> GetEnumerator()
    {
        return _forward.GetEnumerator();
    }

    public class Indexer<T3, T4>
    {
        private readonly Dictionary<T3, T4> _dictionary;

        public Indexer(Dictionary<T3, T4> dictionary)
        {
            _dictionary = dictionary;
        }

        public T4 this[T3 index]
        {
            get { return _dictionary[index]; }
            set { _dictionary[index] = value; }
        }

        public bool Contains(T3 key)
        {
            return _dictionary.ContainsKey(key);
        }
    }
}

Hier ist ein Anwendungsfall, überprüfen Sie gültige Klammern

public static class ValidParenthesisExt
{
    private static readonly Map<char, char>
        _parenthesis = new Map<char, char>
        {
            {'(', ')'},
            {'{', '}'},
            {'[', ']'}
        };

    public static bool IsValidParenthesis(this string input)
    {
        var stack = new Stack<char>();
        foreach (var c in input)
        {
            if (_parenthesis.Forward.Contains(c))
                stack.Push(c);
            else
            {
                if (stack.Count == 0) return false;
                if (_parenthesis.Reverse[c] != stack.Pop())
                    return false;
            }
        }
        return stack.Count == 0;
    }
}
Xavier John
quelle
7

Sie könnten zwei Wörterbücher verwenden, wie andere gesagt haben, aber beachten Sie auch, dass wenn beide TKeyundTValue vom gleichen Typ sind (und deren Laufzeitwertdomänen bekanntermaßen nicht zusammenhängend sind), dasselbe Wörterbuch verwenden können, indem Sie zwei Einträge für jeden Schlüssel erstellen / Wertepaarung:

dict["SomeWord"]= "123" und dict["123"]="SomeWord"

Auf diese Weise kann ein einzelnes Wörterbuch für jede Art der Suche verwendet werden.

zmbq
quelle
3
Ja, dieser Ansatz wurde in der Frage bestätigt :)
3
Dies ignoriert die Möglichkeit, dass sowohl in "Schlüsseln" als auch in "Werten" der gleiche Wert vorhanden ist. dann wird es in dieser Lösung in Konflikt geraten.
user1028741
1
@ user1028741 Einverstanden, obwohl aus dem Beispiel hervorgeht, dass sie "von einem anderen Typ" nicht "vom gleichen Typ" gemeint haben
Hutch
6

Was zum Teufel, ich werde meine Version in die Mischung werfen:

public class BijectiveDictionary<TKey, TValue> 
{
    private EqualityComparer<TKey> _keyComparer;
    private Dictionary<TKey, ISet<TValue>> _forwardLookup;
    private EqualityComparer<TValue> _valueComparer;
    private Dictionary<TValue, ISet<TKey>> _reverseLookup;             

    public BijectiveDictionary()
        : this(EqualityComparer<TKey>.Default, EqualityComparer<TValue>.Default)
    {
    }

    public BijectiveDictionary(EqualityComparer<TKey> keyComparer, EqualityComparer<TValue> valueComparer)
        : this(0, EqualityComparer<TKey>.Default, EqualityComparer<TValue>.Default)
    {
    }

    public BijectiveDictionary(int capacity, EqualityComparer<TKey> keyComparer, EqualityComparer<TValue> valueComparer)
    {
        _keyComparer = keyComparer;
        _forwardLookup = new Dictionary<TKey, ISet<TValue>>(capacity, keyComparer);            
        _valueComparer = valueComparer;
        _reverseLookup = new Dictionary<TValue, ISet<TKey>>(capacity, valueComparer);            
    }

    public void Add(TKey key, TValue value)
    {
        AddForward(key, value);
        AddReverse(key, value);
    }

    public void AddForward(TKey key, TValue value)
    {
        ISet<TValue> values;
        if (!_forwardLookup.TryGetValue(key, out values))
        {
            values = new HashSet<TValue>(_valueComparer);
            _forwardLookup.Add(key, values);
        }
        values.Add(value);
    }

    public void AddReverse(TKey key, TValue value) 
    {
        ISet<TKey> keys;
        if (!_reverseLookup.TryGetValue(value, out keys))
        {
            keys = new HashSet<TKey>(_keyComparer);
            _reverseLookup.Add(value, keys);
        }
        keys.Add(key);
    }

    public bool TryGetReverse(TValue value, out ISet<TKey> keys)
    {
        return _reverseLookup.TryGetValue(value, out keys);
    }

    public ISet<TKey> GetReverse(TValue value)
    {
        ISet<TKey> keys;
        TryGetReverse(value, out keys);
        return keys;
    }

    public bool ContainsForward(TKey key)
    {
        return _forwardLookup.ContainsKey(key);
    }

    public bool TryGetForward(TKey key, out ISet<TValue> values)
    {
        return _forwardLookup.TryGetValue(key, out values);
    }

    public ISet<TValue> GetForward(TKey key)
    {
        ISet<TValue> values;
        TryGetForward(key, out values);
        return values;
    }

    public bool ContainsReverse(TValue value)
    {
        return _reverseLookup.ContainsKey(value);
    }

    public void Clear()
    {
        _forwardLookup.Clear();
        _reverseLookup.Clear();
    }
}

Fügen Sie einige Daten hinzu:

var lookup = new BijectiveDictionary<int, int>();

lookup.Add(1, 2);
lookup.Add(1, 3);
lookup.Add(1, 4);
lookup.Add(1, 5);

lookup.Add(6, 2);
lookup.Add(6, 8);
lookup.Add(6, 9);
lookup.Add(6, 10);

Und dann machen Sie die Suche:

lookup[2] --> 1, 6
lookup[3] --> 1
lookup[8] --> 6
Ostati
quelle
Ich mag, dass dies 1: N
Sebastian
@Sebastian, Sie könnten IEnumerable <KeyValuePair <TKey, TValue >> hinzufügen.
Ostati
4

Sie können diese Erweiterungsmethode verwenden, obwohl sie eine Aufzählung verwendet und daher für große Datenmengen möglicherweise nicht so performant ist. Wenn Sie sich Sorgen um die Effizienz machen, benötigen Sie zwei Wörterbücher. Wenn Sie die beiden Wörterbücher in eine Klasse einschließen möchten, lesen Sie die akzeptierte Antwort auf diese Frage: Bidirektionales 1: 1-Wörterbuch in C #

public static class IDictionaryExtensions
{
    public static TKey FindKeyByValue<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TValue value)
    {
        if (dictionary == null)
            throw new ArgumentNullException("dictionary");

        foreach (KeyValuePair<TKey, TValue> pair in dictionary)
            if (value.Equals(pair.Value)) return pair.Key;

        throw new Exception("the value is not found in the dictionary");
    }
}
moribvndvs
quelle
8
Obwohl dies ein bidirektionales Wörterbuch ist, ist das Abrufen des Werts eine O (n) -Operation, bei der es sich um eine O (1) -Operation handeln sollte. Dies ist für kleine Datenmengen möglicherweise nicht von Bedeutung, kann jedoch bei der Arbeit mit großen Datenmengen zu Leistungsproblemen führen. Die beste Antwort für die Leistung über den Raum wäre die Verwendung von zwei Wörterbüchern mit umgekehrten Daten.
Tom
@TomA Ich stimme voll und ganz mit Tom überein. Die einzige Instanz, in der Sie ein echtes bidirektionales Wörterbuch benötigen, ist, wenn Sie über 100K, 1M + Einträge verfügen. Alles andere als das Scannen ist effektiv ein NOOP.
Chris Marisic
Ich mag diese Lösung für meine Situation (kleine Diktatgrößen), weil ich immer noch Sammlungsinitialisierer verwenden kann. Map <A, B> in der akzeptierten Antwort kann meiner Meinung nach nicht in Sammlungsinitialisierern verwendet werden.
CVertex
@ ChrisMarisic, das scheint eine seltsame Sache zu verkünden. Wenn diese Suche in einer engen Schleife aufgerufen würde, würden Sie den Schmerz selbst bei <500 Einträgen spüren. Dies hängt auch von den Kosten eines Vergleichstests ab. Ich denke nicht, dass pauschale Aussagen wie Ihr Kommentar hilfreich sind.
Lee Campbell
@ LeeCampbell Meine umfassenden Aussagen basieren auf Erfahrungen in der tatsächlichen Realität, wie in messbarer und profilierter Realität. Wenn Sie einen komplexen Typ als Schlüssel für ein Wörterbuch verwenden möchten, ist dies Ihr Problem, nicht mein Problem.
Chris Marisic
1

Wörterbuch

Hier ist eine Mischung aus dem, was mir in jeder Antwort gefallen hat. Es wird implementiert, IEnumerabledamit der Auflistungsinitialisierer verwendet werden kann, wie Sie im Beispiel sehen können.

Nutzungsbeschränkung:

  • Sie verwenden verschiedene Datentypen. (dh )T1T2

Code:

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main()
    {
        Bictionary<string, int> bictionary = 
            new Bictionary<string,int>() {
                { "a",1 }, 
                { "b",2 }, 
                { "c",3 } 
            };

        // test forward lookup
        Console.WriteLine(bictionary["b"]);
        // test forward lookup error
        //Console.WriteLine(bictionary["d"]);
        // test reverse lookup
        Console.WriteLine(bictionary[3]); 
        // test reverse lookup error (throws same error as forward lookup does)
        Console.WriteLine(bictionary[4]); 
    }
}

public class Bictionary<T1, T2> : Dictionary<T1, T2>
{
    public T1 this[T2 index]
    {
        get
        {
            if(!this.Any(x => x.Value.Equals(index)))
               throw new System.Collections.Generic.KeyNotFoundException();
            return this.First(x => x.Value.Equals(index)).Key;
        }
    }
}

Geige:

https://dotnetfiddle.net/mTNEuw

toddmo
quelle
Sehr elegante Lösung! Könnten Sie vielleicht den Mut etwas näher erläutern? Habe ich recht, dass du nicht einmal machen kannst, Bictionary<string, string>wenn alle Saiten einzigartig sind?
Marcus Mangelsdorf
@ Merlin2001, das stimmt. Genauer gesagt, Sie konnten damit keine Forward-Lookups durchführen. Ich muss darüber nachdenken, wie ich das überwinden kann. Es wird kompiliert, findet aber immer zuerst den Reverse-Indexer, wenn die T1 == T2Vorwärtssuche fehlschlägt. Außerdem kann ich den Standardindexer nicht überschreiben, da Suchaufrufe dann nicht eindeutig sind. Ich habe diese Einschränkung hinzugefügt und die vorherige entfernt, da sich die Werte von T1mit den Werten von überschneiden können T2.
Toddmo
10
Auf der Rückseite gibt es ein ziemlich ernstes Leistungsproblem. Das Wörterbuch wird zweimal durchsucht, mit einem O (n) -Leistungstreffer. Es wäre viel schneller, ein zweites Wörterbuch zu verwenden und die Typbeschränkung zu entfernen.
Steve Cooper
@SteveCooper, vielleicht könnte ich die Leistungseinbußen beseitigen, indem ich sie in eine einpacke tryund Ausnahmen in konvertiere KeyNotFoundExceptions.
Toddmo
4
@toddmo Auf diese Weise können Sie möglicherweise die Geschwindigkeit verdoppeln. Das größere Problem ist, dass sowohl .First als auch .Any jeweils ein Element suchen und jedes testen. Das Testen einer Liste mit 1.000.000 Elementen dauert 1.000.000 Mal länger als das Suchen einer Liste mit 1 Elementen. Wörterbücher sind viel schneller und werden nicht langsamer, wenn Sie weitere Elemente hinzufügen. Ein zweites inverses Wörterbuch spart über große Listen hinweg epische Zeit. Es mag nicht relevant sein, aber es ist die Art von Dingen, die mit kleinen Datenmengen während des Testens in Ordnung sein können, und dann beeinträchtigt es die Leistung auf einem realen Server mit ernsthaften Daten.
Steve Cooper
1

Dies ist ein altes Problem, aber ich wollte zwei Erweiterungsmethoden hinzufügen, falls jemand es nützlich findet. Das zweite ist nicht so nützlich, bietet aber einen Ausgangspunkt, wenn eins zu eins Wörterbücher unterstützt werden müssen.

    public static Dictionary<VALUE,KEY> Inverse<KEY,VALUE>(this Dictionary<KEY,VALUE> dictionary)
    {
        if (dictionary==null || dictionary.Count == 0) { return null; }

        var result = new Dictionary<VALUE, KEY>(dictionary.Count);

        foreach(KeyValuePair<KEY,VALUE> entry in dictionary)
        {
            result.Add(entry.Value, entry.Key);
        }

        return result;
    }

    public static Dictionary<VALUE, KEY> SafeInverse<KEY, VALUE>(this Dictionary<KEY, VALUE> dictionary)
    {
        if (dictionary == null || dictionary.Count == 0) { return null; }

        var result = new Dictionary<VALUE, KEY>(dictionary.Count);

        foreach (KeyValuePair<KEY, VALUE> entry in dictionary)
        {
            if (result.ContainsKey(entry.Value)) { continue; }

            result.Add(entry.Value, entry.Key);
        }

        return result;
    }
Felipe Ramos
quelle
1

Eine modifizierte Version von Xavier Johns Antwort mit einem zusätzlichen Konstruktor zum Vorwärts- und Rückwärtsführen von Vergleichern. Dies würde beispielsweise Schlüssel unterstützen, bei denen die Groß- und Kleinschreibung nicht berücksichtigt wird. Bei Bedarf können weitere Konstruktoren hinzugefügt werden, um weitere Argumente an die Forward- und Reverse-Dictionary-Konstruktoren zu übergeben.

public class Map<T1, T2> : IEnumerable<KeyValuePair<T1, T2>>
{
    private readonly Dictionary<T1, T2> _forward;
    private readonly Dictionary<T2, T1> _reverse;

    /// <summary>
    /// Constructor that uses the default comparers for the keys in each direction.
    /// </summary>
    public Map()
        : this(null, null)
    {
    }

    /// <summary>
    /// Constructor that defines the comparers to use when comparing keys in each direction.
    /// </summary>
    /// <param name="t1Comparer">Comparer for the keys of type T1.</param>
    /// <param name="t2Comparer">Comparer for the keys of type T2.</param>
    /// <remarks>Pass null to use the default comparer.</remarks>
    public Map(IEqualityComparer<T1> t1Comparer, IEqualityComparer<T2> t2Comparer)
    {
        _forward = new Dictionary<T1, T2>(t1Comparer);
        _reverse = new Dictionary<T2, T1>(t2Comparer);
        Forward = new Indexer<T1, T2>(_forward);
        Reverse = new Indexer<T2, T1>(_reverse);
    }

    // Remainder is the same as Xavier John's answer:
    // https://stackoverflow.com/a/41907561/216440
    ...
}

Anwendungsbeispiel mit einem Schlüssel ohne Berücksichtigung der Groß- und Kleinschreibung:

Map<int, string> categories = 
new Map<int, string>(null, StringComparer.CurrentCultureIgnoreCase)
{
    { 1, "Bedroom Furniture" },
    { 2, "Dining Furniture" },
    { 3, "Outdoor Furniture" }, 
    { 4, "Kitchen Appliances" }
};

int categoryId = 3;
Console.WriteLine("Description for category ID {0}: '{1}'", 
    categoryId, categories.Forward[categoryId]);

string categoryDescription = "DINING FURNITURE";
Console.WriteLine("Category ID for description '{0}': {1}", 
    categoryDescription, categories.Reverse[categoryDescription]);

categoryDescription = "outdoor furniture";
Console.WriteLine("Category ID for description '{0}': {1}", 
    categoryDescription, categories.Reverse[categoryDescription]);

// Results:
/*
Description for category ID 3: 'Outdoor Furniture'
Category ID for description 'DINING FURNITURE': 2
Category ID for description 'outdoor furniture': 3
*/
Simon Tewsi
quelle
1

Hier ist mein Code. Bis auf die gesetzten Konstruktoren ist alles O (1).

using System.Collections.Generic;
using System.Linq;

public class TwoWayDictionary<T1, T2>
{
    Dictionary<T1, T2> _Forwards = new Dictionary<T1, T2>();
    Dictionary<T2, T1> _Backwards = new Dictionary<T2, T1>();

    public IReadOnlyDictionary<T1, T2> Forwards => _Forwards;
    public IReadOnlyDictionary<T2, T1> Backwards => _Backwards;

    public IEnumerable<T1> Set1 => Forwards.Keys;
    public IEnumerable<T2> Set2 => Backwards.Keys;


    public TwoWayDictionary()
    {
        _Forwards = new Dictionary<T1, T2>();
        _Backwards = new Dictionary<T2, T1>();
    }

    public TwoWayDictionary(int capacity)
    {
        _Forwards = new Dictionary<T1, T2>(capacity);
        _Backwards = new Dictionary<T2, T1>(capacity);
    }

    public TwoWayDictionary(Dictionary<T1, T2> initial)
    {
        _Forwards = initial;
        _Backwards = initial.ToDictionary(kvp => kvp.Value, kvp => kvp.Key);
    }

    public TwoWayDictionary(Dictionary<T2, T1> initial)
    {
        _Backwards = initial;
        _Forwards = initial.ToDictionary(kvp => kvp.Value, kvp => kvp.Key);
    }


    public T1 this[T2 index]
    {
        get => _Backwards[index];
        set
        {
            if (_Backwards.TryGetValue(index, out var removeThis))
                _Forwards.Remove(removeThis);

            _Backwards[index] = value;
            _Forwards[value] = index;
        }
    }

    public T2 this[T1 index]
    {
        get => _Forwards[index];
        set
        {
            if (_Forwards.TryGetValue(index, out var removeThis))
                _Backwards.Remove(removeThis);

            _Forwards[index] = value;
            _Backwards[value] = index;
        }
    }

    public int Count => _Forwards.Count;

    public bool Contains(T1 item) => _Forwards.ContainsKey(item);
    public bool Contains(T2 item) => _Backwards.ContainsKey(item);

    public bool Remove(T1 item)
    {
        if (!this.Contains(item))
            return false;

        var t2 = _Forwards[item];

        _Backwards.Remove(t2);
        _Forwards.Remove(item);

        return true;
    }

    public bool Remove(T2 item)
    {
        if (!this.Contains(item))
            return false;

        var t1 = _Backwards[item];

        _Forwards.Remove(t1);
        _Backwards.Remove(item);

        return true;
    }

    public void Clear()
    {
        _Forwards.Clear();
        _Backwards.Clear();
    }
}
Iamsodarncool
quelle
Ich frage mich, wie sich der Konstruktor verhält, wenn Sie ihm ein vorhandenes Wörterbuch übergeben, in dem Schlüssel- und Werttypen identisch sind. Wie wird entschieden, ob die rückwärts oder die vorwärts verwendet werden sollen?
Colm Bhandal
0

Die folgende Kapselungsklasse verwendet linq (IEnumerable Extensions) über 1 Wörterbuchinstanz.

public class TwoWayDictionary<TKey, TValue>
{
    readonly IDictionary<TKey, TValue> dict;
    readonly Func<TKey, TValue> GetValueWhereKey;
    readonly Func<TValue, TKey> GetKeyWhereValue;
    readonly bool _mustValueBeUnique = true;

    public TwoWayDictionary()
    {
        this.dict = new Dictionary<TKey, TValue>();
        this.GetValueWhereKey = (strValue) => dict.Where(kvp => Object.Equals(kvp.Key, strValue)).Select(kvp => kvp.Value).FirstOrDefault();
        this.GetKeyWhereValue = (intValue) => dict.Where(kvp => Object.Equals(kvp.Value, intValue)).Select(kvp => kvp.Key).FirstOrDefault();
    }

    public TwoWayDictionary(KeyValuePair<TKey, TValue>[] kvps)
        : this()
    {
        this.AddRange(kvps);
    }

    public void AddRange(KeyValuePair<TKey, TValue>[] kvps)
    {
        kvps.ToList().ForEach( kvp => {        
            if (!_mustValueBeUnique || !this.dict.Any(item => Object.Equals(item.Value, kvp.Value)))
            {
                dict.Add(kvp.Key, kvp.Value);
            } else {
                throw new InvalidOperationException("Value must be unique");
            }
        });
    }

    public TValue this[TKey key]
    {
        get { return GetValueWhereKey(key); }
    }

    public TKey this[TValue value]
    {
        get { return GetKeyWhereValue(value); }
    }
}

class Program
{
    static void Main(string[] args)
    {
        var dict = new TwoWayDictionary<string, int>(new KeyValuePair<string, int>[] {
            new KeyValuePair<string, int>(".jpeg",100),
            new KeyValuePair<string, int>(".jpg",101),
            new KeyValuePair<string, int>(".txt",102),
            new KeyValuePair<string, int>(".zip",103)
        });


        var r1 = dict[100];
        var r2 = dict[".jpg"];

    }

}
Brett Caswell
quelle
0

Dies verwendet einen Indexer für die umgekehrte Suche.
Die umgekehrte Suche ist O (n), es werden jedoch auch keine zwei Wörterbücher verwendet

public sealed class DictionaryDoubleKeyed : Dictionary<UInt32, string>
{   // used UInt32 as the key as it has a perfect hash
    // if most of the lookup is by word then swap
    public void Add(UInt32 ID, string Word)
    {
        if (this.ContainsValue(Word)) throw new ArgumentException();
        base.Add(ID, Word);
    }
    public UInt32 this[string Word]
    {   // this will be O(n)
        get
        {
            return this.FirstOrDefault(x => x.Value == Word).Key;
        }
    } 
}
Paparazzo
quelle
Zum Beispiel: mögliche NRE in this[string Word]. Zusätzliche Probleme sind Variablennamen, die nicht den gängigen Praktiken entsprechen, Kommentare, die nicht mit dem Code übereinstimmen ( UInt16vs UInt32- deshalb: Verwenden Sie keine Kommentare!), Die Lösung ist nicht generisch, ...
BartoszKP
0

Hier ist eine alternative Lösung zu den vorgeschlagenen. Die innere Klasse wurde entfernt und die Kohärenz beim Hinzufügen / Entfernen von Elementen sichergestellt

using System.Collections;
using System.Collections.Generic;

public class Map<E, F> : IEnumerable<KeyValuePair<E, F>>
{
    private readonly Dictionary<E, F> _left = new Dictionary<E, F>();
    public IReadOnlyDictionary<E, F> left => this._left;
    private readonly Dictionary<F, E> _right = new Dictionary<F, E>();
    public IReadOnlyDictionary<F, E> right => this._right;

    public void RemoveLeft(E e)
    {
        if (!this.left.ContainsKey(e)) return;
        this._right.Remove(this.left[e]);
        this._left.Remove(e);
    }

    public void RemoveRight(F f)
    {
        if (!this.right.ContainsKey(f)) return;
        this._left.Remove(this.right[f]);
        this._right.Remove(f);
    }

    public int Count()
    {
        return this.left.Count;
    }

    public void Set(E left, F right)
    {
        if (this.left.ContainsKey(left))
        {
            this.RemoveLeft(left);
        }
        if (this.right.ContainsKey(right))
        {
            this.RemoveRight(right);
        }
        this._left.Add(left, right);
        this._right.Add(right, left);
    }


    public IEnumerator<KeyValuePair<E, F>> GetEnumerator()
    {
        return this.left.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.left.GetEnumerator();
    }
}
Sirpaddow
quelle
0

Da ist ein BijectionDictionaryIn diesem Open Source-Repo Typ verfügbar:

https://github.com/ColmBhandal/CsharpExtras .

Es unterscheidet sich qualitativ nicht wesentlich von den anderen gegebenen Antworten. Wie die meisten dieser Antworten werden zwei Wörterbücher verwendet.

Ich glaube, was an diesem Wörterbuch im Vergleich zu den anderen bisherigen Antworten neu ist, ist, dass es sich nicht wie ein Zwei-Wege-Wörterbuch verhält, sondern nur wie ein Ein-Wege-Wörterbuch, das Ihnen vertraut ist und es Ihnen dann dynamisch ermöglicht, das Wörterbuch mithilfe von zu wechseln die Reverse-Eigenschaft. Die gespiegelte Objektreferenz ist flach, sodass weiterhin dasselbe Kernobjekt wie die ursprüngliche Referenz geändert werden kann. Sie können also zwei Verweise auf dasselbe Objekt haben, außer dass einer von ihnen umgedreht ist.

Eine andere Sache, die an diesem Wörterbuch wahrscheinlich einzigartig ist, ist, dass im Testprojekt unter diesem Repo einige Tests dafür geschrieben wurden. Es wurde von uns in der Praxis verwendet und war bisher ziemlich stabil.

Colm Bhandal
quelle