Случайная запись из словаря - PullRequest
49 голосов
/ 22 июня 2009

Каков наилучший способ получить случайную запись из словаря в c #?

Мне нужно получить несколько случайных объектов из словаря для отображения на странице, однако я не могу использовать следующее, так как словари не доступны по индексу:

Random rand = new Random();
Dictionary< string, object> dict = GetDictionary();
return dict[rand.Next()];

Есть предложения?

Ответы [ 9 ]

45 голосов
/ 22 июня 2009

Если вы используете .net 3.5, Enumerable имеет метод расширения ElementAt , который позволит вам:

return dict.ElementAt(rand.Next(0, dict.Count)).Value;
42 голосов
/ 22 июня 2009

Обновлен для использования обобщений, чтобы быть еще быстрее, и с объяснением, почему эта опция быстрее.

Этот ответ аналогичен другим ответам, но, поскольку вы сказали, что вам нужно «несколько случайных элементов», это будет более производительным:

public IEnumerable<TValue> RandomValues<TKey, TValue>(IDictionary<TKey, TValue> dict)
{
    Random rand = new Random();
    List<TValue> values = Enumerable.ToList(dict.Values);
    int size = dict.Count;
    while(true)
    {
        yield return values[rand.Next(size)];
    }
}

Вы можете использовать этот метод так:

Dictionary<string, object> dict = GetDictionary();
foreach (object value in RandomValues(dict).Take(10))
{
    Console.WriteLine(value);
}

Это улучшило производительность по сравнению с другими ответами (включая ответ yshuditelu).

  1. Не нужно создавать новую коллекцию всех элементов словаря каждый раз, когда вы хотите получить новое случайное значение. Это действительно большое дело, если в вашем словаре много элементов.
  2. Нет необходимости выполнять поиск на основе ключа словаря каждый раз, когда вы выбираете случайное значение. Не такая большая сделка, как # 1, но все равно в два раза быстрее.

Мои тесты показывают, что с 1000 объектами в словаре этот метод работает примерно в 70 раз быстрее, чем другие предложенные методы.

16 голосов
/ 22 июня 2009

Из вашего словаря ...

Dictionary<string, int> dict = new Dictionary<string, object>()

Вы можете создать полный список ключей ...

List<string> keyList = new List<string>(dict.Keys);

, а затем выберите случайный ключ из списка.

Random rand = new Random();
string randomKey = keyList[rand.Next(keyList.Count)];

Затем просто возвращает случайный объект , соответствующий этому ключу.

return dict[randomKey];
12 голосов
/ 24 июня 2009

Мой другой ответ верен для вопроса и был бы полезен во многих случаях, таких как получение информации о броске из пользовательских кубиков (каждый бросок кубика является случайным, независимым от других кубиков). Однако ваши комментарии звучат так, будто вы надеетесь получить ряд «уникальных» элементов из Dictionary, вроде раздачи карт из колоды. После раздачи карты вы никогда не захотите снова увидеть ту же карту до повторного перетасования. В этом случае лучшая стратегия будет зависеть от того, что именно вы делаете.

Если вы получаете только несколько элементов из большого Dictionary, тогда вы сможете адаптировать другой мой ответ, удаляя случайный элемент из списка каждый раз, когда извлекается новый. Вы, вероятно, также захотите превратить список в LinkedList, потому что, хотя найти элемент по его индексу будет медленнее, гораздо дешевле удалить элементы из его середины. Код для этого будет немного сложнее, поэтому, если вы готовы пожертвовать некоторой производительностью ради простоты, вы можете просто сделать это:

public IEnumerable<TValue> UniqueRandomValues<TKey, TValue>(IDictionary<TKey, TValue> dict)
{
    Random rand = new Random();
    Dictionary<TKey, TValue> values = new Dictionary<TKey, TValue>(dict);
    while(values.Count > 0)
    {
        TKey randomKey = values.Keys.ElementAt(rand.Next(0, values.Count));  // hat tip @yshuditelu 
        TValue randomValue = values[randomKey];
        values.Remove(randomKey);
        yield return randomValue;
    }
}

Если, с другой стороны, вы планируете извлечь из своего словаря значительное количество элементов (т. Е. Выложить больше, чем log (n) своей "колоды"), вам лучше будет просто перетасовать сначала вся колода, а потом тянет сверху:

public IEnumerable<TValue> UniqueRandomValues<TKey, TValue>(IDictionary<TKey, TValue> dict)
{
    // Put the values in random order
    Random rand = new Random();
    LinkedList<TValue> values = new LinkedList<TValue>(from v in dict.Values
                                                       orderby rand.Next()
                                                       select v);
    // Remove the values one at a time
    while(values.Count > 0)
    {
        yield return values.Last.Value;
        values.RemoveLast();
    }
}

Кредит идет на ookii.org для простого кода тасования. Если это все еще не совсем то, что вы искали, возможно, вы можете начать новый вопрос с более подробной информацией о том, что вы пытаетесь сделать.

3 голосов
/ 22 июня 2009

Что-то вроде:

Random rand = new Random();
Dictionary dict = GetDictionary();
var k = dict.Keys.ToList()[rand.Next(dict.Count)];
return dict[k];
2 голосов
/ 22 июня 2009

Это будет не очень быстро, но должно работать:

Random rand = new Random();
Dictionary dict = GetDictionary();
return dict.Skip(rand.Next(dict.Count)).First().Value;
1 голос
/ 22 июня 2009

Простым решением будет использование метода расширения ToList() и индекс индекса списка.

Если вам просто нужны значения или ключи (не пара ключ / значение), верните эти коллекции из словаря и используйте также ToList().

        Random rand = new Random();
        Dictionary<string, object> dict = GetDictionary();
        var k = dict.ToList()[rand.Next(dict.Count)];
        // var k = dict.Values.ToList()[rand.Next(dict.Count)];
        // var k = dict.Keys.ToList()[rand.Next(dict.Count)];

        Console.WriteLine("Random dict pair {0} = {1}", k.Key, k.Value);
0 голосов
/ 02 августа 2014
public static class DictionaryExtensions
{
    public static TKey[] Shuffle<TKey, TValue>(
       this System.Collections.Generic.Dictionary<TKey, TValue> source)
    {
        Random r = new Random();
        TKey[] wviTKey = new TKey[source.Count];
        source.Keys.CopyTo(wviTKey, 0);

        for (int i = wviTKey.Length; i > 1; i--)
        {
            int k = r.Next(i);
            TKey temp = wviTKey[k];
            wviTKey[k] = wviTKey[i - 1];
            wviTKey[i - 1] = temp;
        }

        return wviTKey;
    }
}

Пример

            // Using
            System.Collections.Generic.Dictionary<object, object> myDictionary = new System.Collections.Generic.Dictionary<object, object>();
            // myDictionary.Add(myObjectKey1, myObjectValue1); // Sample
            // myDictionary.Add(myObjectKey2, myObjectValue2); // Sample
            // myDictionary.Add(myObjectKey3, myObjectValue3); // Sample
            // myDictionary.Add(myObjectKey4, myObjectValue4); // Sample

            // var myShufledKeys = myDictionary.Shuffle(); // Sample
            // var myShufledValue = myDictionary[myShufledKeys[0]]; // Sample

            // Easy Sample
            var myObjects = System.Linq.Enumerable.Range(0, 4);
            foreach(int i in myObjects)
                myDictionary.Add(i, string.Format("myValueObjectNumber: {0}", i));

            var myShufledKeys = myDictionary.Shuffle();
            var myShufledValue = myDictionary[myShufledKeys[0]];
0 голосов
/ 22 июня 2009

Я считаю, что единственный способ - это сначала создать отдельный список KeyValuePairs.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...