.NET 사전에 중복 키가 있습니까?


256

.NET 기본 클래스 라이브러리에 중복 키를 사용할 수있는 사전 클래스가 있습니까? 내가 찾은 유일한 해결책은 예를 들어 다음과 같은 클래스를 만드는 것입니다.

Dictionary<string, List<object>>

그러나 이것은 실제로 사용하는 데 매우 자극적입니다. Java에서는 MultiMap 이이 작업을 수행하지만 .NET에서 아날로그를 찾을 수 없다고 생각합니다.


3
이 중복 키는 어떻게 중복 값 (목록)입니까?
Shamim Hafiz

1
@ShamimHafiz, 아니요, 값은 중복 될 필요가 없습니다. 당신이 저장 중복에있는 경우 { a, 1 }{ a, 2 }위치를 해시 테이블에서 a키있는 하나 개의 대안은 것입니다 { a, [1, 2] }.
nawfal

1
실제로, 여기서 실제로 원하는 것은 각 키가 하나 이상의 값에 매핑 될 수있는 모음입니다. "중복 키"라는 표현이 실제로 이것을 전달하지는 않는다고 생각합니다.
DavidRR

나중에 참조 할 수 있도록 동일한 키를 반복해서 추가하는 대신 1 개의 키만 값을 추가하는 것을 고려해야합니다.
Ya Wang

키와 값이 모두 문자열 인 경우 NameValueCollection (여러 문자열 값을 각 문자열 키와 연결할 수 있음)이 있습니다.
AnorZaken

답변:


228

.NET 3.5를 사용하는 경우 Lookup 클래스를 .

편집 : 당신은 일반적으로 사용 Lookup하여Enumerable.ToLookup . 이것은 나중에 변경하지 않아도된다고 가정하지만 일반적으로 충분합니다.

그 경우 하지 않는 당신을 위해 일을, 내가 도움이 될 것입니다 프레임 워크에서 아무것도 생각하지 않습니다 - 그리고 사전을 사용하는 것이 좋은가수록 같다가 :(


Lookup에 참여해 주셔서 감사합니다. 표준 순서 기준이 아닌 linq 쿼리에서 결과를 분할 (그룹화)하는 좋은 방법을 제공합니다.
Robert Paulson

3
@Josh : Enumerable.ToLookup을 사용하여 하나를 만듭니다.
Jon Skeet

29
주의 말씀 : Lookup직렬화 할 수없는
SliverNinja - MSFT

1
해당 조회에 항목을 어떻게 추가해야합니까?
moldovanu

171

List 클래스는 실제로 컬렉션을 반복하려는 중복이 포함 된 키 / 값 컬렉션에 매우 효과적입니다. 예:

List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>();

// add some values to the collection here

for (int i = 0;  i < list.Count;  i++)
{
    Print(list[i].Key, list[i].Value);
}

31
이 솔루션은 기능적으로 작동하지만 List 구현에는 키 또는 값에 대한 지식이 없으며 키 검색을 전혀 최적화 할 수 없습니다.
Spencer Rose

41

List <KeyValuePair <string, string>>로이를 수행하는 한 가지 방법이 있습니다.

public class ListWithDuplicates : List<KeyValuePair<string, string>>
{
    public void Add(string key, string value)
    {
        var element = new KeyValuePair<string, string>(key, value);
        this.Add(element);
    }
}

var list = new ListWithDuplicates();
list.Add("k1", "v1");
list.Add("k1", "v2");
list.Add("k1", "v3");

foreach(var item in list)
{
    string x = string.format("{0}={1}, ", item.Key, item.Value);
}

출력 k1 = v1, k1 = v2, k1 = v3


내 시나리오에서 잘 작동하고 사용하기 쉽습니다.
user6121177

21

문자열을 키와 값으로 사용하는 경우 System.Collections.Specialized.NameValueCollection 을 사용 하면 GetValues ​​(string key) 메서드를 통해 문자열 값 배열을 반환합니다.


6
NameValueCollection은 여러 키를 허용하지 않습니다.
Jenny O'Reilly

@Jenny O'Reilly : 물론, 중복 키를 추가 할 수 있습니다.
isHuman

엄밀히 말해 @ JennyO'Reilly는 링크 된 MSDN 페이지의 설명에 명확하게 나와 있으므로 정확합니다. 이 클래스는 단일 키 아래에 여러 문자열 값을 저장합니다 .
Ronald

그것은 허용하지만 여러 값을 반환 할 것입니다. 인덱스와 키를 사용해 보았습니다.
user6121177

18

나는 방금 MultiDictionary라는 클래스를 포함 하는 PowerCollections 라이브러리를 발견했습니다. 이것은 이런 유형의 기능을 깔끔하게 마무리합니다.


14

조회 사용에 관한 매우 중요한 참고 사항 :

구현하는 객체를 Lookup(TKey, TElement)호출 하여의 인스턴스를 만들 수 있습니다ToLookupIEnumerable(T)

의 새 인스턴스를 생성하는 공개 생성자가 없습니다 Lookup(TKey, TElement). 또한 Lookup(TKey, TElement)개체는 변경할 수 없습니다. 즉 , 개체 나 키를 Lookup(TKey, TElement)만든 후에 는 개체 나 키를 추가하거나 제거 할 수 없습니다 .

(MSDN에서)

나는 이것이 대부분의 사용을위한 쇼 스토퍼가 될 것이라고 생각합니다.


6
나는 이것이 쇼 스토퍼가 될 곳이 거의 없다고 생각할 수 있습니다. 그러나 나는 불변의 물건이 훌륭하다고 생각합니다.
Joel Mueller

1
@JoelMueller 나는 이것이 쇼 스토퍼 인 많은 경우를 생각할 수 있습니다. 항목을 추가하기 위해 사전을 다시 만들어야하는 것은 특별히 효율적인 해결책은 아닙니다.
reirab

12

나는 List<KeyValuePair<object, object>>욥 과 같은 일을 할 것이라고 생각합니다.


이 목록에서 열쇠를 어떻게 찾아 보나요?
Wayne Bloss

당신은 그것을 반복해야하지만 : .NET 3.5의 LookUp-Class를 알지 못했습니다 : 아마도 내용을 검색하는 데 더 유용 할 것입니다.
MADMap

2
@wizlib : 유일한 방법은 목록을 반복하는 것입니다. 해시만큼 효율적이지 않습니다. -1
petr k.

@ 페덱. 그것은 실제로 데이터가 무엇인지에 달려 있습니다. 고유 키가 매우 적고 해시 충돌의 오버 헤드를 원하지 않기 때문에이 구현을 사용했습니다. +1
Evan M

9

> = .NET 4를 사용하는 경우 Tuple클래스 를 사용할 수 있습니다 .

// declaration
var list = new List<Tuple<string, List<object>>>();

// to add an item to the list
var item = Tuple<string, List<object>>("key", new List<object>);
list.Add(item);

// to iterate
foreach(var i in list)
{
    Console.WriteLine(i.Item1.ToString());
}

이것은 List<KeyValuePair<key, value>>위와 같은 솔루션처럼 보입니다 . 내가 잘못?
Nathan Goings 2016

6

"중복 키"항목을 허용하는 사전의 "자신의"버전을 쉽게 롤백 할 수 있습니다. 다음은 대략적인 간단한 구현입니다. 기본적으로 대부분에 대한 지원을 추가하는 것을 고려할 수도 있습니다 IDictionary<T>.

public class MultiMap<TKey,TValue>
{
    private readonly Dictionary<TKey,IList<TValue>> storage;

    public MultiMap()
    {
        storage = new Dictionary<TKey,IList<TValue>>();
    }

    public void Add(TKey key, TValue value)
    {
        if (!storage.ContainsKey(key)) storage.Add(key, new List<TValue>());
        storage[key].Add(value);
    }

    public IEnumerable<TKey> Keys
    {
        get { return storage.Keys; }
    }

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

    public IList<TValue> this[TKey key]
    {
        get
        {
            if (!storage.ContainsKey(key))
                throw new KeyNotFoundException(
                    string.Format(
                        "The given key {0} was not found in the collection.", key));
            return storage[key];
        }
    }
}

사용 방법에 대한 간단한 예 :

const string key = "supported_encodings";
var map = new MultiMap<string,Encoding>();
map.Add(key, Encoding.ASCII);
map.Add(key, Encoding.UTF8);
map.Add(key, Encoding.Unicode);

foreach (var existingKey in map.Keys)
{
    var values = map[existingKey];
    Console.WriteLine(string.Join(",", values));
}


3

NameValueCollection은 하나의 키 (문자열이기도 함)에서 여러 문자열 값을 지원하지만 내가 아는 유일한 예입니다.

나는 그런 종류의 기능이 필요한 상황에 처할 때 귀하의 예와 비슷한 구조를 만드는 경향이 있습니다.


3

사용하는 경우 List<KeyValuePair<string, object>>옵션을, 당신은 검색을 수행 LINQ를 사용할 수 있습니다 :

List<KeyValuePair<string, object>> myList = new List<KeyValuePair<string, object>>();
//fill it here
var q = from a in myList Where a.Key.Equals("somevalue") Select a.Value
if(q.Count() > 0){ //you've got your value }

2
그렇습니다.하지만 더 빨라지는 않습니다 (아직 해싱 없음)
Haukman

3

새로운 C # (7.0에서 온 것입니다) 이후로 다음과 같이 할 수도 있습니다.

var duplicatedDictionaryExample = new List<(string Key, string Value)> { ("", "") ... }

표준 목록으로 사용하고 있지만 원하는 이름을 가진 두 개의 값이 있습니다.

foreach(var entry in duplicatedDictionaryExample)
{ 
    // do something with the values
    entry.Key;
    entry.Value;
}

2

실제 복제본이 아니라 합동을 의미합니까? 그렇지 않으면 해시 테이블이 작동하지 않습니다.

합동은 두 개의 개별 키가 동등한 값으로 해시 할 수 있지만 키가 동일하지 않음을 의미합니다.

예를 들어, 해시 테이블의 해시 함수는 hashval = key mod 3이라고 가정하십시오. 1과 4는 모두 1에 매핑되지만 다른 값입니다. 이것은 목록에 대한 아이디어가 작용하는 곳입니다.

1을 조회해야하는 경우 해당 값은 1로 해시되며 Key = 1을 찾을 때까지 목록이 순회됩니다.

중복 키를 삽입 할 수 있으면 어떤 키가 어떤 값에 매핑되는지 구별 할 수 없습니다.


2
해시 테이블은 이미 동일한 값으로 해시되는 키를 처리합니다 (충돌이라고 함). 여러 값을 동일한 정확한 키에 매핑하려는 상황을 언급하고 있습니다.

2

내가 사용하는 방식은

Dictionary<string, List<string>>

이렇게하면 문자열 목록을 보유하는 단일 키가 있습니다.

예:

List<string> value = new List<string>();
if (dictionary.Contains(key)) {
     value = dictionary[key];
}
value.Add(newValue);

훌륭하고 깨끗합니다.
Jerry Nixon

이것은 내 유스 케이스를 처리하는 훌륭한 솔루션입니다.
더빙 스타일

1

나는 같은 대답을 찾기 위해이 게시물을 우연히 발견했지만 아무것도 찾지 못했기 때문에 사전 목록을 사용하여 기본 예제 솔루션을 리깅하여 [] 연산자를 재정 의하여 다른 모든 사람들이 목록에 새 사전을 추가하도록했습니다. 주어진 키 (set), 값 목록 (get)을 반환합니다.
추악하고 비효율적이며 키로 만 가져오고 설정하며 항상 목록을 반환하지만 작동합니다.

 class DKD {
        List<Dictionary<string, string>> dictionaries;
        public DKD(){
            dictionaries = new List<Dictionary<string, string>>();}
        public object this[string key]{
             get{
                string temp;
                List<string> valueList = new List<string>();
                for (int i = 0; i < dictionaries.Count; i++){
                    dictionaries[i].TryGetValue(key, out temp);
                    if (temp == key){
                        valueList.Add(temp);}}
                return valueList;}
            set{
                for (int i = 0; i < dictionaries.Count; i++){
                    if (dictionaries[i].ContainsKey(key)){
                        continue;}
                    else{
                        dictionaries[i].Add(key,(string) value);
                        return;}}
                dictionaries.Add(new Dictionary<string, string>());
                dictionaries.Last()[key] =(string)value;
            }
        }
    }

1

@Hector Correa의 답변을 일반 유형의 확장으로 변경하고 사용자 지정 TryGetValue를 추가했습니다.

  public static class ListWithDuplicateExtensions
  {
    public static void Add<TKey, TValue>(this List<KeyValuePair<TKey, TValue>> collection, TKey key, TValue value)
    {
      var element = new KeyValuePair<TKey, TValue>(key, value);
      collection.Add(element);
    }

    public static int TryGetValue<TKey, TValue>(this List<KeyValuePair<TKey, TValue>> collection, TKey key, out IEnumerable<TValue> values)
    {
      values = collection.Where(pair => pair.Key.Equals(key)).Select(pair => pair.Value);
      return values.Count();
    }
  }

0

이것은 동시대 사전입니다. 이것이 도움이 될 것이라고 생각합니다.

public class HashMapDictionary<T1, T2> : System.Collections.IEnumerable
{
    private System.Collections.Concurrent.ConcurrentDictionary<T1, List<T2>> _keyValue = new System.Collections.Concurrent.ConcurrentDictionary<T1, List<T2>>();
    private System.Collections.Concurrent.ConcurrentDictionary<T2, List<T1>> _valueKey = new System.Collections.Concurrent.ConcurrentDictionary<T2, List<T1>>();

    public ICollection<T1> Keys
    {
        get
        {
            return _keyValue.Keys;
        }
    }

    public ICollection<T2> Values
    {
        get
        {
            return _valueKey.Keys;
        }
    }

    public int Count
    {
        get
        {
            return _keyValue.Count;
        }
    }

    public bool IsReadOnly
    {
        get
        {
            return false;
        }
    }

    public List<T2> this[T1 index]
    {
        get { return _keyValue[index]; }
        set { _keyValue[index] = value; }
    }

    public List<T1> this[T2 index]
    {
        get { return _valueKey[index]; }
        set { _valueKey[index] = value; }
    }

    public void Add(T1 key, T2 value)
    {
        lock (this)
        {
            if (!_keyValue.TryGetValue(key, out List<T2> result))
                _keyValue.TryAdd(key, new List<T2>() { value });
            else if (!result.Contains(value))
                result.Add(value);

            if (!_valueKey.TryGetValue(value, out List<T1> result2))
                _valueKey.TryAdd(value, new List<T1>() { key });
            else if (!result2.Contains(key))
                result2.Add(key);
        }
    }

    public bool TryGetValues(T1 key, out List<T2> value)
    {
        return _keyValue.TryGetValue(key, out value);
    }

    public bool TryGetKeys(T2 value, out List<T1> key)
    {
        return _valueKey.TryGetValue(value, out key);
    }

    public bool ContainsKey(T1 key)
    {
        return _keyValue.ContainsKey(key);
    }

    public bool ContainsValue(T2 value)
    {
        return _valueKey.ContainsKey(value);
    }

    public void Remove(T1 key)
    {
        lock (this)
        {
            if (_keyValue.TryRemove(key, out List<T2> values))
            {
                foreach (var item in values)
                {
                    var remove2 = _valueKey.TryRemove(item, out List<T1> keys);
                }
            }
        }
    }

    public void Remove(T2 value)
    {
        lock (this)
        {
            if (_valueKey.TryRemove(value, out List<T1> keys))
            {
                foreach (var item in keys)
                {
                    var remove2 = _keyValue.TryRemove(item, out List<T2> values);
                }
            }
        }
    }

    public void Clear()
    {
        _keyValue.Clear();
        _valueKey.Clear();
    }

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

예 :

public class TestA
{
    public int MyProperty { get; set; }
}

public class TestB
{
    public int MyProperty { get; set; }
}

            HashMapDictionary<TestA, TestB> hashMapDictionary = new HashMapDictionary<TestA, TestB>();

            var a = new TestA() { MyProperty = 9999 };
            var b = new TestB() { MyProperty = 60 };
            var b2 = new TestB() { MyProperty = 5 };
            hashMapDictionary.Add(a, b);
            hashMapDictionary.Add(a, b2);
            hashMapDictionary.TryGetValues(a, out List<TestB> result);
            foreach (var item in result)
            {
                //do something
            }

0

이 간단한 클래스를 사용합니다.

public class ListMap<T,V> : List<KeyValuePair<T, V>>
{
    public void Add(T key, V value) {
        Add(new KeyValuePair<T, V>(key, value));
    }

    public List<V> Get(T key) {
        return FindAll(p => p.Key.Equals(key)).ConvertAll(p=> p.Value);
    }
}

용법:

var fruits = new ListMap<int, string>();
fruits.Add(1, "apple");
fruits.Add(1, "orange");
var c = fruits.Get(1).Count; //c = 2;

0

null 값을 키로 지원하는 보너스로 다음과 같은 사전 래퍼를 만들 수 있습니다.

/// <summary>
/// Dictionary which supports duplicates and null entries
/// </summary>
/// <typeparam name="TKey">Type of key</typeparam>
/// <typeparam name="TValue">Type of items</typeparam>
public class OpenDictionary<TKey, TValue>
{
    private readonly Lazy<List<TValue>> _nullStorage = new Lazy<List<TValue>>(
        () => new List<TValue>());

    private readonly Dictionary<TKey, List<TValue>> _innerDictionary =
        new Dictionary<TKey, List<TValue>>();

    /// <summary>
    /// Get all entries
    /// </summary>
    public IEnumerable<TValue> Values =>
        _innerDictionary.Values
            .SelectMany(x => x)
            .Concat(_nullStorage.Value);

    /// <summary>
    /// Add an item
    /// </summary>
    public OpenDictionary<TKey, TValue> Add(TKey key, TValue item)
    {
        if (ReferenceEquals(key, null))
            _nullStorage.Value.Add(item);
        else
        {
            if (!_innerDictionary.ContainsKey(key))
                _innerDictionary.Add(key, new List<TValue>());

            _innerDictionary[key].Add(item);
        }

        return this;
    }

    /// <summary>
    /// Remove an entry by key
    /// </summary>
    public OpenDictionary<TKey, TValue> RemoveEntryByKey(TKey key, TValue entry)
    {
        if (ReferenceEquals(key, null))
        {
            int targetIdx = _nullStorage.Value.FindIndex(x => x.Equals(entry));
            if (targetIdx < 0)
                return this;

            _nullStorage.Value.RemoveAt(targetIdx);
        }
        else
        {
            if (!_innerDictionary.ContainsKey(key))
                return this;

            List<TValue> targetChain = _innerDictionary[key];
            if (targetChain.Count == 0)
                return this;

            int targetIdx = targetChain.FindIndex(x => x.Equals(entry));
            if (targetIdx < 0)
                return this;

            targetChain.RemoveAt(targetIdx);
        }

        return this;
    }

    /// <summary>
    /// Remove all entries by key
    /// </summary>
    public OpenDictionary<TKey, TValue> RemoveAllEntriesByKey(TKey key)
    {
        if (ReferenceEquals(key, null))
        {
            if (_nullStorage.IsValueCreated)
                _nullStorage.Value.Clear();
        }       
        else
        {
            if (_innerDictionary.ContainsKey(key))
                _innerDictionary[key].Clear();
        }

        return this;
    }

    /// <summary>
    /// Try get entries by key
    /// </summary>
    public bool TryGetEntries(TKey key, out IReadOnlyList<TValue> entries)
    {
        entries = null;

        if (ReferenceEquals(key, null))
        {
            if (_nullStorage.IsValueCreated)
            {
                entries = _nullStorage.Value;
                return true;
            }
            else return false;
        }
        else
        {
            if (_innerDictionary.ContainsKey(key))
            {
                entries = _innerDictionary[key];
                return true;
            }
            else return false;
        }
    }
}

사용 샘플 :

var dictionary = new OpenDictionary<string, int>();
dictionary.Add("1", 1); 
// The next line won't throw an exception; 
dictionary.Add("1", 2);

dictionary.TryGetEntries("1", out List<int> result); 
// result is { 1, 2 }

dictionary.Add(null, 42);
dictionary.Add(null, 24);
dictionary.TryGetEntries(null, out List<int> result); 
// result is { 42, 24 }

코드의 기능, 질문에 대한 답변 및 예제 사용법에 대해 설명 할 수 있습니까?
Enigmativity

@ Enigmativity, 그것은 정확히 무엇을 요구했는지, 질문은 중복을 지원하는 일부 사전을 제안하는 것이 었습니다. null을 키로 처리하십시오 (실제로 나쁜 연습이더라도) 사용법은 매우 간단합니다. var dictionary = new OpenDictionary<string, int>(); dictionary.Add("1", 1); // The next line won't throw an exception; dictionary.Add("1", 2); dictionary.TryGetEntries("1", out List<int> result); // result is { 1, 2 }
Alexander Tolstikov

답변에 세부 정보를 추가 할 수 있습니까?
Enigmativity

@Enigmativity, 당신이 원래의 대답을 의미한다면, 확실히
Alexander Tolstikov

-1

U는 사전을 사용하려는 모든 위치에서 복합 문자열 키를 작성하는 방법을 정의 할 수 있습니다.

private string keyBuilder(int key1, int key2)
{
    return string.Format("{0}/{1}", key1, key2);
}

사용하기 위해 :

myDict.ContainsKey(keyBuilder(key1, key2))

-3

중복 키는 사전의 전체 계약을 위반합니다. 사전에서 각 키는 고유하며 단일 값으로 매핑됩니다. 객체를 임의의 수의 추가 객체에 연결하려는 경우 가장 좋은 방법은 DataSet과 비슷합니다 (일반적으로 테이블). 한 열에는 키를, 다른 열에는 키를 넣으십시오. 이것은 사전보다 상당히 느리지 만 주요 객체를 해시하는 기능을 잃어 버린 것은 절충입니다.


5
성능 향상을 위해 사전을 사용하는 것이 중요하지 않습니까? DataSet을 사용하는 것은 List <KeyValuePair <T, U >>보다 낫지 않습니다.
Doug

-4

또한 가능합니다 :

Dictionary<string, string[]> previousAnswers = null;

이런 식으로 고유 키를 가질 수 있습니다. 이것이 효과가 있기를 바랍니다.


OP는 중복 키를 허용하는 사전을 요청했습니다.
Skaparate

-10

다음과 같이 대소 문자가 다른 동일한 키를 추가 할 수 있습니다.

키 1
키 1
KEY1
키 1
키 1
키 1

나는 답이 더미라는 것을 알고 있지만 나를 위해 일했다.


4
아니요, 효과가 없었습니다. 사전은 키로 O (1) 로 분류되는 매우 빠른 조회를 허용 합니다. 다르게 입력 된 여러 키를 추가하면 어떻게 검색합니까? 모든 대문자 / 소문자 조합을 시도해 보시겠습니까? 어떻게하더라도 성능은 일반적인 단일 사전 검색의 성능이 아닙니다. 이는 키의 백업 가능 문자 수에 따라 키당 값의 제한과 같은 다른 더 분명한 단점에 추가됩니다.
Eugene Beresovsky
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.