ได้รับคีย์หลายค่าที่ระบุของพจนานุกรมทั่วไปหรือไม่?


122

การรับค่าของคีย์จากพจนานุกรมทั่วไป. NET เป็นเรื่องง่าย:

Dictionary<int, string> greek = new Dictionary<int, string>();
greek.Add(1, "Alpha");
greek.Add(2, "Beta");
string secondGreek = greek[2];  // Beta

แต่การพยายามรับคีย์ที่กำหนดค่านั้นไม่ตรงไปตรงมาเพราะอาจมีหลายคีย์:

int[] betaKeys = greek.WhatDoIPutHere("Beta");  // expecting single 2

1
เหตุใดจึงเป็นประเภทผลตอบแทนint[]เมื่อคุณคาดหวังค่าเดียว
anar khalilov

3
@Anar อ่านคำตอบของฉันเพื่อ Domenic; “ ค่าที่ซ้ำกันไม่น่าจะเป็นไปได้ แต่ก็ไม่สามารถทำได้”
Dour High Arch

คีย์ของค่า? ฉันคิดว่าคุณหมายถึงกุญแจ
Max Hodges

คำตอบ:


144

ตกลงนี่คือเวอร์ชันสองทิศทางหลายแบบ:

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

class BiDictionary<TFirst, TSecond>
{
    IDictionary<TFirst, IList<TSecond>> firstToSecond = new Dictionary<TFirst, IList<TSecond>>();
    IDictionary<TSecond, IList<TFirst>> secondToFirst = new Dictionary<TSecond, IList<TFirst>>();

    private static IList<TFirst> EmptyFirstList = new TFirst[0];
    private static IList<TSecond> EmptySecondList = new TSecond[0];

    public void Add(TFirst first, TSecond second)
    {
        IList<TFirst> firsts;
        IList<TSecond> seconds;
        if (!firstToSecond.TryGetValue(first, out seconds))
        {
            seconds = new List<TSecond>();
            firstToSecond[first] = seconds;
        }
        if (!secondToFirst.TryGetValue(second, out firsts))
        {
            firsts = new List<TFirst>();
            secondToFirst[second] = firsts;
        }
        seconds.Add(second);
        firsts.Add(first);
    }

    // Note potential ambiguity using indexers (e.g. mapping from int to int)
    // Hence the methods as well...
    public IList<TSecond> this[TFirst first]
    {
        get { return GetByFirst(first); }
    }

    public IList<TFirst> this[TSecond second]
    {
        get { return GetBySecond(second); }
    }

    public IList<TSecond> GetByFirst(TFirst first)
    {
        IList<TSecond> list;
        if (!firstToSecond.TryGetValue(first, out list))
        {
            return EmptySecondList;
        }
        return new List<TSecond>(list); // Create a copy for sanity
    }

    public IList<TFirst> GetBySecond(TSecond second)
    {
        IList<TFirst> list;
        if (!secondToFirst.TryGetValue(second, out list))
        {
            return EmptyFirstList;
        }
        return new List<TFirst>(list); // Create a copy for sanity
    }
}

class Test
{
    static void Main()
    {
        BiDictionary<int, string> greek = new BiDictionary<int, string>();
        greek.Add(1, "Alpha");
        greek.Add(2, "Beta");
        greek.Add(5, "Beta");
        ShowEntries(greek, "Alpha");
        ShowEntries(greek, "Beta");
        ShowEntries(greek, "Gamma");
    }

    static void ShowEntries(BiDictionary<int, string> dict, string key)
    {
        IList<int> values = dict[key];
        StringBuilder builder = new StringBuilder();
        foreach (int value in values)
        {
            if (builder.Length != 0)
            {
                builder.Append(", ");
            }
            builder.Append(value);
        }
        Console.WriteLine("{0}: [{1}]", key, builder);
    }
}

2
จากสิ่งที่ฉันอ่านใน msdn นี่ควรเป็น BiLookup แทนที่จะเป็น BiDictionary หรือไม่? ไม่ใช่ว่ามันสำคัญหรืออะไรแค่อยากรู้ว่าฉันเข้าใจถูกต้องหรือไม่ ...
Svish

นอกจากนี้ฉันใช้ GetByFirst และคืน EmptySecondList เพิ่มบางอย่างเข้าไปแล้วเรียก GetByFirst อีกครั้งฉันจะไม่ได้รับรายชื่อพร้อมบางสิ่งในนั้นและไม่ใช่รายการที่ว่างเปล่า
Svish

@Svish: ไม่เพราะเมื่อคุณพยายามเพิ่มลงในรายการมันจะทำให้เกิดข้อยกเว้น (คุณไม่สามารถเพิ่มลงในอาร์เรย์ได้) และใช่ BiLookup น่าจะเป็นชื่อที่ดีกว่า
Jon Skeet

ในขณะที่ฉันเห็นสิ่งนี้ตอบคำถามของ OP แต่นี่เป็นการใช้งานที่ไร้เดียงสาหรือไม่? การใช้งานที่เป็นจริงจะไม่ใช่พจนานุกรม <> รายการ <> พจนานุกรมเพื่อให้คุณสามารถค้นหาวัตถุที่มีรูปแบบหลากหลายด้วยคีย์ที่แตกต่างกัน 2 ปุ่มได้หรือไม่?
Chris Marisic

@ChrisMarisic: ไม่แน่ใจว่าคุณหมายถึงอะไร - แต่บางอย่างเช่นนี้เป็นสิ่งที่ฉันใช้ค่อนข้างน้อยและไม่ต้องการอะไรเพิ่มเติม
Jon Skeet

74

อย่างที่คนอื่น ๆ พูดกันไม่มีการแมปภายในพจนานุกรมจากค่าไปยังคีย์

ฉันเพิ่งสังเกตเห็นว่าคุณต้องการแมปจากค่าเป็นหลายคีย์ - ฉันจะทิ้งโซลูชันนี้ไว้ที่นี่สำหรับเวอร์ชันค่าเดียว แต่ฉันจะเพิ่มคำตอบอื่นสำหรับแผนที่สองทิศทางแบบหลายรายการ

แนวทางปกติที่จะนำมาใช้ที่นี่คือการมีพจนานุกรมสองฉบับ - หนึ่งการทำแผนที่ทางเดียวและอีกแบบหนึ่ง ห่อหุ้มไว้ในคลาสแยกต่างหากและหาสิ่งที่คุณต้องการทำเมื่อคุณมีคีย์หรือค่าที่ซ้ำกัน (เช่นโยนข้อยกเว้นเขียนทับรายการที่มีอยู่หรือละเว้นรายการใหม่) โดยส่วนตัวแล้วฉันอาจจะโยนข้อยกเว้น - มันทำให้กำหนดพฤติกรรมแห่งความสำเร็จได้ง่ายขึ้น สิ่งนี้:

using System;
using System.Collections.Generic;

class BiDictionary<TFirst, TSecond>
{
    IDictionary<TFirst, TSecond> firstToSecond = new Dictionary<TFirst, TSecond>();
    IDictionary<TSecond, TFirst> secondToFirst = new Dictionary<TSecond, TFirst>();

    public void Add(TFirst first, TSecond second)
    {
        if (firstToSecond.ContainsKey(first) ||
            secondToFirst.ContainsKey(second))
        {
            throw new ArgumentException("Duplicate first or second");
        }
        firstToSecond.Add(first, second);
        secondToFirst.Add(second, first);
    }

    public bool TryGetByFirst(TFirst first, out TSecond second)
    {
        return firstToSecond.TryGetValue(first, out second);
    }

    public bool TryGetBySecond(TSecond second, out TFirst first)
    {
        return secondToFirst.TryGetValue(second, out first);
    }
}

class Test
{
    static void Main()
    {
        BiDictionary<int, string> greek = new BiDictionary<int, string>();
        greek.Add(1, "Alpha");
        greek.Add(2, "Beta");
        int x;
        greek.TryGetBySecond("Beta", out x);
        Console.WriteLine(x);
    }
}

1
ฉันไม่คิดว่าจะมีเหตุผลอะไรที่จะทำให้มันมาจากคลาสที่เป็นรูปธรรม - ฉันไม่ชอบการสืบทอดโดยปราศจากความคิดที่รอบคอบ - แต่มันสามารถใช้ IEnumerable ได้เป็นต้นในความเป็นจริงมันสามารถใช้ IDictionary <TFirst, TSecond> และ IDictionary <วินาทีแรก>
Jon Skeet

1
(แม้ว่ามันจะค่อนข้างแปลกถ้า TFirst และ TSecond เหมือนกัน ... )
Jon Skeet

6
จริงๆแล้วคุณไม่สามารถใช้ทั้ง IDictionary <TFirst, TSecond> และ IDictionary <TSecond, TFirst> ได้ในเวลาเดียวกัน NET 4.0 จะไม่อนุญาตให้ทำเช่นนั้น
Sebastian

2
@nawfal: หนึ่งในการAddเรียกพจนานุกรมจะล้มเหลว - แต่ถ้าเป็นการเรียกครั้งที่สองเราจะทำให้ระบบอยู่ในสถานะสับสน วิธีของฉันคุณยังคงมีคอลเลกชันที่สม่ำเสมอหลังจากข้อยกเว้น
Jon Skeet

1
@nawfal: ฉันไม่รู้ว่านั่นเป็นเหตุผลว่าทำไมฉันถึงทำอย่างนั้นเมื่อฉันเขียนคำตอบครั้งแรก ... ฉันเดา;)
Jon Skeet

26

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

var greek = new Dictionary<int, string> { { 1, "Alpha" }, { 2, "Alpha" } };

สิ่งที่คุณคาดหวังว่าจะได้รับgreek.WhatDoIPutHere("Alpha")?

ดังนั้นคุณไม่สามารถคาดหวังว่าสิ่งนี้จะถูกรวมเข้ากับกรอบงาน คุณต้องการวิธีการของคุณเองสำหรับการใช้งานเฉพาะของคุณเอง - คุณต้องการส่งคืนอาร์เรย์ (หรือIEnumerable<T>) หรือไม่? คุณต้องการทิ้งข้อยกเว้นหากมีหลายคีย์ที่มีค่าที่กำหนดหรือไม่? แล้วถ้าไม่มีล่ะ?

โดยส่วนตัวแล้วฉันจะแจกแจงเช่นนี้:

IEnumerable<TKey> KeysFromValue<TKey, TValue>(this Dictionary<TKey, TValue> dict, TValue val)
{
    if (dict == null)
    {
        throw new ArgumentNullException("dict");
    }
    return dict.Keys.Where(k => dict[k] == val);
}

var keys = greek.KeysFromValue("Beta");
int exceptionIfNotExactlyOne = greek.KeysFromValue("Beta").Single();

โซลูชันที่สวยงาม แต่ต้องใช้งานได้ใน 2.0 ค่าที่ซ้ำกันไม่น่าจะเป็นไปได้ แต่ไม่เป็นไปไม่ได้การคืนคอลเล็กชันจะดีกว่า
Dour High Arch

23

บางทีวิธีที่ง่ายที่สุดหากไม่มี Linq ก็สามารถวนซ้ำคู่ได้:

int betaKey; 
foreach (KeyValuePair<int, string> pair in lookup)
{
    if (pair.Value == value)
    {
        betaKey = pair.Key; // Found
        break;
    }
}
betaKey = -1; // Not found

หากคุณมี Linq สามารถทำได้อย่างง่ายดายด้วยวิธีนี้:

int betaKey = greek.SingleOrDefault(x => x.Value == "Beta").Key;

dour แต่คุณมีประเภท var ด้านบน?! แน่นอนคุณอยู่ใน 3.0? ดูการอัปเดตของฉันด้านล่างด้วย
dove

ขออภัยฉันใช้ "var" เพื่อลดการพิมพ์ ฉันไม่ต้องการทำการค้นหาเชิงเส้นเนื่องจากพจนานุกรมอาจมีขนาดใหญ่
Dour High Arch

2
varเป็นคุณลักษณะภาษาไม่ใช่คุณลักษณะกรอบ คุณสามารถใช้null-coalescing จาก C # -6.0 และยังคงกำหนดเป้าหมาย CF-2.0 ได้หากคุณต้องการจริงๆ
binki

3

พจนานุกรมไม่ได้เก็บค่าแฮชไว้เฉพาะคีย์ดังนั้นการค้นหาโดยใช้ค่าจะใช้เวลาเชิงเส้นเป็นอย่างน้อย ทางออกที่ดีที่สุดของคุณคือการทำซ้ำองค์ประกอบในพจนานุกรมและติดตามคีย์ที่ตรงกันหรือเปลี่ยนไปใช้โครงสร้างข้อมูลอื่นบางทีอาจใช้คีย์การแมปพจนานุกรมสองคีย์ -> ค่าและค่า -> List_of_keys หากคุณทำอย่างหลังคุณจะแลกเปลี่ยนพื้นที่เก็บข้อมูลเพื่อค้นหาความเร็ว ไม่ต้องใช้เวลามากนักในการเปลี่ยนตัวอย่าง @Cybis ให้เป็นโครงสร้างข้อมูลดังกล่าว


3

เนื่องจากฉันต้องการพจนานุกรม BiDirectional เต็มรูปแบบ (ไม่ใช่เฉพาะแผนที่) ฉันจึงเพิ่มฟังก์ชันที่ขาดหายไปเพื่อให้เป็นคลาสที่เข้ากันได้กับ IDictionary ซึ่งอ้างอิงจากเวอร์ชันที่มีคู่คีย์ - ค่าที่ไม่ซ้ำกัน นี่คือไฟล์หากต้องการ (งานส่วนใหญ่คือ XMLDoc ผ่าน):

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Common
{
    /// <summary>Represents a bidirectional collection of keys and values.</summary>
    /// <typeparam name="TFirst">The type of the keys in the dictionary</typeparam>
    /// <typeparam name="TSecond">The type of the values in the dictionary</typeparam>
    [System.Runtime.InteropServices.ComVisible(false)]
    [System.Diagnostics.DebuggerDisplay("Count = {Count}")]
    //[System.Diagnostics.DebuggerTypeProxy(typeof(System.Collections.Generic.Mscorlib_DictionaryDebugView<,>))]
    //[System.Reflection.DefaultMember("Item")]
    public class BiDictionary<TFirst, TSecond> : Dictionary<TFirst, TSecond>
    {
        IDictionary<TSecond, TFirst> _ValueKey = new Dictionary<TSecond, TFirst>();
        /// <summary> PropertyAccessor for Iterator over KeyValue-Relation </summary>
        public IDictionary<TFirst, TSecond> KeyValue => this;
        /// <summary> PropertyAccessor for Iterator over ValueKey-Relation </summary>
        public IDictionary<TSecond, TFirst> ValueKey => _ValueKey;

        #region Implemented members

        /// <Summary>Gets or sets the value associated with the specified key.</Summary>
        /// <param name="key">The key of the value to get or set.</param>
        /// <Returns>The value associated with the specified key. If the specified key is not found,
        ///      a get operation throws a <see cref="KeyNotFoundException"/>, and
        ///      a set operation creates a new element with the specified key.</Returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null.</exception>
        /// <exception cref="T:System.Collections.Generic.KeyNotFoundException">
        /// The property is retrieved and <paramref name="key"/> does not exist in the collection.</exception>
        /// <exception cref="T:System.ArgumentException"> An element with the same key already
        /// exists in the <see cref="ValueKey"/> <see cref="Dictionary&lt;TFirst,TSecond&gt;"/>.</exception>
        public new TSecond this[TFirst key]
        {
            get { return base[key]; }
            set { _ValueKey.Remove(base[key]); base[key] = value; _ValueKey.Add(value, key); }
        }

        /// <Summary>Gets or sets the key associated with the specified value.</Summary>
        /// <param name="val">The value of the key to get or set.</param>
        /// <Returns>The key associated with the specified value. If the specified value is not found,
        ///      a get operation throws a <see cref="KeyNotFoundException"/>, and
        ///      a set operation creates a new element with the specified value.</Returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="val"/> is null.</exception>
        /// <exception cref="T:System.Collections.Generic.KeyNotFoundException">
        /// The property is retrieved and <paramref name="val"/> does not exist in the collection.</exception>
        /// <exception cref="T:System.ArgumentException"> An element with the same value already
        /// exists in the <see cref="KeyValue"/> <see cref="Dictionary&lt;TFirst,TSecond&gt;"/>.</exception>
        public TFirst this[TSecond val]
        {
            get { return _ValueKey[val]; }
            set { base.Remove(_ValueKey[val]); _ValueKey[val] = value; base.Add(value, val); }
        }

        /// <Summary>Adds the specified key and value to the dictionary.</Summary>
        /// <param name="key">The key of the element to add.</param>
        /// <param name="value">The value of the element to add.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> or <paramref name="value"/> is null.</exception>
        /// <exception cref="T:System.ArgumentException">An element with the same key or value already exists in the <see cref="Dictionary&lt;TFirst,TSecond&gt;"/>.</exception>
        public new void Add(TFirst key, TSecond value) {
            base.Add(key, value);
            _ValueKey.Add(value, key);
        }

        /// <Summary>Removes all keys and values from the <see cref="Dictionary&lt;TFirst,TSecond&gt;"/>.</Summary>
        public new void Clear() { base.Clear(); _ValueKey.Clear(); }

        /// <Summary>Determines whether the <see cref="Dictionary&lt;TFirst,TSecond&gt;"/> contains the specified
        ///      KeyValuePair.</Summary>
        /// <param name="item">The KeyValuePair to locate in the <see cref="Dictionary&lt;TFirst,TSecond&gt;"/>.</param>
        /// <Returns>true if the <see cref="Dictionary&lt;TFirst,TSecond&gt;"/> contains an element with
        ///      the specified key which links to the specified value; otherwise, false.</Returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="item"/> is null.</exception>
        public bool Contains(KeyValuePair<TFirst, TSecond> item) => base.ContainsKey(item.Key) & _ValueKey.ContainsKey(item.Value);

        /// <Summary>Removes the specified KeyValuePair from the <see cref="Dictionary&lt;TFirst,TSecond&gt;"/>.</Summary>
        /// <param name="item">The KeyValuePair to remove.</param>
        /// <Returns>true if the KeyValuePair is successfully found and removed; otherwise, false. This
        ///      method returns false if <paramref name="item"/> is not found in the <see cref="Dictionary&lt;TFirst,TSecond&gt;"/>.</Returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="item"/> is null.</exception>
        public bool Remove(KeyValuePair<TFirst, TSecond> item) => base.Remove(item.Key) & _ValueKey.Remove(item.Value);

        /// <Summary>Removes the value with the specified key from the <see cref="Dictionary&lt;TFirst,TSecond&gt;"/>.</Summary>
        /// <param name="key">The key of the element to remove.</param>
        /// <Returns>true if the element is successfully found and removed; otherwise, false. This
        ///      method returns false if <paramref name="key"/> is not found in the <see cref="Dictionary&lt;TFirst,TSecond&gt;"/>.</Returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null.</exception>
        public new bool Remove(TFirst key) => _ValueKey.Remove(base[key]) & base.Remove(key);

        /// <Summary>Gets the key associated with the specified value.</Summary>
        /// <param name="value">The value of the key to get.</param>
        /// <param name="key">When this method returns, contains the key associated with the specified value,
        ///      if the value is found; otherwise, the default value for the type of the key parameter.
        ///      This parameter is passed uninitialized.</param>
        /// <Returns>true if <see cref="ValueKey"/> contains an element with the specified value; 
        ///      otherwise, false.</Returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="value"/> is null.</exception>
        public bool TryGetValue(TSecond value, out TFirst key) => _ValueKey.TryGetValue(value, out key);
        #endregion
    }
}

2

แก้ไข: โอเคที่จะมีการค้นหาบางอย่างคุณจะต้องมีสิ่งอื่นนอกเหนือจากพจนานุกรมเพราะถ้าคุณคิดว่าพจนานุกรมเป็นกุญแจทางเดียว นั่นคือค่าอาจไม่ซ้ำกัน

ที่บอกว่าดูเหมือนว่าคุณกำลังใช้ c # 3.0 ดังนั้นคุณอาจไม่ต้องหันไปใช้การวนซ้ำและสามารถใช้สิ่งต่างๆเช่น:

var key = (from k in yourDictionary where string.Compare(k.Value, "yourValue", true)  == 0 select k.Key).FirstOrDefault();

พจนานุกรมไม่มี. FindByValue ฉันอยากจะย้ายไปยังโครงสร้างข้อมูลอื่นมากกว่าที่จะวนซ้ำค่า
Dour High Arch

2

คลาสพจนานุกรมไม่เหมาะสำหรับกรณีนี้ แต่ถ้าคุณต้องการทำจริงๆ (ใน C # 2.0) คุณสามารถทำได้:

public List<TKey> GetKeysFromValue<TKey, TVal>(Dictionary<TKey, TVal> dict, TVal val)
{
   List<TKey> ks = new List<TKey>();
   foreach(TKey k in dict.Keys)
   {
      if (dict[k] == val) { ks.Add(k); }
   }
   return ks;
}

ฉันชอบโซลูชัน LINQ เพื่อความสง่างาม แต่นี่เป็นวิธี 2.0


1

คุณไม่สามารถสร้างคลาสย่อยของพจนานุกรมที่มีฟังก์ชันนั้นได้หรือไม่?


    public class MyDict < TKey, TValue > : Dictionary < TKey, TValue >
    {
        private Dictionary < TValue, TKey > _keys;

        public TValue this[TKey key]
        {
            get
            {
                return base[key];
            }
            set 
            { 
                base[key] = value;
                _keys[value] = key;
            }
        }

        public MyDict()
        {
            _keys = new Dictionary < TValue, TKey >();
        }

        public TKey GetKeyFromValue(TValue value)
        {
            return _keys[value];
        }
    }

แก้ไข: ขออภัยครั้งแรกไม่ได้รับรหัส


นั่นจะเป็นเพียงการเปลี่ยนสิ่งที่ฉันใช้สำหรับคีย์และส่งคืนเฉพาะค่า int ของคีย์สตริงเท่านั้นฉันต้องไปทั้งสองวิธี และตามที่ Domenic ชี้ให้เห็นฉันสามารถมีค่าสตริงที่ซ้ำกันได้
Dour High Arch

หากคุณสามารถมีค่าสตริงที่ซ้ำกันสำหรับคีย์ int ของคุณคุณคาดหวังอะไรที่จะได้รับกลับมาเมื่อคุณค้นหาด้วยสตริง วัตถุรายการของ int ที่เกี่ยวข้อง?
Cybis

1

โซลูชันพจนานุกรมสองทิศทาง "แบบธรรมดา" ที่นำเสนอในที่นี้มีความซับซ้อนและอาจยากที่จะทำความเข้าใจดูแลรักษาหรือขยายเวลา คำถามเดิมยังถามถึง "คีย์สำหรับค่า" แต่เห็นได้ชัดว่าอาจมีหลายคีย์ (ฉันแก้ไขคำถามแล้ว) วิธีการทั้งหมดค่อนข้างน่าสงสัย

การเปลี่ยนแปลงซอฟต์แวร์ การเขียนโค้ดที่ดูแลรักษาง่ายควรให้ความสำคัญกับวิธีแก้ปัญหาที่ซับซ้อน "ฉลาด" อื่น ๆ วิธีรับคีย์คืนจากค่าในพจนานุกรมคือการวนซ้ำ พจนานุกรมไม่ได้ออกแบบให้เป็นแบบสองทิศทาง


หรืออาจเป็นพจนานุกรมที่สองที่จับคู่ค่าแต่ละค่ากับคีย์ของมัน
DavidRR

@DavidRR คีย์ต้องไม่ซ้ำกันเท่านั้นดังนั้นวิธีการพจนานุกรมที่สองจะไม่ได้ผลจริงๆ แต่คุณสามารถวนซ้ำในพจนานุกรมเพื่อรับคีย์สำหรับค่าได้
Max Hodges

ถ้าโทรปัญหาสำหรับพจนานุกรมให้การสนับสนุนหลายintค่าต่อคีย์แล้วพจนานุกรมสามารถกำหนดเช่นนี้string Dictionary<string, List<int>>
DavidRR

ทีนี้จะสร้างแบบสองทิศทางโดยไม่ต้องวนซ้ำได้อย่างไร
Max Hodges

สำหรับคำถามของ OP นั้นมาตรฐานDictionaryไม่ได้นำเสนอความสามารถแบบสองทิศทาง ดังนั้นหากทั้งหมดที่คุณมีเป็นมาตรฐานDictionaryและคุณต้องการค้นหาคีย์ที่เกี่ยวข้องกับค่าเฉพาะคุณต้องทำซ้ำ! อย่างไรก็ตามสำหรับพจนานุกรม "ขนาดใหญ่" การทำซ้ำอาจส่งผลให้ประสิทธิภาพไม่ดี โปรดทราบว่าคำตอบที่ฉันเสนอนั้นขึ้นอยู่กับการทำซ้ำ (ผ่าน LINQ) หากค่าเริ่มต้นของคุณDictionaryไม่มีการเปลี่ยนแปลงเพิ่มเติมคุณสามารถสร้างการย้อนกลับDictionaryหนึ่งครั้งเพื่อเพิ่มความเร็วในการค้นหาแบบย้อนกลับ
DavidRR

1

ใช้LINQเพื่อทำการDictionary<K, V>ค้นหาแบบย้อนกลับ แต่โปรดทราบว่าค่านิยมของคุณDictionary<K, V>อาจไม่แตกต่างกัน

สาธิต:

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

class ReverseDictionaryLookupDemo
{
    static void Main()
    {
        var dict = new Dictionary<int, string>();
        dict.Add(4, "Four");
        dict.Add(5, "Five");
        dict.Add(1, "One");
        dict.Add(11, "One"); // duplicate!
        dict.Add(3, "Three");
        dict.Add(2, "Two");
        dict.Add(44, "Four"); // duplicate!

        Console.WriteLine("\n== Enumerating Distinct Values ==");
        foreach (string value in dict.Values.Distinct())
        {
            string valueString =
                String.Join(", ", GetKeysFromValue(dict, value));

            Console.WriteLine("{0} => [{1}]", value, valueString);
        }
    }

    static List<int> GetKeysFromValue(Dictionary<int, string> dict, string value)
    {
        // Use LINQ to do a reverse dictionary lookup.
        // Returns a 'List<T>' to account for the possibility
        // of duplicate values.
        return
            (from item in dict
             where item.Value.Equals(value)
             select item.Key).ToList();
    }
}

ผลลัพธ์ที่คาดหวัง:

== Enumerating Distinct Values ==
Four => [4, 44]
Five => [5]
One => [1, 11]
Three => [3]
Two => [2]

1
ปัญหาที่ฉันเห็นคือคุณกำลังตรวจสอบทุกองค์ประกอบในพจนานุกรมเพื่อให้ได้ทิศทางย้อนกลับ เวลาค้นหา O (n) เอาชนะวัตถุประสงค์ของการใช้พจนานุกรม ควรเป็น O (1)
stephen

@stephen - เห็นด้วย ตามที่คนอื่น ๆ ได้ชี้ให้เห็นว่าหากประสิทธิภาพเป็นสิ่งสำคัญยิ่งพจนานุกรมแยกต่างหากสำหรับค่าหรือพจนานุกรมสองทิศทางก็จะเหมาะสม อย่างไรก็ตามหากจำเป็นต้องทำการค้นหาค่าไม่บ่อยนักและประสิทธิภาพในการดำเนินการนั้นเป็นที่ยอมรับแนวทางที่ฉันสรุปไว้ที่นี่อาจเป็นสิ่งที่ควรค่าแก่การพิจารณา ที่กล่าวว่าการใช้ LINQ ในคำตอบของฉันไม่เข้ากันได้กับความต้องการของ OP สำหรับโซลูชันที่เหมาะสำหรับใช้กับ. NET 2.0 (แม้ว่าข้อ จำกัด . NET 2.0 จะมีโอกาสน้อยกว่าในปี 2014)
DavidRR

1
Dictionary<string, string> dic = new Dictionary<string, string>();
dic["A"] = "Ahmed";
dic["B"] = "Boys";

foreach (string mk in dic.Keys)
{
    if(dic[mk] == "Ahmed")
    {
        Console.WriteLine("The key that contains \"Ahmed\" is " + mk);
    }
}

1
ขอบคุณสำหรับการโพสต์คำตอบ! ในขณะที่ข้อมูลโค้ดสามารถตอบคำถามได้ แต่ก็ยังดีที่จะเพิ่มข้อมูลเพิ่มเติมรอบ ๆ เช่นอธิบายเป็นต้น
j0k

0

เป็นการบิดคำตอบที่ยอมรับ ( https://stackoverflow.com/a/255638/986160 ) สมมติว่าคีย์จะเชื่อมโยงกับค่าสัญญาณในพจนานุกรม คล้ายกับ ( https://stackoverflow.com/a/255630/986160 ) แต่ดูหรูหรากว่าเล็กน้อย ความแปลกใหม่อยู่ที่คลาสการบริโภคสามารถใช้เป็นทางเลือกในการแจงนับ (แต่สำหรับสตริงด้วย) และพจนานุกรมนั้นใช้ IEnumerable

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

namespace MyApp.Dictionaries
{

    class BiDictionary<TFirst, TSecond> : IEnumerable
    {
        IDictionary<TFirst, TSecond> firstToSecond = new Dictionary<TFirst, TSecond>();
        IDictionary<TSecond, TFirst> secondToFirst = new Dictionary<TSecond, TFirst>();

        public void Add(TFirst first, TSecond second)
        {
            firstToSecond.Add(first, second);
            secondToFirst.Add(second, first);
        }

        public TSecond this[TFirst first]
        {
            get { return GetByFirst(first); }
        }

        public TFirst this[TSecond second]
        {
            get { return GetBySecond(second); }
        }

        public TSecond GetByFirst(TFirst first)
        {
            return firstToSecond[first];
        }

        public TFirst GetBySecond(TSecond second)
        {
            return secondToFirst[second];
        }

        public IEnumerator GetEnumerator()
        {
            return GetFirstEnumerator();
        }

        public IEnumerator GetFirstEnumerator()
        {
            return firstToSecond.GetEnumerator();
        }

        public IEnumerator GetSecondEnumerator()
        {
            return secondToFirst.GetEnumerator();
        }
    }
}

และในฐานะชนชั้นบริโภคที่คุณสามารถมีได้

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

namespace MyApp.Dictionaries
{
    class Greek
    {

        public static readonly string Alpha = "Alpha";
        public static readonly string Beta = "Beta";
        public static readonly string Gamma = "Gamma";
        public static readonly string Delta = "Delta";


        private static readonly BiDictionary<int, string> Dictionary = new BiDictionary<int, string>();


        static Greek() {
            Dictionary.Add(1, Alpha);
            Dictionary.Add(2, Beta);
            Dictionary.Add(3, Gamma);
            Dictionary.Add(4, Delta);
        }

        public static string getById(int id){
            return Dictionary.GetByFirst(id);
        }

        public static int getByValue(string value)
        {
            return Dictionary.GetBySecond(value);
        }

    }
}

1
โดยพื้นฐานแล้วจะเหมือนกับคำตอบที่โพสต์เมื่อหกปีที่แล้วและตามที่ระบุไว้คีย์จะไม่เกี่ยวข้องกับค่าเดียว แต่ละคีย์อาจมีหลายค่า
Dour High Arch

ฉันรู้ดี แต่เวอร์ชันของฉันใช้ IEnumerable และมีความสง่างามมากขึ้น .. นอกจากนี้ตัวอย่างคลาสการบริโภคยังทำให้คลาส BiDictionary มีระดับการใช้งานที่แตกต่างกันซึ่งช่วยแก้ปัญหาการคงที่ของสตริงและรหัสที่ C # ไม่ได้จัดเตรียมไว้ให้ ฉันยังอ้างถึงถ้าคุณอ่านคำตอบของฉัน!
Michail Michailidis

0

แล้วการแก้ปัญหาของฆราวาส

ฟังก์ชันที่คล้ายกับฟังก์ชันด้านล่างนี้สามารถเขียนขึ้นเพื่อสร้างพจนานุกรมได้:

    public Dictionary<TValue, TKey> Invert(Dictionary<TKey, TValue> dict) {
    Dictionary<TValue, TKey> ret = new Dictionary<TValue, TKey>();
    foreach (var kvp in dict) {ret[kvp.value] = kvp.key;} return ret; }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.