从Hashtable获取随机密钥值

时间:2012-08-14 21:12:01

标签: c# performance

我有一个Hashtable,我不知道它的内容是什么。

现在我想从中得到一个Key和值;

我使用哈希表是因为它的速度因为哈希表的内容超过4,500,000 KeyValuePair所以我不能使用GetEnumerator来降低程序速度

6 个答案:

答案 0 :(得分:5)

您使用List<TKey>

Dictionary<string, string> dict = ... your hashtable which could be huge

List<string> keys = new List<string>(dict.Keys);
int size = dict.Count;
Random rand = new Random();
string randomKey = keys[rand.Next(size)];

我们只是创建一个List<TKey>,其元素指向内存中与哈希表的键相同的位置,然后我们从此列表中选择一个随机元素。

如果你想从哈希表中获取一个随机元素值,那么给定一个随机密钥应该非常简单。

string randomeElement = dict[randomKey];

答案 1 :(得分:4)

  

我无法使用GetEnumerator降低程序速度“

那是一个问题。您已经接受了 迭代所有条目的答案,并将密钥复制到新列表中,因此不清楚您是否已放弃该要求。

一种在内存和速度上肯定会更高效的方法是迭代整个字典,但在任何时候都保留一个随机元素,优化集合,我们可以便宜地获得计数。这是一个扩展方法,它将为.NET中的任何泛型序列执行此操作:

public static T RandomElement<T>(this IEnumerable<T> source,
                                 Random rng)
{
    // Optimize for the "known count" case.
    ICollection<T> collection = source as ICollection<T>;
    if (collection != null)
    {
        // ElementAt will optimize further for the IList<T> case
        return source.ElementAt(rng.Next(collection.Count));
    }

    T current = default(T);
    int count = 0;
    foreach (T element in source)
    {
        count++;
        if (rng.Next(count) == 0)
        {
            current = element;
        }            
    }
    if (count == 0)
    {
        throw new InvalidOperationException("Sequence was empty");
    }
    return current;
}

因此对于Dictionary<TKey, TValue>,您最终会以KeyValuePair<TKey, TValue>的方式结束 - 或者您可以先投射到Keys

var key = dictionary.Keys.RandomElement(rng);

(请参阅我的article on Random了解这方面的问题。)

如果你想要一个真正的伪随机密钥,而不仅仅是一个任意密钥(你可以得到),我不相信你能比O(n)做得更好。通过序列中的第一个,如其他地方所述)。

请注意,在Darin的答案中将密钥复制到列表可以让您更有效地获得多个随机元素。这一切都取决于您的要求。

答案 2 :(得分:2)

随机密钥的随机性如何?

哈希表没有定义其项目的存储顺序,因此您可以抓住第一个项目。它不是真的随机,但它也不是插入顺序或排序顺序。这会是随机的吗?

Dictionary<string, string> dict = GetYourHugeHashTable();

KeyValuePair<string, string> randomItem = dict.First();
DoAComputation(randomItem.Key, randomItem.Value);
dict.Remove(randomItem.Key);

答案 3 :(得分:2)

使用Linq你可以做到:

Dictionary<string, string> dicto = new Dictionary<string, string>();

Random rand = new Random();

int size = dicto.Count;

int randNum = rand.Next(0, size);

KeyValuePair<string, string> randomPair = dicto.ElementAt( randNum );

string randomVal = randomPair.Value;

例如,

string tmp = dicto.ElementAt( 30 ).Value;

将Dicto中第30个项目的值复制到字符串tmp。

在内部,我认为它一次一个地通过密钥对,直到它到达第三十个,而不是全部复制它们,所以你不需要将所有元素加载到内存中。

我不确定你的意思是不知道内容是什么。

您不知道dicto的KeyValuePair中的类型? 或者只是不知道dicto中会有什么价值?

答案 4 :(得分:1)

Hashtable.Keys将为您提供指向内部键列表的指针。那很快。从Hashtable中删除项目也是O(1)操作,因此即使有大量项目,这也会很快。

你可以这样做一个循环(我认为没有理由在你的问题中使用随机);

var k = Hashtable.Keys(); // Will reflect actual contents, even if changes occur

while (k.Count > 0 )
{
 var i = Keys.First();
 {
       Process(i);
       Hashtable.Remove(i)  
 }
}

答案 5 :(得分:0)

好吧,如果您知道将要定位的.NET BCL版本(即,如果它已修复),您可以随时检查Dictionary<TKey, TValue>的内部,以弄清楚它如何私下存储其密钥。用它来随机抽取一个。

例如,使用当前已在我的工作笔记本电脑上安装的Mono版本,我看到Dictionary<TKey, TValue>类型有一个名为keySlots的私有字段(我假设如果你有所不同你在Windows上)。使用这些知识,您可以实现类似这样的函数:

static readonly Dictionary<Type, FieldInfo> KeySlotsFields = new Dictionary<Type, FieldInfo>();

public static KeyValuePair<TKey, TValue> GetRandomKeyValuePair<TKey, TValue>(this Random random, Dictionary<TKey, TValue> dictionary, Random random = null)
{
    // Here's where you'd get the FieldInfo that you've identified
    // for your target version of the BCL.
    FieldInfo keySlotsField = GetKeySlotsField<TKey, TValue>();

    var keySlots = (TKey[])keySlotsField.GetValue(dictionary);

    var key = (TKey)keySlots[random.Next(keySlots.Length)];

    // The keySlots field references an array with some empty slots,
    // so we need to loop until we come across an existing key.
    while (key == null)
    {
        key = (TKey)keySlots[random.Next(keySlots.Length)];
    }

    return new KeyValuePair<TKey, TValue>(key, dictionary[key]);
}

// This happens to work for me on Mono; you'd almost certainly need to
// rewrite it for different platforms.
public FieldInfo GetKeySlotsField<TKey, TValue>()
{
    Type keyType = typeof(TKey);

    FieldInfo keySlotsField;
    if (!KeySlotsFields.TryGetValue(keyType, out keySlotsField))
    {
        KeySlotsFields[keyType] = keySlotsField = typeof(Dictionary<TKey, TValue>).GetField("keySlots", BindingFlags.Instance | BindingFlags.NonPublic);
    }

    return keySlotsField;
}

在你的情况下,这可能是一个合适的解决方案,或者它可能是一个可怕的想法。只有你有足够的上下文来进行这个电话。

至于上面的示例方法:我个人喜欢将扩展方法添加到Random类中,以获取涉及随机性的任何功能。那只是我的选择;显然你可以走另一条路。