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ć?

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

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

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

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.


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.


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

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

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

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.


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

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.