2016-08-23 43 views
8

Przez jakiś czas szukałem google i odkryłem, że najlepszym sposobem, który pozwala mieć listę zawierającą zmienne z odpowiednim kluczem unikalnym, jest HashTable lub Dictionary, ale nie znalazłem niczego, co umożliwiłoby automatyczne klucze (typu integer). Chcę wywołać funkcję, która dodaje obiekt (przekazywany jako parametr) do słownika i zwraca automatycznie wygenerowany klucz (int) i bez żadnych duplikatów kluczy. Jak mogłem to osiągnąć? Całkowicie walczę!Automatyczny klucz słownika?

EDYCJA: Aby wyjaśnić pewne kwestie. To jest serwer i chcę przypisać unikalny klucz dla każdego klienta. Jeśli użyję maksymalnej wartości klucza, ta wartość wkrótce osiągnie maksymalną wartość int na dużych serwerach. Ponieważ jeśli klient łączy się, a następnie rozłącza, pozostawia niewykorzystaną wartość, która powinna zostać ponownie wykorzystana, aby uniknąć osiągnięcia bardzo wysokiej maksymalnej wartości klucza.

+2

Wystarczy napisać klasę, która otacza słownik za pomocą metody, którą opisałeś - zakładam, że wiesz, jak zamierzasz wygenerować swój klucz z obiektu? –

+1

Klucz nie zostanie wygenerowany z obiektu, klucz będzie używany do powiązania obiektów ze sobą. Próbowałem zapętlić każdy klucz i sprawdzić, czy ten klucz jest równy 0 i jeśli klucz 0 nie istnieje, utworzyłbym nowy obiekt z tym kluczem, jeśli istnieje, powtarzałbym za 1,2,3,4. ale wiedziałem, że to spowoduje wielkie problemy z wydajnością. – None

+1

W jaki sposób generowany jest nowy klucz? Jeśli jest to po prostu automatycznie zwiększane, możesz po prostu zawinąć 'List '. – Lee

Odpowiedz

5

Następujące powinien zrobić i ponownie wykorzystuje uwolnione klawiszy:

internal class AutoKeyDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable 
{ 
    private readonly Dictionary<TKey, TValue> inner; 
    private readonly Func<TKey, TKey> incrementor; 
    private readonly Stack<TKey> freeKeys; 
    private readonly TKey keySeed; 
    private TKey currentKey; 

    public AutoKeyDictionary(TKey keySeed, Func<TKey, TKey> incrementor) 
    { 
     if (keySeed == null) 
      throw new ArgumentNullException("keySeed"); 

     if (incrementor == null) 
      throw new ArgumentNullException("incrementor"); 

     inner = new Dictionary<TKey, TValue>(); 
     freeKeys = new Stack<TKey>(); 
     currentKey = keySeed; 
    } 

    public TKey Add(TValue value) //returns the used key 
    { 
     TKey usedKey; 

     if (freeKeys.Count > 0) 
     { 
      usedKey = freeKeys.Pop(); 
      inner.Add(usedKey, value); 
     } 
     else 
     { 
      usedKey = currentKey; 
      inner.Add(usedKey, value); 
      currentKey = incrementor(currentKey); 
     } 

     return usedKey; 
    } 

    public void Clear() 
    { 
     inner.Clear(); 
     freeKeys.Clear(); 
     currentKey = keySeed; 
    } 

    public bool Remove(TKey key) 
    { 
     if (inner.Remove(key)) 
     { 
      if (inner.Count > 0) 
      { 
       freeKeys.Push(key); 
      } 
      else 
      { 
       freeKeys.Clear(); 
       currentKey = keySeed; 
      } 

      return true; 
     } 

     return false; 
    } 

    public bool TryGetValue(TKey key, out TValue value) { return inner.TryGetValue(key, out value); } 
    public TValue this[TKey key] { get {return inner[key];} set{inner[key] = value;} } 
    public bool ContainsKey(TKey key) { return inner.ContainsKey(key); } 
    public bool ContainsValue(TValue value) { return inner.ContainsValue (value); } 
    public int Count { get{ return inner.Count; } } 
    public Dictionary<TKey,TValue>.KeyCollection Keys { get { return inner.Keys; } } 
    public Dictionary<TKey, TValue>.ValueCollection Values { get { return inner.Values; } } 
    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { return inner.GetEnumerator(); } 
    IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)inner).GetEnumerator(); } 
} 

Oświadczenie: Nie testowałem tego kodu, to może mieć kilka pesty robaki większego znaczenia, ogólne podejście jest dźwięk.

2

utworzyć metodę, która pobiera maksymalną wartość klucza ze słownika przy użyciu LINQ, dodaje 1 do niego, a następnie używa tego jako klucz do wartości chciałbyś dodać coś takiego:

public void AddToMyDictionary(string value) 
{ 
    int NextKey = MyDictionary.Keys.Max() + 1; 
    MyDictionary.Add(NextKey, value); 
} 

Oczywiście , to zakłada, że ​​twój słownik jest Dictionary<int, string>, ale możesz oczywiście modyfikować do swoich celów.

Jeśli chcesz ponownie użyć kluczy, które zostały usunięte, zapisz następny indeks, gdy coś zostanie dodane/usunięte.

private int NextKey = 0; 

public int AddToMyDictionary(string value) 
{ 
    int currentKey = NextKey; 
    MyDictionary.Add(currentKey, value); 
    NextKey = MyDictionary.Keys.Max() + 1; 
    return currentKey; 
} 

public void RemoveFromMyDictionary(int key) 
{ 
    MyDictionary.Remove(key); 
    NextKey = key; 
} 
+0

To ma złożoność O (n), więc bardziej wyspecjalizowane podejście będzie prawdopodobnie lepsze. – Lee

+1

Problem polega na tym, że niektóre obiekty mogą zostać usunięte, więc chcę użyć tych nieużywanych wartości przed dalszym rozszerzeniem. Ponieważ te dane będą wysyłane przez Internet. – None

+0

Oczywistym rozwiązaniem jest pamiętanie o tym, jak max, jako pole prywatne. Więc po dodaniu, wykonaj 'maxKey ++; MyDictionary.Add (maxKey, wartość); '. –

0

Po to jest int Object.GetHashCode().

+3

Kody mieszania nie muszą być unikatowe. –

+1

Tak jak @Stefan powiedział – None

4

Napisz klasę, która to robi. Coś takiego:

class AutoIndexDictionary : IEnumerable<Whatever> 
{ 
    private readonly Dictionary<int, Whatever> myDict = new Dictionary<int, Whatever>(); 

    private int currentIndex = 0; 

    public int Add(Whatever item) 
    { 
    var myIndex = currentIndex 
    myDict.Add(myIndex, item); 
    currentIndex ++; 
    return myIndex; 
    } 

    public void Remove(int index) 
    { 
    myDict.Remove(index); 
    } 

    // implement IEnumerable, indexer etc. 
    // ... 
} 
+0

pisałem ten sam kod tutaj. Edycja: Możesz dokonać odczytu bieżącego indeksu z zewnątrz: 'public int CurrentIndex {get; prywatny zestaw; } = 0; '. –

0

Would not List robić to, co mówisz, bez jakiegokolwiek dodatkowego narzutu? Nazywa się to "unikalnym kluczem całkowitym", ale w terminologii List nazywa się to po prostu "indeksem".

Jeśli naprawdę chciał niestandardową funkcję, aby dodać wartość i dostać klucz w jednym kroku, można dziedziczyć List<T>, tak:

class MyCustomList<T> : List<T> 
{ 
    //Not thread-safe 
    public int AddAndGetKey(T valueToAdd) 
    { 
     Add(valueToAdd); 
     return LastIndexOf(valueToAdd); 
    } 
} 

używam LastIndexOf() ponieważ lista może zawierać zduplikowanych wartości a dodanie do listy zawsze dodaje do końca. Powinno to zadziałać, chyba że wejdziesz w sytuacje wielowątkowe, w których musisz dodać indeks indeksu w jednej operacji atomowej. (Alternatywnie możesz dodać metodę rozszerzenia do List<T>.)

Zaletą korzystania z List jest brak przerw w klawiszach. Po drugiej stronie usunięcie elementu w środku zmieni klucz każdego elementu po nim. Ale myślę, że to zależy od tego, jakiego zachowania szukasz.

+0

Chciałem zasugerować to samo, ale wtedy przyszło mi do głowy, że klucz nie zmienia się na pozycji, jednak hashset to zrobiłby – MikeT

+1

Jeśli obiekt ze środka listy zostanie usunięty, wszystkie obiekty po tej wartości spowoduje spadek indeksu o 1. Potrzebuję jednak stałych kluczy. – None

0

Biorąc pod uwagę dodatkowe informacje zawarte w Twojej edycji, nie uważam, że int jest właściwym typem danych dla ciebie, nie powinieneś ponownie używać identyfikatorów tak, jak opisujesz, tak jakby klient z identyfikatorem został rozłączony, ale nie sobie sprawę, że możesz mieć 1 identyfikator używany przez 2 klientów. zmień swój typ danych na Guid, a kiedy otrzymasz nowego klienta, podaj mu klucz o numerze Guid.NewGuid(), a szansa, że ​​zduplikowane klucze spadną jak najbliżej 0

0

Podoba mi się rozwiązanie Stefana Steineggera.Tutaj jest alternatywą, która wykorzystuje List<> za kulisami, ale zapewnia List<> nigdy nie jest usuwany z:

class AutoKeyDictionary<TValue> : IEnumerable<TValue> where TValue : class 
{ 
    readonly List<TValue> list = new List<TValue>(); 

    public int Add(TValue val) 
    { 
    if (val == null) 
     throw new ArgumentNullException(nameof(val), "This collection will not allow null values."); 
    list.Add(val); 
    return list.Count - 1; 
    } 

    public void RemoveAt(int key) 
    { 
    // do not remove ('list.Count' must never decrease), overwrite with null 
    // (consider throwing if key was already removed) 
    list[key] = null; 
    } 

    public TValue this[int key] 
    { 
    get 
    { 
     var val = list[key]; 
     if (val == null) 
     throw new ArgumentOutOfRangeException(nameof(key), "The value with that key is no longer in this collection."); 
     return val; 
    } 
    } 

    public int NextKey => list.Count; 

    public int Count => list.Count(v => v != null); // expensive O(n), Linq 

    public bool ContainsKey(int key) => key >= 0 && key < list.Count && list[key] != null; 

    public TValue TryGetValue(int key) => (key >= 0 && key < list.Count) ? list[key] : null; 

    public void Clear() 
    { 
    for (var i = 0; i < list.Count; ++i) 
     list[i] = null; 
    } 

    public IEnumerator<TValue> GetEnumerator() => list.Where(v => v != null).GetEnumerator(); // Linq 

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 

    public int FirstKeyOf(TValue val) => list.IndexOf(val); 

    public IDictionary<int, TValue> ToDictionary() 
    { 
    var retColl = new SortedList<int, TValue>(list.Count); 
    for (var i = 0; i < list.Count; ++i) 
    { 
     var val = list[i]; 
     if (val != null) 
     retColl.Add(i, val); 
    } 
    return retColl; 
    } 

    // and so on... 
} 

Nie wątku bezpieczny, oczywiście.

Należy pamiętać, że ta sama wartość może znajdować się kilka razy w kolekcji, ale z różnymi kluczami.