自动字典键?

Automatic dictionary key?

我一直在谷歌搜索一段时间,我发现让你拥有一个包含具有相应唯一键的变量的列表的最佳方法是 HashTableDictionary,但我没有找到任何可以让您拥有自动键(整数类型)的东西。我想调用一个函数,将一个对象(作为参数传递)添加到字典和 returns 自动生成的键(int),并且没有任何键重复。我怎么能做到这一点?我真的很挣扎!

编辑:澄清事情。这是一个服务器,我想为每个客户端分配一个唯一的密钥。如果我使用最大键值,这个值很快就会在大型服务器上达到 int 最大值。因为如果客户端连接然后断开连接,他会留下一个未使用的值,应该重复使用该值以避免达到非常高的密钥最大值。

创建一个方法,使用 LINQ 从字典中获取最大键值,将其加 1,然后将其用作您要添加的值的键,如下所示:

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

显然,这假定您的字典是 Dictionary<int, string>,但您显然可以根据自己的目的进行修改。

如果您想重新使用已删除的键,请在添加/删除内容时存储下一个索引。

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

这就是 int Object.GetHashCode() 的用途。

写一个 class 来做这个。像这样:

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

List 会不会照你说的做,而且没有任何额外的开销?你称它为 "unique integer key",但在 List 术语中,它简称为 "index".

如果你真的想要一个自定义函数来一步添加一个值并获取一个键,你可以继承自 List<T>,像这样:

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

我使用 LastIndexOf() 是因为列表可能包含重复值,添加到列表中的内容总是添加到末尾。所以这应该有效,除非你进入多线程情况,你必须在一个原子操作中添加和获取索引。 (或者,您可以向 List<T> 添加扩展方法。)

使用 List 的优点是键之间没有间隙。另一方面,删除中间的项目会更改其后每个项目的键。但我想这取决于您要寻找的行为。

鉴于您在编辑中提供的附加信息,我认为 int 不是适合您的数据类型,您不应该按照您描述的方式重复使用 ID,就好像带有 ID 的客户端断开连接但不没有意识到您可能有 1 个 ID 被 2 个客户端使用。将您的数据类型更改为 Guid,然后当您获得一个新客户端时,给它一个 Guid.NewGuid() 的键,重复键的机会尽可能降低到 0

我喜欢 Stefan Steinegger 的解决方案。这是在幕后使用 List<> 的替代方法,但确保永远不会从以下位置删除 List<>

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

显然不是线程安全的。

请注意,同一值可以在集合中多次出现,但键不同。

应该执行以下操作,它会重新使用释放的密钥:

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

免责声明:我没有测试过这段代码,它可能有一些不太重要的小错误,一般方法是合理的。