พจนานุกรมสองทาง / สองทิศทางใน C #?


90

ฉันต้องการจัดเก็บคำศัพท์ในพจนานุกรมด้วยวิธีต่อไปนี้:

ฉันสามารถรับรหัสคำทีละคำ: dict["SomeWord"]-> 123และรับรหัสคำศัพท์: dict[123]->"SomeWord"

เป็นของจริงหรือไม่? แน่นอนวิธีหนึ่งที่จะทำคือสองพจนานุกรม: Dictionary<string,int>และDictionary<int,string>แต่จะมีวิธีอื่นได้หรือไม่


2
ไม่มีชนิดข้อมูลมาตรฐาน (ณ . NET 4) ที่ให้ O (1) เข้าถึงทั้งสองทาง ... AFAIK :)

ไม่ใช่ว่าแผนที่สองทิศทาง (คีย์เวิร์ด?) กำหนดข้อ จำกัด เพิ่มเติมเว้นแต่ว่าจะมีแผนที่หลายทิศทาง ...

คำตอบ:


110

ฉันเขียนคลาสสั้น ๆ สองสามคลาสที่ให้คุณทำในสิ่งที่ต้องการ คุณอาจต้องขยายฟีเจอร์เพิ่มเติม แต่ก็เป็นจุดเริ่มต้นที่ดี

การใช้รหัสมีลักษณะดังนี้:

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

map.Add(42, "Hello");

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

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

นี่คือคำจำกัดความ:

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; }
}

2
@ Pedro77 - ตอนนี้ ;-)
Enigmativity

2
@ Pedro77 - ฉันแค่ทำตัวหน้าด้านโดยบอกว่าชั้นเรียนของฉันเป็นโซลูชัน "แผนที่" ใหม่
Enigmativity

12
สิ่งนี้ไม่ได้รักษาค่าคงที่ของคลาสตามข้อยกเว้น เป็นไปได้ที่_forward.Addจะประสบความสำเร็จและ_reverse.Addล้มเหลวทำให้คุณมีคู่ที่เพิ่มเข้ามาบางส่วน

5
@hvd - อย่างที่บอก - มันเป็นคลาสที่รวบรวมกันอย่างรวดเร็ว
Enigmativity

3
@AaA มันไม่ได้แก้ไขForwardคุณสมบัติพจนานุกรมด้วยตนเอง (ซึ่งมีprivate set;) แต่เป็นการแก้ไขค่าในพจนานุกรมนั้นผ่านคุณสมบัติ Indexer ของคลาส Indexer ซึ่งส่งผ่านไปยังพจนานุกรม public T4 this[T3 index] { get { return _dictionary[index]; } set { _dictionary[index] = value; } }นั่นเป็นการทำลายการค้นหาไปข้างหน้า / ย้อนกลับ
Jeroen van Langen

27

น่าเสียใจที่คุณต้องมีพจนานุกรมสองเล่มสำหรับแต่ละทิศทาง อย่างไรก็ตามคุณสามารถรับพจนานุกรมผกผันได้อย่างง่ายดายโดยใช้ LINQ:

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

11

ขยายในโค้ด Enigmativity โดยเพิ่มวิธีการเริ่มต้นและมี

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);
        }
    }
}

นี่คือกรณีการใช้งานตรวจสอบวงเล็บที่ถูกต้อง

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;
    }
}

7

คุณสามารถใช้พจนานุกรมสองพจนานุกรมได้ตามที่คนอื่น ๆ กล่าวไว้ แต่โปรดทราบด้วยว่าหากทั้งสองTKeyและTValueเป็นประเภทเดียวกัน (และโดเมนค่ารันไทม์ของพวกเขาเป็นที่รู้กันว่าไม่ปะติดปะต่อกัน) คุณก็สามารถใช้พจนานุกรมเดียวกันได้โดยสร้างสองรายการสำหรับแต่ละคีย์ / การจับคู่ค่า:

dict["SomeWord"]= "123" และ dict["123"]="SomeWord"

วิธีนี้สามารถใช้พจนานุกรมเดียวสำหรับการค้นหาประเภทใดประเภทหนึ่ง


3
ใช่วิธีนี้ได้รับการยอมรับในคำถาม :)

3
สิ่งนี้จะไม่สนใจความเป็นไปได้ของค่าเดียวกันที่มีอยู่ทั้งใน "คีย์" และ "ค่า" จากนั้นมันจะขัดแย้งกันในการแก้ปัญหานี้
user1028741

1
@ user1028741 เห็นด้วยแม้ว่าจากตัวอย่างจะปรากฏว่าพวกเขาหมายถึง "ประเภทอื่น" ไม่ใช่ "ประเภทเดียวกัน"
Hutch

ซึ่งอาจส่งผลให้เกิดผลลัพธ์ที่ไม่คาดคิดในอนาคตจากนั้นโค้ดจะต้องผ่านการ refactoring เช่นจากนั้นด้านซ้ายและด้านขวาจะเริ่มทับซ้อนกัน มันเพิ่มประสิทธิภาพที่ใกล้เคียงกับอะไรเลย
Vinigas

6

ห่าฉันจะโยนเวอร์ชันของฉันลงในส่วนผสม:

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();
    }
}

เพิ่มข้อมูลลงไป:

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);

จากนั้นทำการค้นหา:

lookup[2] --> 1, 6
lookup[3] --> 1
lookup[8] --> 6

ฉันชอบที่สิ่งนี้รองรับ 1: N
Sebastian

@ เซบาสเตียนคุณสามารถเพิ่ม IEnumerable <KeyValuePair <TKey, TValue >>
Ostati

4

คุณสามารถใช้วิธีการขยายนี้ได้แม้ว่าจะใช้การแจงนับ แต่ก็อาจไม่ได้ผลดีเท่าชุดข้อมูลขนาดใหญ่ หากคุณกังวลเกี่ยวกับประสิทธิภาพคุณต้องมีพจนานุกรมสองเล่ม หากคุณต้องการรวมพจนานุกรมทั้งสองเป็นชั้นเดียวโปรดดูคำตอบที่ยอมรับสำหรับคำถามนี้: พจนานุกรมแบบสองทิศทาง 1 ถึง 1 ใน 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");
    }
}

8
แม้ว่านี่จะเป็นพจนานุกรมแบบสองทิศทางการดึงค่าเป็นการดำเนินการ O (n) ซึ่งควรเป็นการดำเนินการ O (1) สิ่งนี้อาจไม่สำคัญสำหรับชุดข้อมูลขนาดเล็ก แต่อาจทำให้เกิดปัญหาด้านประสิทธิภาพเมื่อทำงานกับชุดข้อมูลขนาดใหญ่ คำตอบที่ดีที่สุดสำหรับประสิทธิภาพเหนือพื้นที่คือการใช้พจนานุกรมสองพจนานุกรมกับข้อมูลที่กลับรายการ
ทอม

@ TomA ฉันเห็นด้วยกับ Tom อย่างเต็มที่อินสแตนซ์เดียวที่คุณต้องการพจนานุกรมแบบสองทิศทางที่แท้จริงคือเมื่อคุณมีรายการ 100K, 1M + สิ่งที่สแกนน้อยกว่ามันก็เป็น NOOP อย่างมีประสิทธิภาพ
Chris Marisic

ฉันชอบโซลูชันนี้สำหรับสถานการณ์ของฉัน (ขนาดคำสั่งเล็ก ๆ ) เพราะฉันยังสามารถใช้ตัวเริ่มต้นการรวบรวมได้ แผนที่ <A, B> ในคำตอบที่ยอมรับฉันไม่คิดว่าจะใช้ในตัวเริ่มต้นการรวบรวมได้
CVertex

@ChrisMarisic นั่นดูเหมือนเป็นเรื่องแปลกที่จะประกาศ หากการค้นหานี้ถูกเรียกแบบวนซ้ำฉันพนันได้เลยว่าคุณจะรู้สึกเจ็บปวดแม้จะมี <500 รายการก็ตาม นอกจากนี้ยังขึ้นอยู่กับค่าใช้จ่ายของการทดสอบเปรียบเทียบ ฉันไม่คิดว่าคำพูดที่กว้างเกินไปเช่นความคิดเห็นของคุณมีประโยชน์
Lee Campbell

@LeeCampbell ข้อความที่กวาดมาของฉันขึ้นอยู่กับประสบการณ์ในความเป็นจริงตามความเป็นจริงที่วัดได้และมีประวัติ หากคุณต้องการใช้ประเภทที่ซับซ้อนเป็นกุญแจสำคัญในพจนานุกรมนั่นคือปัญหาของคุณไม่ใช่ปัญหาของฉัน
Chris Marisic

1

พจนานุกรม

นี่คือสิ่งที่ฉันชอบในแต่ละคำตอบ มันดำเนินการIEnumerableเพื่อให้สามารถใช้ collection initializer ดังที่คุณเห็นในตัวอย่าง

ข้อ จำกัด การใช้งาน:

  • คุณกำลังใช้ประเภทข้อมูลที่แตกต่างกัน (กล่าวคือ)T1T2

รหัส:

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;
        }
    }
}

ซอ:

https://dotnetfiddle.net/mTNEuw


วิธีแก้ที่สวยหรูมาก! คุณช่วยอธิบายความกล้าของมันต่อไปได้ไหม? ฉันพูดถูกBictionary<string, string>หรือเปล่าที่คุณไม่สามารถสร้างได้แม้ว่าสตริงทั้งหมดจะไม่ซ้ำกัน?
Marcus Mangelsdorf

@ Merlin2001 ถูกต้อง อย่างแม่นยำยิ่งกว่านั้นคุณไม่สามารถทำการค้นหาล่วงหน้าได้ ฉันจะต้องคิดว่าจะเอาชนะมันอย่างไร รวบรวม แต่จะพบตัวทำดัชนีย้อนกลับก่อนT1 == T2เสมอดังนั้นการค้นหาไปข้างหน้าจึงล้มเหลว นอกจากนี้ฉันไม่สามารถแทนที่ตัวทำดัชนีเริ่มต้นได้เพราะการเรียกค้นหาจะคลุมเครือ ฉันได้เพิ่มข้อ จำกัด นี้และลบข้อ จำกัด ก่อนหน้านี้ออกเนื่องจากค่าT1สามารถทับซ้อนกับค่าของT2.
Toddmo

10
มีปัญหาด้านประสิทธิภาพที่ค่อนข้างร้ายแรงในทางกลับกัน พจนานุกรมจะถูกค้นหาสองครั้งโดยมีการตีประสิทธิภาพ O (n) การใช้พจนานุกรมที่สองจะเร็วกว่ามากและจะลบข้อ จำกัด ประเภท
Steve Cooper

@SteveCooper บางทีฉันอาจจะกำจัดประสิทธิภาพที่โดนโดยการรวมเข้าtryและแปลงข้อยกเว้นเป็นKeyNotFoundExceptions.
Toddmo

4
@toddmo คุณอาจจะเพิ่มความเร็วเป็นสองเท่าด้วยวิธีนี้ ปัญหาที่ใหญ่กว่าคือทั้ง. First และ. ค้นหาทีละรายการทดสอบทีละรายการ ดังนั้นในการทดสอบรายการ 1,000,000 รายการจึงใช้เวลาในการค้นหานานกว่ารายการ 1 องค์ประกอบถึง 1,000,000 เท่า พจนานุกรมเร็วขึ้นมากและไม่ช้าลงเมื่อคุณเพิ่มรายการมากขึ้นดังนั้นพจนานุกรมผกผันที่สองจะช่วยประหยัดเวลาได้มากในรายการขนาดใหญ่ อาจไม่เกี่ยวข้อง แต่เป็นประเภทของสิ่งที่สามารถใช้ได้กับข้อมูลจำนวนเล็กน้อยในระหว่างการทดสอบและจากนั้นก็ทำลายประสิทธิภาพบนเซิร์ฟเวอร์จริงด้วยข้อมูลที่ร้ายแรง
Steve Cooper

1

นี่เป็นปัญหาเก่า แต่ฉันต้องการเพิ่มวิธีการขยายสองวิธีในกรณีที่มีใครพบว่ามีประโยชน์ ข้อที่สองไม่มีประโยชน์ แต่เป็นจุดเริ่มต้นหากต้องรองรับพจนานุกรมแบบหนึ่งต่อหนึ่ง

    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;
    }

1

คำตอบของ Xavier John เวอร์ชันแก้ไขพร้อมตัวสร้างเพิ่มเติมเพื่อนำไปข้างหน้าและย้อนกลับ Comparers สิ่งนี้จะรองรับคีย์ที่ไม่คำนึงถึงตัวพิมพ์เล็กและใหญ่ตัวอย่างเช่น สามารถเพิ่มตัวสร้างเพิ่มเติมได้หากจำเป็นเพื่อส่งผ่านข้อโต้แย้งเพิ่มเติมไปยังตัวสร้างพจนานุกรมไปข้างหน้าและย้อนกลับ

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
    ...
}

ตัวอย่างการใช้งานด้วยคีย์ที่ไม่คำนึงถึงตัวพิมพ์เล็กและใหญ่:

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
*/

1

นี่คือรหัสของฉัน ทุกอย่างเป็น 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();
    }
}

ฉันสงสัยว่าตัวสร้างจะทำงานอย่างไรถ้าคุณส่งผ่านพจนานุกรมที่มีอยู่โดยที่ประเภทคีย์และค่าเหมือนกัน จะแก้ปัญหาอย่างไรว่าจะใช้การถอยหลังกับการส่งต่อ?
Colm Bhandal

0

คลาสการห่อหุ้มต่อไปนี้ใช้ linq (IEnumerable Extensions) มากกว่า 1 อินสแตนซ์พจนานุกรม

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"];

    }

}

0

สิ่งนี้ใช้ตัวสร้างดัชนีสำหรับการค้นหาแบบย้อนกลับ
การค้นหาแบบย้อนกลับคือ O (n) แต่ก็ไม่ได้ใช้พจนานุกรมสองชุด

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;
        }
    } 
}

ตัวอย่างเช่น: NRE this[string Word]เป็นไปได้ใน ปัญหาเพิ่มเติมคือชื่อตัวแปรไม่สอดคล้องกับแนวทางปฏิบัติทั่วไปความคิดเห็นไม่สอดคล้องกับรหัส ( UInt16เทียบกับUInt32- นั่นคือเหตุผล: อย่าใช้ความคิดเห็น!) วิธีแก้ปัญหาไม่ใช่แบบทั่วไป ...
BartoszKP

0

นี่เป็นทางเลือกอื่นสำหรับวิธีที่แนะนำ ลบชั้นในและประกันการเชื่อมโยงกันเมื่อเพิ่ม / ลบรายการ

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();
    }
}


0

มี BijectionDictionaryประเภทที่พร้อมใช้งานใน repo โอเพ่นซอร์สนี้:

https://github.com/ColmBhandal/CsharpExtras

ไม่ได้แตกต่างในเชิงคุณภาพมากนักกับคำตอบอื่น ๆ ที่ได้รับ ใช้พจนานุกรมสองเล่มเช่นเดียวกับคำตอบส่วนใหญ่

ฉันเชื่อว่านวนิยายคืออะไรเกี่ยวกับพจนานุกรมนี้เทียบกับคำตอบอื่น ๆ คือแทนที่จะทำตัวเหมือนพจนานุกรมสองทางมันจะทำงานเหมือนพจนานุกรมทางเดียวที่คุ้นเคยจากนั้นให้คุณพลิกพจนานุกรมโดยใช้ คุณสมบัติย้อนกลับ การอ้างอิงวัตถุที่พลิกจะตื้นดังนั้นจึงยังคงสามารถแก้ไขวัตถุหลักเดียวกันกับการอ้างอิงเดิมได้ ดังนั้นคุณสามารถมีการอ้างอิงสองรายการไปยังวัตถุเดียวกันยกเว้นหนึ่งในนั้นถูกพลิก

สิ่งที่อาจไม่เหมือนใครเกี่ยวกับพจนานุกรมนี้คือมีการทดสอบบางอย่างที่เขียนไว้ในโครงการทดสอบภายใต้ repo นั้น มันถูกใช้โดยเราในทางปฏิบัติและค่อนข้างเสถียรจนถึงขณะนี้


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.