Jak uzyskać dostęp do losowej pozycji na liście?

233

Mam ArrayList i muszę być w stanie kliknąć przycisk, a następnie losowo wybrać ciąg z tej listy i wyświetlić go w oknie komunikatu.

Jak miałbym to zrobić?

jay_t55
źródło

Odpowiedzi:

404
  1. Utwórz Randomgdzieś instancję klasy. Pamiętaj, że bardzo ważne jest, aby nie tworzyć nowej instancji za każdym razem, gdy potrzebujesz losowej liczby. Powinieneś ponownie użyć starej instancji, aby osiągnąć jednorodność wygenerowanych liczb. Możesz staticgdzieś mieć pole (uważaj na kwestie bezpieczeństwa wątków):

    static Random rnd = new Random();
  2. Poproś Randominstancję o podanie losowej liczby z maksymalną liczbą elementów w ArrayList:

    int r = rnd.Next(list.Count);
  3. Wyświetl ciąg:

    MessageBox.Show((string)list[r]);
Mehrdad Afshari
źródło
Czy istnieje dobry sposób na zmodyfikowanie tego, aby liczba się nie powtarzała? Powiedzmy, że chciałem przetasować talię kart, losowo wybierając jedną kartę, ale oczywiście nie mogę wybrać tej samej karty dwa razy.
AdamMc331
7
@ McAdam331 Wyszukaj algorytm Shuffle Fisher-Yatesa: en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
Mehrdad Afshari
2
Czy powinno to być „rnd.Next (list.Count-1)” zamiast „rnd.Next (list.Count)”, aby uniknąć dostępu do elementu max, który byłby o jeden poza indeksem przypuszczalnie opartym na 0?
B. Clay Shannon,
22
@ B.ClayShannon Nie. Górne ograniczenie Next(max)połączenia jest wyłączne.
Mehrdad Afshari,
1
A kiedy lista jest pusta?
tsu1980,
137

Zwykle używam tej małej kolekcji metod rozszerzenia:

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

W przypadku silnie wpisanej listy pozwoli to na napisanie:

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

Jeśli wszystko, co masz, to ArrayList, możesz rzucić:

var strings = myArrayList.Cast<string>();
Mark Seemann
źródło
jaka jest ich złożoność? czy leniwa natura IEnumerable oznacza, że ​​nie jest to O (N)?
Dave Hillier
17
Ta odpowiedź ponownie przetasowuje listę za każdym razem, gdy wybierzesz losową liczbę. Znacznie bardziej efektywne byłoby zwracanie losowej wartości indeksu, szczególnie w przypadku dużych list. Użyj tego w PickRandom - return list[rnd.Next(list.Count)];
swax
To nie tasuje oryginalnej listy, robi to na innej liście, co w rzeczywistości może nie być dobre dla wydajności, jeśli lista jest wystarczająco duża.
nawfal
.OrderBy (.) Nie tworzy innej listy - Tworzy obiekt typu IEnumerable <T>, który iteruje po oryginalnej liście w uporządkowany sposób.
Johan Tidén
5
Algorytm generowania identyfikatora GUID jest nieprzewidywalny, ale nie losowy. Rozważ Randomzamiast tego trzymanie instancji w stanie statycznym.
Dai
90

Możesz to zrobić:

list.OrderBy(x => Guid.NewGuid()).FirstOrDefault()
Felipe Fujiy Pessoto
źródło
Piękny. W ASP.NET MVC 4.5, używając listy, musiałem zmienić to na: list.OrderBy (x => Guid.NewGuid ()). FirstOrDefault ();
Andy Brown,
3
W większości przypadków nie będzie to miało znaczenia, ale jest to prawdopodobnie znacznie wolniejsze niż użycie rnd.Next. OTOH będzie działać na IEnumerable <T>, nie tylko na listach.
rozpuszczalna ryba
12
Nie jestem pewien, czy to losowe. Przewodniki są wyjątkowe, a nie losowe.
pomber
1
Myślę, że lepsza i rozszerzona wersja tej odpowiedzi i komentarz @ solublefish jest ładnie podsumowany w tej odpowiedzi (plus mój komentarz ) na podobne pytanie.
Neo
23

Utwórz Randominstancję:

Random rnd = new Random();

Pobierz losowy ciąg:

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

Pamiętaj jednak, że jeśli często to robisz, powinieneś ponownie użyć Randomobiektu. Umieść go jako pole statyczne w klasie, aby było ono inicjowane tylko raz, a następnie dostęp do niego.

Joey
źródło
20

Lub prosta klasa rozszerzenia taka jak ta:

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

Następnie wystarczy zadzwonić:

myList.RandomElement();

Działa również dla tablic.

Unikałbym dzwonienia, OrderBy()ponieważ może to być drogie w przypadku większych kolekcji. W List<T>tym celu użyj indeksowanych kolekcji takich jak lub tablic.

Dave_cz
źródło
3
Tablice w .NET już się implementują, IListwięc drugie przeciążenie nie jest konieczne.
Dai
3

Dlaczego nie:

public static T GetRandom<T>(this IEnumerable<T> list)
{
   return list.ElementAt(new Random(DateTime.Now.Millisecond).Next(list.Count()));
}
Lucas
źródło
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
źródło
3
Ten fragment kodu może rozwiązać pytanie, ale wyjaśnienie naprawdę pomaga poprawić jakość posta. Pamiętaj, że w przyszłości odpowiadasz na pytanie dla czytelników, a ci ludzie mogą nie znać przyczyn Twojej sugestii kodu.
gunr2171
3
Powiedziałbym, że maxValueparametrem metody Nextpowinna być tylko liczba elementów na liście, a nie minus jeden, ponieważ zgodnie z dokumentacją „ maxValue jest wyłączną górną granicą liczby losowej ”.
David Ferenczy Rogožan
1

Używam tego ExtensionMethod od dłuższego czasu:

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
źródło
Zapomniałeś dodać instancji klasy Random
bafsar
1

Zasugeruję inne podejście: jeśli kolejność elementów na liście nie jest ważna przy ekstrakcji (a każdy element powinien zostać wybrany tylko raz), to zamiast Listmożesz użyć ConcurrentBagbezpiecznej dla wątków, nieuporządkowanej kolekcji obiekty:

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

EventHandler:

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

TryTakeBędą próbować wyodrębnić „random” obiekt z kolekcji nieuporządkowanej.

Shahar Shokrani
źródło
0

Potrzebowałem więcej przedmiotu zamiast jednego. Więc napisałem to:

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

Dzięki temu możesz uzyskać elementy, które chcesz tak losowo, jak to:

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

var randomItemList = _allItems.GetSelectedRandom(10); 
Bafsar
źródło
0

Drukowanie losowej nazwy kraju z pliku JSON.
Model:

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

Implementacja:

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
źródło
-3

Dlaczego nie [2]:

public static T GetRandom<T>(this List<T> list)
{
     return list[(int)(DateTime.Now.Ticks%list.Count)];
}
Хидеки Матосува
źródło