Como acessar o item aleatório na lista?

233

Eu tenho um ArrayList e preciso clicar em um botão e escolher aleatoriamente uma sequência dessa lista e exibi-la em uma caixa de mensagens.

Como eu faria isso?

jay_t55
fonte

Respostas:

404
  1. Crie uma instância da Randomclasse em algum lugar. Observe que é muito importante não criar uma nova instância sempre que você precisar de um número aleatório. Você deve reutilizar a instância antiga para obter uniformidade nos números gerados. Você pode ter um staticcampo em algum lugar (tenha cuidado com os problemas de segurança do encadeamento):

    static Random rnd = new Random();
  2. Peça à Randominstância para fornecer um número aleatório com o máximo do número de itens no ArrayList:

    int r = rnd.Next(list.Count);
  3. Exiba a sequência:

    MessageBox.Show((string)list[r]);
Mehrdad Afshari
fonte
Existe alguma maneira de modificar isso para que um número não se repita? Digamos que eu queira embaralhar um baralho selecionando aleatoriamente um de cada vez, mas obviamente não posso selecionar o mesmo cartão duas vezes.
AdamMc331
7
@ McAdam331 Olhe para cima Fisher-Yates algoritmo Aleatório: en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
Mehrdad Afshari
2
Deve ser "rnd.Next (list.Count-1)" em vez de "rnd.Next (list.Count)" para evitar o acesso ao elemento max, que seria um além do presumivelmente índice baseado em 0?
22616 Clay Shannon
22
@ B.ClayShannon Não. O limite superior da Next(max)chamada é exclusivo.
Mehrdad Afshari
1
E quando a lista está vazia?
Tsu1980 07/12/19
137

Eu costumo usar esta pequena coleção de métodos de extensão:

public static class EnumerableExtension
{
    public static T PickRandom<T>(this IEnumerable<T> source)
    {
        return source.PickRandom(1).Single();
    }

    public static IEnumerable<T> PickRandom<T>(this IEnumerable<T> source, int count)
    {
        return source.Shuffle().Take(count);
    }

    public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source)
    {
        return source.OrderBy(x => Guid.NewGuid());
    }
}

Para uma lista fortemente tipada, isso permitiria escrever:

var strings = new List<string>();
var randomString = strings.PickRandom();

Se tudo o que você tem é um ArrayList, é possível convertê-lo:

var strings = myArrayList.Cast<string>();
Mark Seemann
fonte
qual é a complexidade disso? a natureza preguiçosa de IEnumerable significa que não é O (N)?
precisa
17
Essa resposta reorganiza a lista toda vez que você escolhe um número aleatório. Seria muito mais eficiente retornar um valor de índice aleatório, especialmente para listas grandes. Use isso no PickRandom - return list[rnd.Next(list.Count)];
swax
Isto não embaralhar a lista original, ele faz em outra lista de fato que ainda não pode ser bom para a eficiência se a lista é grande o suficiente ..
Nawfal
.OrderBy (.) Não cria outra lista - Ele cria um objeto do tipo IEnumerable <T> que está iterando pela lista original de maneira ordenada.
Johan Tidén 7/08/2013
5
O algoritmo de geração de GUID é imprevisível, mas não aleatório. Considere manter uma instância de Randomno estado estático.
Dai
90

Você pode fazer:

list.OrderBy(x => Guid.NewGuid()).FirstOrDefault()
Felipe Fujiy Pessoto
fonte
Lindo. No ASP.NET MVC 4.5, usando uma lista, tive que mudar isso para: list.OrderBy (x => Guid.NewGuid ()). FirstOrDefault ();
Andy Brown
3
Na maioria dos casos, isso não importa, mas isso provavelmente é muito mais lento do que usar o rnd.Next. OTOH funcionará no IEnumerable <T>, não apenas nas listas.
Solublefish 22/03
12
Não tenho certeza de quão aleatório é isso. Guids são únicos, não aleatórios.
7286 pomber #
1
Penso que uma versão melhor e mais extensa desta resposta e o comentário do @ solublefish está resumida nesta resposta (mais o meu comentário ) a uma pergunta semelhante.
Neo
23

Crie uma Randominstância:

Random rnd = new Random();

Busque uma sequência aleatória:

string s = arraylist[rnd.Next(arraylist.Count)];

Lembre-se, porém, que se você fizer isso com frequência, deverá reutilizar o Randomobjeto. Coloque-o como um campo estático na classe, para que seja inicializado apenas uma vez e depois acesse-o.

Joey
fonte
20

Ou classe de extensão simples como esta:

public static class CollectionExtension
{
    private static Random rng = new Random();

    public static T RandomElement<T>(this IList<T> list)
    {
        return list[rng.Next(list.Count)];
    }

    public static T RandomElement<T>(this T[] array)
    {
        return array[rng.Next(array.Length)];
    }
}

Em seguida, basta ligar para:

myList.RandomElement();

Funciona para matrizes também.

Eu evitaria ligar OrderBy(), pois pode ser caro para coleções maiores. Use coleções indexadas como List<T>ou matrizes para esse fim.

Dave_cz
fonte
3
As matrizes no .NET já são implementadas, IListportanto a segunda sobrecarga é desnecessária.
Dai
3

Por que não:

public static T GetRandom<T>(this IEnumerable<T> list)
{
   return list.ElementAt(new Random(DateTime.Now.Millisecond).Next(list.Count()));
}
Lucas
fonte
2
ArrayList ar = new ArrayList();
        ar.Add(1);
        ar.Add(5);
        ar.Add(25);
        ar.Add(37);
        ar.Add(6);
        ar.Add(11);
        ar.Add(35);
        Random r = new Random();
        int index = r.Next(0,ar.Count-1);
        MessageBox.Show(ar[index].ToString());
Rajesh Varma
fonte
3
Embora esse trecho de código possa resolver a questão, incluir uma explicação realmente ajuda a melhorar a qualidade da sua postagem. Lembre-se de que você está respondendo à pergunta dos leitores no futuro e essas pessoas podem não saber os motivos da sua sugestão de código.
gunr2171
3
Eu diria que o maxValueparâmetro do método Nextdeve ser apenas um número de elementos em uma lista, não menos um, porque de acordo com a documentação " maxValue é o limite superior exclusivo do número aleatório ".
David Ferenczy Rogožan
1

Estou usando este ExtensionMethod há algum tempo:

public static IEnumerable<T> GetRandom<T>(this IEnumerable<T> list, int count)
{
    if (count <= 0)
      yield break;
    var r = new Random();
    int limit = (count * 10);
    foreach (var item in list.OrderBy(x => r.Next(0, limit)).Take(count))
      yield return item;
}
Carlos Toledo
fonte
Você tinha esquecido para adicionar exemplo classe Random
bafsar
1

Sugiro uma abordagem diferente: se a ordem dos itens dentro da lista não for importante na extração (e cada item deve ser selecionado apenas uma vez), em vez de um, Listvocê pode usar um ConcurrentBagque é uma coleção não ordenada e segura de threads objetos:

var bag = new ConcurrentBag<string>();
bag.Add("Foo");
bag.Add("Boo");
bag.Add("Zoo");

O EventHandler:

string result;
if (bag.TryTake(out result))
{
    MessageBox.Show(result);
}

O TryTaketentará extrair um objeto "aleatório" da coleção não ordenada.

Shahar Shokrani
fonte
0

Eu precisava de mais itens em vez de apenas um. Então, eu escrevi isso:

public static TList GetSelectedRandom<TList>(this TList list, int count)
       where TList : IList, new()
{
    var r = new Random();
    var rList = new TList();
    while (count > 0 && list.Count > 0)
    {
        var n = r.Next(0, list.Count);
        var e = list[n];
        rList.Add(e);
        list.RemoveAt(n);
        count--;
    }

    return rList;
}

Com isso, você pode obter elementos quantos quiser, de forma aleatória assim:

var _allItems = new List<TModel>()
{
    // ...
    // ...
    // ...
}

var randomItemList = _allItems.GetSelectedRandom(10); 
bafsar
fonte
0

Imprimindo aleatoriamente o nome do país a partir do arquivo JSON.
Modelo:

public class Country
    {
        public string Name { get; set; }
        public string Code { get; set; }
    }

Implementação:

string filePath = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, @"..\..\..\")) + @"Data\Country.json";
            string _countryJson = File.ReadAllText(filePath);
            var _country = JsonConvert.DeserializeObject<List<Country>>(_countryJson);


            int index = random.Next(_country.Count);
            Console.WriteLine(_country[index].Name);
RM Shahidul Islam Shahed
fonte
-3

Por que não [2]:

public static T GetRandom<T>(this List<T> list)
{
     return list[(int)(DateTime.Now.Ticks%list.Count)];
}
Идеки Матосува
fonte