sobota, 13 października 2012

Random - losowy element kolekcji za pomocą LINQ

Często potrzebujemy losowy element danej kolekcji. Aby nie pisać ciągle tego samego kodu wybierającego element losowy, możemy napisać metodę LINQ, którą użyjemy w dowolnym projekcie.
Metoda będzie się nazywać RandomElement.

Wymagania dla metody:
- przyjmuje źródło implementujące IEnumerable<T> i zwraca jeden element typu T
- przyjmuje opcjonalnie seed (kontrola nad generowanymi liczbami losowymi)
- jeżeli źródło jest nullem rzucany jest wyjątek ArgumentNullException
- jeżeli źródło nie zawiera elementów (kolekcja jest pusta) rzucany jest wyjątek InvalidOperationException

Implementacja:

Code:
using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication1
{
    public static class LinqExtensions
    {
        public static T RandomElement<T>(this IEnumerable<T> source, int? seed = null)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source is null");
            }

            int count = 0;
            if (source is ICollection<T>)
            {
                count = ((ICollection<T>)source).Count;
            }
            else
            {
                count = source.Count();
            }
            if (count == 0)
            {
                throw new InvalidOperationException("Source contains no elements");
            }

            Random random;
            if (!seed.HasValue)
            {
                random = new Random();
            }
            else
            {
                random = new Random(seed.Value);
            }

            var list = source as IList<T>;
            var index = random.Next(0, count);
            if (list != null)
            {
                return list[index];
            }
            else
            {
                using(IEnumerator<T> e = source.GetEnumerator())
                {
              e.MoveNext();
                    while (index > 0)
                    {
                     e.MoveNext();
                        --index;
                    }

                    return e.Current;
                }
            }
        }
    }
}


Przedstawiona metoda została zoptymalizowana aby korzystać z możliwości które dają kolekcje. Jak wiadomo listy, tablice itd. można przeglądać odnosząc się do ich elementów poprzez indeks. Pobranie poprzez indeks wartości jest dużo szybsze niż iteracja po całej kolekcji i wybranie żądanej wartości.

Przykładowe użycie metody:

Code:
            var sequence = Enumerable.Range(0, 100);
            var selectec = sequence.RandomElement();

Brak komentarzy:

Prześlij komentarz