我有一个Hashtable,我不知道它的内容是什么。
现在我想从中得到一个Key和值;
我使用哈希表是因为它的速度因为哈希表的内容超过4,500,000 KeyValuePair所以我不能使用GetEnumerator来降低程序速度
答案 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
类中,以获取涉及随机性的任何功能。那只是我的选择;显然你可以走另一条路。