C # 'daki <T> Listesinden N rasgele öğe seçin


158

Genel bir listeden 5 rasgele öğe seçmek için hızlı bir algoritma gerekir. Örneğin, a'dan 5 rastgele eleman almak istiyorum List<string>.


12
Rastgele ile, Kapsayıcı veya Özel mi demek istediniz? IOW, aynı eleman bir kereden fazla seçilebilir mi? (gerçekten rastgele) Veya bir öğe seçildikten sonra, artık kullanılabilir havuzdan seçilemez mi?
Pretzel

Yanıtlar:


127

Her eleman için yineleme yapın ve seçim olasılığını yapın = (gerekli sayı) / (kalan sayı)

Eğer 40 öğeniz olsaydı, ilkinin 5/40 seçilme şansı olurdu. Eğer öyleyse, bir sonrakinin 4/39 şansı vardır, aksi takdirde 5/39 şansı vardır. Sonuna kadar 5 öğeniz olacak ve çoğu zaman ondan önce hepsine sahip olacaksınız.


33
Bunun çok yanlış olduğunu hissediyorum. Arka uç çok daha büyük olasılıklar göreceğinden, listenin arka ucu ön uçtan daha sık seçilecek gibi görünüyor. Örneğin, ilk 35 sayı seçilmezse, son 5 sayı seçilmelidir. İlk sayı sadece 5/40 şansı görür, ancak son sayı 5/40 kereden 1/1 daha sık görülür. Bu algoritmayı uygulamadan önce listeyi rastgele ayarlamanız gerekir.
Ankur Goel

23
Tamam, bu algoritmayı, her biri bir 5/40 (.125) çekimin seçildiği 40 element listesinde 10 milyon kez çalıştırdım ve sonra bu simülasyonu birkaç kez çalıştırdım. Bunun eşit olarak dağıtılmadığı ortaya çıkıyor. Eleman 22'den 22'ye kadar elemanlar seçilmez hale gelir (16 = .123, 17 = .124), eleman 34'e fazla seçim yapılır (34 = .129). 39 ve 40. elementler de göz ardı ediliyor ama fazla değil (39 = .1247, 40 = .1246)
Ankur Goel

22
@Ankur: Bunun istatistiksel olarak anlamlı olduğuna inanmıyorum. Bunun eşit bir dağıtım sağlayacağına dair endüktif bir kanıt olduğuna inanıyorum.
özyinelemeli

9
Aynı denemeyi 100 milyon kez tekrarladım ve denememde en az seçilen öğe, en sık seçilen maddeden% 0.106'dan daha az sıklıkta seçildi.
özyinelemeli

5
@recursive: Kanıt neredeyse önemsizdir. Herhangi bir K için K öğesinin K dışında nasıl seçileceğini ve herhangi bir N için N öğesinde 0 öğenin nasıl seçileceğini biliyoruz. K / N olasılığı olan ilk öğeyi seçip sonra kalan N-1'den hala ihtiyaç duyulan K veya K-1 öğelerini seçmek için bilinen yöntemi kullanarak K öğelerini N dışında seçebiliriz.
Ilmari Karonen

216

Linq kullanarak:

YourList.OrderBy(x => rnd.Next()).Take(5)

2
+1 Ancak, iki öğe rnd.Next () veya benzerlerinden aynı sayıyı alırsa, ilk öğe seçilecektir ve ikincisi muhtemelen olmayacaktır (daha fazla öğe gerekmiyorsa). Yine de kullanıma bağlı olarak yeterince rastgele.
Lasse Espeholt

8
Bence sipariş O (n log (n)) olduğunu, bu yüzden kod sadeliği ana endişe (yani küçük listeler ile) ise bu çözümü seçiyor.
Guido

2
Ancak bu tüm listeyi numaralandırmıyor ve sıralamıyor mu? "Hızlı" ile OP "kolay" anlamına gelmedikçe, "performant" değil ...
drzaus

2
Bu yalnızca OrderBy () öğesi her bir öğe için anahtar seçiciyi yalnızca bir kez çağırdığında çalışır. İki eleman arasında bir karşılaştırma yapmak istediğinde çağırırsa, her seferinde farklı bir değer alır ve bu da sıralamayı bozar. [Belgeler] ( msdn.microsoft.com/en-us/library/vstudio/… ) hangisini yaptığını söylemiyor.
Oliver Bock

2
YourListÇok fazla öğe olup olmadığına dikkat edin , ancak yalnızca birkaçını seçmek istiyorsunuz. Bu durumda, bunu yapmanın etkili bir yolu değildir.
Callum Watkins

39
public static List<T> GetRandomElements<T>(this IEnumerable<T> list, int elementsCount)
{
    return list.OrderBy(arg => Guid.NewGuid()).Take(elementsCount).ToList();
}

27

Bu aslında göründüğünden daha zor bir problemdir, çünkü esasen matematiksel olarak doğru birçok çözüm tüm olasılıkları vurmanıza izin vermeyecektir (aşağıda daha fazlası).

Birincisi, uygulaması kolay, eğer gerçekten rastgele bir sayıya sahipseniz doğru:

(0) Kyle'ın cevabı, O (n).

(1) n çiftinin bir listesini [[0, rand), (1, rand), (2, rand), ...] oluşturun, ikinci koordinatına göre sıralayın ve ilk k'yi kullanın (sizin için, k = 5) rastgele alt kümenizi almak için endeksler. Sanırım O (n log n) zamanı olmasına rağmen uygulanması kolay.

(2) k rastgele öğelerin endeksleri olarak büyüyecek boş bir s = [] listesi başlatın. {0, 1, 2, ..., n-1} 'de rastgele bir r seçin, r = rand% n ve bunu s'ye ekleyin. Sonra r = rand% (n-1) alın ve s'ye yapıştırın; çarpışmalardan kaçınmak için # öğelerini s'den daha küçük olarak ekleyin. Sonra r = rand% (n-2) alın ve s'de k farklı elementiniz olana kadar aynı şeyi yapın. Bu en kötü çalışma süresine O (k ^ 2) sahiptir. Yani k << n için bu daha hızlı olabilir. Sıralı tutar ve hangi bitişik aralıklara sahip olduğunu izlerseniz, O (k log k) içinde uygulayabilirsiniz, ancak bu daha fazla iştir.

@Kyle - haklısın, ikinci olarak cevabına katılıyorum. İlk başta aceleyle okudum ve yanlışlıkla yanlış olan her bir öğeyi sabit olasılık k / n ile sıralı olarak seçtiğinizi düşündüğünüzü düşündüm - ancak uyarlanabilir yaklaşımınız bana doğru görünüyor. Bunun için üzgünüm.

Tamam ve şimdi vurucu için: asimptotik olarak (sabit k, n büyümesi için), n ^ k / k var! k element altkümesinin n elementten seçimi [bu bir kestirimdir (n k seçin)]. N büyükse ve k çok küçük değilse, bu sayılar çok büyüktür. Herhangi bir standart 32 bit rasgele sayı üretecinde umabileceğiniz en iyi çevrim uzunluğu 2 ^ 32 = 256 ^ 4'tür. 1000 öğeden oluşan bir listemiz varsa ve rastgele 5 tane seçmek istiyorsak, standart bir rastgele sayı üretecinin tüm olasılıklara çarpması mümkün değildir. Bununla birlikte, daha küçük setler için iyi çalışan ve her zaman rastgele görünüyor "bir seçim ile Tamam sürece, bu algoritmalar tamam olmalıdır.

Zeyilname : Bunu yazdıktan sonra, fikri (2) doğru bir şekilde uygulamanın zor olduğunu fark ettim, bu yüzden bu cevabı açıklığa kavuşturmak istedim. O (k log k) süresini elde etmek için, O (log m) aramalarını ve eklerini destekleyen dizi benzeri bir yapıya ihtiyacınız vardır - dengeli bir ikili ağaç bunu yapabilir. S adlı bir dizi oluşturmak için böyle bir yapı kullanarak, bazı pseudopython:

# Returns a container s with k distinct random numbers from {0, 1, ..., n-1}
def ChooseRandomSubset(n, k):
  for i in range(k):
    r = UniformRandom(0, n-i)                 # May be 0, must be < n-i
    q = s.FirstIndexSuchThat( s[q] - q > r )  # This is the search.
    s.InsertInOrder(q ? r + q : r + len(s))   # Inserts right before q.
  return s

Bunun yukarıdaki İngilizce açıklamayı nasıl etkili bir şekilde uyguladığını görmek için birkaç örnek durumdan geçmenizi öneririm.


2
(1) için bir listeyi sıralamaya göre daha hızlı karıştırabilirsiniz, (2) için%
jk

Eğer bir RNG döngüsü uzunluğu hakkında itirazı göz önüne alındığında, herhangi bir yolu yoktur yapabilirsiniz eşit olasılıkla tüm setleri seçecektir bir algoritma oluşturmak?
Jonah

(1) için, O (n günlüğü (n)) geliştirmek için k en küçük öğeleri bulmak için seçim sıralaması kullanabilirsiniz. Bu O (n * k) cinsinden çalışacaktır.
Jared

@Jonah: Sanırım. Daha büyük bir tane oluşturmak için birden fazla bağımsız rasgele sayı üretecini birleştirebileceğimizi varsayalım ( crypto.stackexchange.com/a/27431 ). O zaman sadece söz konusu listenin büyüklüğü ile başa çıkmak için yeterince geniş bir aralığa ihtiyacınız var.
Jared

16

Seçilen cevabın doğru ve oldukça tatlı olduğunu düşünüyorum. Yine de sonucu rastgele sırayla istediğim için farklı uyguladım.

    static IEnumerable<SomeType> PickSomeInRandomOrder<SomeType>(
        IEnumerable<SomeType> someTypes,
        int maxCount)
    {
        Random random = new Random(DateTime.Now.Millisecond);

        Dictionary<double, SomeType> randomSortTable = new Dictionary<double,SomeType>();

        foreach(SomeType someType in someTypes)
            randomSortTable[random.NextDouble()] = someType;

        return randomSortTable.OrderBy(KVP => KVP.Key).Take(maxCount).Select(KVP => KVP.Value);
    }

HARİKA! Gerçekten bana yardım etti!
Armstrongest

1
Environment.TickCount vs. DateTime.Now.Millisecond'a dayalı yeni Random () kullanmamak için herhangi bir nedeniniz var mı?
Lasse Espeholt

Hayır, temerrütün var olduğunun farkında değildi.
Frank Schwieterman

2
Tamam bir yıl geç ama ... Bu ersin daha kısa cevap için pan yok değil ve tekrarlanan rasgele bir sayı alırsanız başarısız olmaz (Ersin's tekrarlanan bir çiftin ilk öğesine karşı bir önyargı olacak)
Andiih

1
Random random = new Random(DateTime.Now.Millisecond);her çağrıda kesinlikle yanlış. RandomHer seferinde yeni bir örnek oluşturmak gerçek rastgeleliği azaltır. static readonlyTercihen varsayılan kurucu ile oluşturulmuş bir örneğini kullanın .
jpmc26

12

Ben sadece bu sorunla karşılaştı ve bazı daha google arama beni rastgele bir liste karıştırma sorununa getirdi: http://en.wikipedia.org/wiki/Fisher-Yates_shuffle

Listenizi (rastgele) tamamen rastgele karıştırmak için bunu yaparsınız:

N öğesinden oluşan bir dizi a'yı karıştırmak için (0..n-1 dizinleri):

  for i from n  1 downto 1 do
       j  random integer with 0  j  i
       exchange a[j] and a[i]

Yalnızca ilk 5 öğeye ihtiyacınız varsa, o zaman i'yi n-1'den 1'e kadar çalıştırmak yerine, yalnızca n-5'e çalıştırmanız gerekir (yani: n-5).

Diyelim ki k öğelere ihtiyacınız var,

Bu şöyle olur:

  for (i = n  1; i >= n-k; i--)
  {
       j = random integer with 0  j  i
       exchange a[j] and a[i]
  }

Seçilen her öğe dizinin sonuna doğru kaydırılır, böylece seçilen k öğeleri dizinin son k öğeleridir.

Bu zaman O (k) alır, burada k ihtiyacınız olan rastgele seçilen eleman sayısıdır.

Ayrıca, ilk listenizi değiştirmek istemiyorsanız, tüm takaslarınızı geçici bir listeye yazabilir, listeyi tersine çevirebilir ve tekrar uygulayabilir, böylece ters takas kümesini gerçekleştirebilir ve değiştirmeden ilk listenizi döndürebilirsiniz. O (k) çalışma süresi.

Son olarak, gerçek stickler için, (n == k) ise, rastgele seçilen tam sayı her zaman 0 olacağından, nk değil 1'de durmalısınız.


Blog yazımda C # kullanarak uyguladım: vijayt.com/post/random-select-using-fisher-yates-algorithm . Umarım C # yolunu arayan birine yardımcı olur.
vijayst


8

Gönderen Algoritma içinde Dragons , C # bir yorumuna:

int k = 10; // items to select
var items = new List<int>(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 });
var selected = new List<int>();
double needed = k;
double available = items.Count;
var rand = new Random();
while (selected.Count < k) {
   if( rand.NextDouble() < needed / available ) {
      selected.Add(items[(int)available-1])
      needed--;
   }
   available--;
}

Bu algoritma, öğeler listesinin benzersiz göstergelerini seçecektir.


Listede sadece yeterli miktarda öğe alın, ancak rastgele alınmayın.
culithay

2
Bu uygulama, varsonuçların kullanılması neededve availableher ikisi de tamsayı olması nedeniyle bozulur , bu da needed/availableher zaman 0 yapar.
Niko

1
Bu, kabul edilen cevabın bir uygulaması gibi görünüyor.
DCShannon

6

Bir gruptan N rastgele öğe seçmenin siparişle ilgisi olmamalıdır ! Rasgelelik öngörülemezlikle ilgilidir ve bir gruptaki pozisyonları karıştırmakla ilgili değildir. Biraz sıralama ile ilgili tüm cevaplar, yapmayanlardan daha az verimli olmak zorundadır. Verimlilik burada anahtar olduğundan, öğelerin sırasını çok fazla değiştirmeyen bir şey yayınlayacağım.

1) Gerçek rastgele değerlere ihtiyacınız varsa , hangi öğelerin seçileceği konusunda herhangi bir kısıtlama olmadığı anlamına gelir (yani, seçilen öğe yeniden seçildikten sonra):

public static List<T> GetTrueRandom<T>(this IList<T> source, int count, 
                                       bool throwArgumentOutOfRangeException = true)
{
    if (throwArgumentOutOfRangeException && count > source.Count)
        throw new ArgumentOutOfRangeException();

    var randoms = new List<T>(count);
    randoms.AddRandomly(source, count);
    return randoms;
}

İstisna bayrağını kapatırsanız, rastgele öğeleri istediğiniz sayıda seçebilirsiniz.

{1, 2, 3, 4} ürününüz varsa, 3 öğe için {1, 4, 4}, {1, 4, 3} vb. 5 öğe!

Kontrol edilecek bir şey olmadığı için bu oldukça hızlı olmalıdır.

2) Tekrarlama olmayan gruptan bireysel üyelere ihtiyacınız varsa , o zaman bir sözlüğe güvenirim (birçok kişi daha önce işaret ettiği gibi).

public static List<T> GetDistinctRandom<T>(this IList<T> source, int count)
{
    if (count > source.Count)
        throw new ArgumentOutOfRangeException();

    if (count == source.Count)
        return new List<T>(source);

    var sourceDict = source.ToIndexedDictionary();

    if (count > source.Count / 2)
    {
        while (sourceDict.Count > count)
            sourceDict.Remove(source.GetRandomIndex());

        return sourceDict.Select(kvp => kvp.Value).ToList();
    }

    var randomDict = new Dictionary<int, T>(count);
    while (randomDict.Count < count)
    {
        int key = source.GetRandomIndex();
        if (!randomDict.ContainsKey(key))
            randomDict.Add(key, sourceDict[key]);
    }

    return randomDict.Select(kvp => kvp.Value).ToList();
}

Kod, diğer sözlük yaklaşımlarından biraz daha uzundur çünkü ben sadece eklemekle kalmıyorum, aynı zamanda listeden de kaldırıyorum, bu yüzden iki döngü. Burada, eşit olunca hiçbir şeyi yeniden sıralamadığımı görebilirsiniz . Çünkü rastgeleliğin bir bütün olarak geri dönen sette olması gerektiğine inanıyorum . İstersen demek 5 rastgele ürün arasından , verilmemelidir olsun eğer onun veya ancak gerekirse 4 aynı kümesinden öğeleri, o zaman tahmin edilemeyecek içinde vermelidir , , gruptan öğe ekleyerek daha öğeler. Performans nedenleriyle kullanmak yerinecountsource.Count1, 2, 3, 4, 51, 3, 4, 2, 51, 2, 3, 4, 51, 2, 3, 41, 3, 5, 22, 3, 5, 4 vb İkincisi, rasgele öğelerin sayısı olmak ne zaman döndürülen orijinal grubun yarısından fazla, daha sonra kaldırmak daha kolaysource.Count - countcountsourcesourceDict Kaldır yönteminde rasgele dizin elde etmek için.

Dolayısıyla, {1, 2, 3, 4} varsa, bu, 3 öğe için {1, 2, 3}, {3, 4, 1} vb. İle sonuçlanabilir.

3) Orijinal gruptaki kopyaları dikkate alarak grubunuzdan gerçekten farklı rastgele değerlere ihtiyacınız varsa, yukarıdakiyle aynı yaklaşımı kullanabilirsiniz, ancak HashSetbir sözlükten daha açık olacaktır.

public static List<T> GetTrueDistinctRandom<T>(this IList<T> source, int count, 
                                               bool throwArgumentOutOfRangeException = true)
{
    if (count > source.Count)
        throw new ArgumentOutOfRangeException();

    var set = new HashSet<T>(source);

    if (throwArgumentOutOfRangeException && count > set.Count)
        throw new ArgumentOutOfRangeException();

    List<T> list = hash.ToList();

    if (count >= set.Count)
        return list;

    if (count > set.Count / 2)
    {
        while (set.Count > count)
            set.Remove(list.GetRandom());

        return set.ToList();
    }

    var randoms = new HashSet<T>();
    randoms.AddRandomly(list, count);
    return randoms.ToList();
}

randomsDeğişkeni yapılır HashSetçiftleri nadir durumlarda nadir ilave önlemek için Random.Nextgiriş listesi küçük olduğu durumlarda, aynı değeri verebilir.

Böylece {1, 2, 2, 4} => 3 rastgele öğe => {1, 2, 4} ve asla {1, 2, 2}

{1, 2, 2, 4} => 4 rastgele öğe => istisna !! veya ayarlanan işarete bağlı olarak {1, 2, 4}.

Kullandığım bazı uzantı yöntemleri:

static Random rnd = new Random();
public static int GetRandomIndex<T>(this ICollection<T> source)
{
    return rnd.Next(source.Count);
}

public static T GetRandom<T>(this IList<T> source)
{
    return source[source.GetRandomIndex()];
}

static void AddRandomly<T>(this ICollection<T> toCol, IList<T> fromList, int count)
{
    while (toCol.Count < count)
        toCol.Add(fromList.GetRandom());
}

public static Dictionary<int, T> ToIndexedDictionary<T>(this IEnumerable<T> lst)
{
    return lst.ToIndexedDictionary(t => t);
}

public static Dictionary<int, T> ToIndexedDictionary<S, T>(this IEnumerable<S> lst, 
                                                           Func<S, T> valueSelector)
{
    int index = -1;
    return lst.ToDictionary(t => ++index, valueSelector);
}

Listedeki öğelerin 1000'ler onlarca ile tüm ilgili performans 10000 kez tekrarlanır edilecek olan, o zaman olmasını isteyebilirsiniz hızlı rastgele sınıf daha System.Random, ama bunun büyük olasılıkla ikincisi dikkate büyük bir anlaşma asla düşünmüyorum darboğaz, yeterince hızlı ..

Düzenleme: Döndürülen öğelerin sırasını da yeniden düzenlemeniz gerekiyorsa, o zaman dhakim'in Fisher-Yates yaklaşımını yenebilecek hiçbir şey yok - kısa, tatlı ve basit ..


6

@JohnShedletsky tarafından kabul edilen cevap ( yorum) ile ilgili yorum düşünüyordum :

bunu O (originalList.Length) yerine O (altküme.Length) ile yapabilmeniz gerekir.

Temel olarak, subset rastgele indeksler ve daha sonra orijinal listeden koparabilmelisiniz.

Yöntem

public static class EnumerableExtensions {

    public static Random randomizer = new Random(); // you'd ideally be able to replace this with whatever makes you comfortable

    public static IEnumerable<T> GetRandom<T>(this IEnumerable<T> list, int numItems) {
        return (list as T[] ?? list.ToArray()).GetRandom(numItems);

        // because ReSharper whined about duplicate enumeration...
        /*
        items.Add(list.ElementAt(randomizer.Next(list.Count()))) ) numItems--;
        */
    }

    // just because the parentheses were getting confusing
    public static IEnumerable<T> GetRandom<T>(this T[] list, int numItems) {
        var items = new HashSet<T>(); // don't want to add the same item twice; otherwise use a list
        while (numItems > 0 )
            // if we successfully added it, move on
            if( items.Add(list[randomizer.Next(list.Length)]) ) numItems--;

        return items;
    }

    // and because it's really fun; note -- you may get repetition
    public static IEnumerable<T> PluckRandomly<T>(this IEnumerable<T> list) {
        while( true )
            yield return list.ElementAt(randomizer.Next(list.Count()));
    }

}

Hatta daha verimli olmak istedim, muhtemelen bir kullanırsınız HashSetait endeksleri gerçek liste öğelerini değil, (karmaşık türler veya pahalı karşılaştırmalar olması durumunda);

Birim Testi

Ve çarpışma vb. Olmadığından emin olmak için.

[TestClass]
public class RandomizingTests : UnitTestBase {
    [TestMethod]
    public void GetRandomFromList() {
        this.testGetRandomFromList((list, num) => list.GetRandom(num));
    }

    [TestMethod]
    public void PluckRandomly() {
        this.testGetRandomFromList((list, num) => list.PluckRandomly().Take(num), requireDistinct:false);
    }

    private void testGetRandomFromList(Func<IEnumerable<int>, int, IEnumerable<int>> methodToGetRandomItems, int numToTake = 10, int repetitions = 100000, bool requireDistinct = true) {
        var items = Enumerable.Range(0, 100);
        IEnumerable<int> randomItems = null;

        while( repetitions-- > 0 ) {
            randomItems = methodToGetRandomItems(items, numToTake);
            Assert.AreEqual(numToTake, randomItems.Count(),
                            "Did not get expected number of items {0}; failed at {1} repetition--", numToTake, repetitions);
            if(requireDistinct) Assert.AreEqual(numToTake, randomItems.Distinct().Count(),
                            "Collisions (non-unique values) found, failed at {0} repetition--", repetitions);
            Assert.IsTrue(randomItems.All(o => items.Contains(o)),
                        "Some unknown values found; failed at {0} repetition--", repetitions);
        }
    }
}

2
Sorunlarla güzel fikir. (1) Büyük listeniz çok büyükse (örneğin bir veritabanından okuyun), o zaman hafızayı aşabilecek tüm listeyi fark edersiniz. (2) K, N'ye yakınsa, döngünüzde hak talebinde bulunulmamış bir dizin ararken çok fazla şey harcarsınız ve bu da kodun öngörülemeyen bir süre gerektirmesine neden olur. Bu sorunlar çözülebilir.
Paul Chernoch

1
Çökertme sorununa benim çözümüm şuysa: K <N / 2 ise, istediğiniz gibi yapın. K> = N / 2 ise, saklanması gerekenler yerine saklanmaması gereken endeksleri seçin. Hala biraz dayak var, ama çok daha az.
Paul Chernoch

Ayrıca bunun, numaralandırılan öğelerin sırasını değiştirdiğini fark etti, bu bazı durumlarda kabul edilebilir, ancak bazılarında kabul edilemez.
Paul Chernoch

Ortalama olarak, K = N / 2 (Paul'ün önerdiği iyileştirme için en kötü durum) için, (daralma geliştirildi) algoritması ~ 0.693 * N yineleme alıyor gibi görünüyor. Şimdi bir hız karşılaştırması yapın. Bu kabul edilen cevaptan daha mı iyi? Hangi örnek boyutları için?
mbomb007

6

Lazily tarafından değerlendirilen bir uzatma yöntemi oluşturmak için yukarıdaki cevapların birkaçını birleştirdim. Testlerim Kyle'ın yaklaşımının (Order (N)), drzaus'un rasgele endeksleri seçmek için bir set kullanmasından (Order (K)) çok daha yavaş olduğunu gösterdi. Birincisi, rastgele sayı üretecine çok daha fazla çağrı yapar, ayrıca öğeler üzerinde daha fazla tekrarlar.

Uygulamamın amaçları:

1) IList olmayan bir IEnumerable verilirse tam listeyi gerçekleştirmeyin. Bana bir milyon öğeden oluşan bir dizi verilirse, hafızam tükenmek istemiyorum. Çevrimiçi bir çözüm için Kyle'ın yaklaşımını kullanın.

2) Bunun bir IList olduğunu söyleyebilirsem, drzaus'un yaklaşımını bir bükülme ile kullanın. K, N'nin yarısından fazlasıysa, tekrar tekrar birçok rastgele indeks seçtiğim ve atlamak zorunda olduğum için daralma riski taşıyorum. Böylece tutmak için endekslerin bir listesini oluşturmak.

3) Öğelerin karşılaşıldıkları sırayla iade edileceğini garanti ederim. Kyle'ın algoritması herhangi bir değişiklik gerektirmedi. drzaus algoritması, rastgele indekslerin seçildiği sırayla öğeleri yaymamamı gerektiriyordu. Tüm dizinleri bir SortedSet içinde topladım, sonra öğeleri sıralı dizin düzeninde yaydım.

4) K, N'ye kıyasla büyükse ve setin duygusunu tersine çeviririm, o zaman tüm öğeleri numaralandırırım ve indeksin sette olmadığını test ederim. Bu, Order (K) çalışma süresini kaybettiğim anlamına gelir, ancak bu durumlarda K N'ye yakın olduğu için fazla kaybetmem.

İşte kod:

    /// <summary>
    /// Takes k elements from the next n elements at random, preserving their order.
    /// 
    /// If there are fewer than n elements in items, this may return fewer than k elements.
    /// </summary>
    /// <typeparam name="TElem">Type of element in the items collection.</typeparam>
    /// <param name="items">Items to be randomly selected.</param>
    /// <param name="k">Number of items to pick.</param>
    /// <param name="n">Total number of items to choose from.
    /// If the items collection contains more than this number, the extra members will be skipped.
    /// If the items collection contains fewer than this number, it is possible that fewer than k items will be returned.</param>
    /// <returns>Enumerable over the retained items.
    /// 
    /// See http://stackoverflow.com/questions/48087/select-a-random-n-elements-from-listt-in-c-sharp for the commentary.
    /// </returns>
    public static IEnumerable<TElem> TakeRandom<TElem>(this IEnumerable<TElem> items, int k, int n)
    {
        var r = new FastRandom();
        var itemsList = items as IList<TElem>;

        if (k >= n || (itemsList != null && k >= itemsList.Count))
            foreach (var item in items) yield return item;
        else
        {  
            // If we have a list, we can infer more information and choose a better algorithm.
            // When using an IList, this is about 7 times faster (on one benchmark)!
            if (itemsList != null && k < n/2)
            {
                // Since we have a List, we can use an algorithm suitable for Lists.
                // If there are fewer than n elements, reduce n.
                n = Math.Min(n, itemsList.Count);

                // This algorithm picks K index-values randomly and directly chooses those items to be selected.
                // If k is more than half of n, then we will spend a fair amount of time thrashing, picking
                // indices that we have already picked and having to try again.   
                var invertSet = k >= n/2;  
                var positions = invertSet ? (ISet<int>) new HashSet<int>() : (ISet<int>) new SortedSet<int>();

                var numbersNeeded = invertSet ? n - k : k;
                while (numbersNeeded > 0)
                    if (positions.Add(r.Next(0, n))) numbersNeeded--;

                if (invertSet)
                {
                    // positions contains all the indices of elements to Skip.
                    for (var itemIndex = 0; itemIndex < n; itemIndex++)
                    {
                        if (!positions.Contains(itemIndex))
                            yield return itemsList[itemIndex];
                    }
                }
                else
                {
                    // positions contains all the indices of elements to Take.
                    foreach (var itemIndex in positions)
                        yield return itemsList[itemIndex];              
                }
            }
            else
            {
                // Since we do not have a list, we will use an online algorithm.
                // This permits is to skip the rest as soon as we have enough items.
                var found = 0;
                var scanned = 0;
                foreach (var item in items)
                {
                    var rand = r.Next(0,n-scanned);
                    if (rand < k - found)
                    {
                        yield return item;
                        found++;
                    }
                    scanned++;
                    if (found >= k || scanned >= n)
                        break;
                }
            }
        }  
    } 

Özel bir rastgele sayı üreteci kullanıyorum, ancak isterseniz C # 's Random'ı kullanabilirsiniz . ( FastRandom Colin Green tarafından yazılmıştır ve SharpNEAT'ın bir parçasıdır. 2 ^ 128-1 arasında bir süreye sahiptir ve bu birçok RNG'den daha iyidir.)

Birim testleri şunlardır:

[TestClass]
public class TakeRandomTests
{
    /// <summary>
    /// Ensure that when randomly choosing items from an array, all items are chosen with roughly equal probability.
    /// </summary>
    [TestMethod]
    public void TakeRandom_Array_Uniformity()
    {
        const int numTrials = 2000000;
        const int expectedCount = numTrials/20;
        var timesChosen = new int[100];
        var century = new int[100];
        for (var i = 0; i < century.Length; i++)
            century[i] = i;

        for (var trial = 0; trial < numTrials; trial++)
        {
            foreach (var i in century.TakeRandom(5, 100))
                timesChosen[i]++;
        }
        var avg = timesChosen.Average();
        var max = timesChosen.Max();
        var min = timesChosen.Min();
        var allowedDifference = expectedCount/100;
        AssertBetween(avg, expectedCount - 2, expectedCount + 2, "Average");
        //AssertBetween(min, expectedCount - allowedDifference, expectedCount, "Min");
        //AssertBetween(max, expectedCount, expectedCount + allowedDifference, "Max");

        var countInRange = timesChosen.Count(i => i >= expectedCount - allowedDifference && i <= expectedCount + allowedDifference);
        Assert.IsTrue(countInRange >= 90, String.Format("Not enough were in range: {0}", countInRange));
    }

    /// <summary>
    /// Ensure that when randomly choosing items from an IEnumerable that is not an IList, 
    /// all items are chosen with roughly equal probability.
    /// </summary>
    [TestMethod]
    public void TakeRandom_IEnumerable_Uniformity()
    {
        const int numTrials = 2000000;
        const int expectedCount = numTrials / 20;
        var timesChosen = new int[100];

        for (var trial = 0; trial < numTrials; trial++)
        {
            foreach (var i in Range(0,100).TakeRandom(5, 100))
                timesChosen[i]++;
        }
        var avg = timesChosen.Average();
        var max = timesChosen.Max();
        var min = timesChosen.Min();
        var allowedDifference = expectedCount / 100;
        var countInRange =
            timesChosen.Count(i => i >= expectedCount - allowedDifference && i <= expectedCount + allowedDifference);
        Assert.IsTrue(countInRange >= 90, String.Format("Not enough were in range: {0}", countInRange));
    }

    private IEnumerable<int> Range(int low, int count)
    {
        for (var i = low; i < low + count; i++)
            yield return i;
    }

    private static void AssertBetween(int x, int low, int high, String message)
    {
        Assert.IsTrue(x > low, String.Format("Value {0} is less than lower limit of {1}. {2}", x, low, message));
        Assert.IsTrue(x < high, String.Format("Value {0} is more than upper limit of {1}. {2}", x, high, message));
    }

    private static void AssertBetween(double x, double low, double high, String message)
    {
        Assert.IsTrue(x > low, String.Format("Value {0} is less than lower limit of {1}. {2}", x, low, message));
        Assert.IsTrue(x < high, String.Format("Value {0} is more than upper limit of {1}. {2}", x, high, message));
    }
}

Testte bir hata yok mu? Sen var if (itemsList != null && k < n/2)içeride hangi araçlar if invertSether zaman falsemantık hiç kullanılmamış olduğunu hangi araçlar.
NetMage

4

@ Ers'in cevabından uzayan, eğer OrderBy'nin olası farklı uygulamaları hakkında endişeleniyorsanız, bu güvenli olmalıdır:

// Instead of this
YourList.OrderBy(x => rnd.Next()).Take(5)

// Temporarily transform 
YourList
    .Select(v => new {v, i = rnd.Next()}) // Associate a random index to each entry
    .OrderBy(x => x.i).Take(5) // Sort by (at this point fixed) random index 
    .Select(x => x.v); // Go back to enumerable of entry

3

İlk kesime gelebileceğim en iyisi bu:

public List<String> getRandomItemsFromList(int returnCount, List<String> list)
{
    List<String> returnList = new List<String>();
    Dictionary<int, int> randoms = new Dictionary<int, int>();

    while (randoms.Count != returnCount)
    {
        //generate new random between one and total list count
        int randomInt = new Random().Next(list.Count);

        // store this in dictionary to ensure uniqueness
        try
        {
            randoms.Add(randomInt, randomInt);
        }
        catch (ArgumentException aex)
        {
            Console.Write(aex.Message);
        } //we can assume this element exists in the dictonary already 

        //check for randoms length and then iterate through the original list 
        //adding items we select via random to the return list
        if (randoms.Count == returnCount)
        {
            foreach (int key in randoms.Keys)
                returnList.Add(list[randoms[key]]);

            break; //break out of _while_ loop
        }
    }

    return returnList;
}

Toplam liste sayısı aralığında rasgele bir liste kullanmak ve daha sonra listeden bu öğeleri çekmek en iyi yol gibi görünüyordu, ancak benzersizliği sağlamak için Sözlüğü kullanmak hala üzerinde duruyorum.

Ayrıca bir dize listesi kullandığımı unutmayın, gerektiği gibi değiştirin.


1
İlk atışta çalıştı!
1616, sangam

3

Kullandığım basit çözüm (muhtemelen büyük listeler için iyi değil): Listeyi geçici listeye kopyalayın, sonra döngüde rastgele geçici listeden Öğe'yi seçin ve geçici liste formunu kaldırırken seçili öğeler listesine koyun (böylece olamaz yeniden seçilir).

Misal:

List<Object> temp = OriginalList.ToList();
List<Object> selectedItems = new List<Object>();
Random rnd = new Random();
Object o;
int i = 0;
while (i < NumberOfSelectedItems)
{
            o = temp[rnd.Next(temp.Count)];
            selectedItems.Add(o);
            temp.Remove(o);
            i++;
 }

Bir listenin ortasından sık sık kaldırmanın maliyeti yüksektir. Çok fazla kaldırma gerektiren bir algoritma için bağlantılı bir liste kullanmayı düşünebilirsiniz. Ya da eşdeğer olarak, kaldırılan öğeyi boş bir değerle değiştirin, ancak daha önce kaldırılmış öğeleri seçtiğinizde ve tekrar seçmeniz gerektiğinde biraz çökersiniz.
Paul Chernoch

3

Burada , algoritma karmaşıklığı O (n) olan Fisher-Yates Shuffle'a dayanan, John Shedletsky'nin işaret ettiği gibi n, liste boyutu yerine alt küme veya örnek boyutudur.

public static IEnumerable<T> GetRandomSample<T>(this IList<T> list, int sampleSize)
{
    if (list == null) throw new ArgumentNullException("list");
    if (sampleSize > list.Count) throw new ArgumentException("sampleSize may not be greater than list count", "sampleSize");
    var indices = new Dictionary<int, int>(); int index;
    var rnd = new Random();

    for (int i = 0; i < sampleSize; i++)
    {
        int j = rnd.Next(i, list.Count);
        if (!indices.TryGetValue(j, out index)) index = j;

        yield return list[index];

        if (!indices.TryGetValue(i, out index)) index = i;
        indices[j] = index;
    }
}

2

Kyle'ın cevabına dayanarak, benim c # uygulaması.

/// <summary>
/// Picks random selection of available game ID's
/// </summary>
private static List<int> GetRandomGameIDs(int count)
{       
    var gameIDs = (int[])HttpContext.Current.Application["NonDeletedArcadeGameIDs"];
    var totalGameIDs = gameIDs.Count();
    if (count > totalGameIDs) count = totalGameIDs;

    var rnd = new Random();
    var leftToPick = count;
    var itemsLeft = totalGameIDs;
    var arrPickIndex = 0;
    var returnIDs = new List<int>();
    while (leftToPick > 0)
    {
        if (rnd.Next(0, itemsLeft) < leftToPick)
        {
            returnIDs .Add(gameIDs[arrPickIndex]);
            leftToPick--;
        }
        arrPickIndex++;
        itemsLeft--;
    }

    return returnIDs ;
}

2

Bu yöntem Kyle'lara eşdeğer olabilir.

Listenizin n boyutunda olduğunu ve k öğeleri istediğinizi varsayalım.

Random rand = new Random();
for(int i = 0; k>0; ++i) 
{
    int r = rand.Next(0, n-i);
    if(r<k) 
    {
        //include element i
        k--;
    }
} 

Tıkır tıkır çalışıyor :)

-Alex Gilbert



1

neden böyle bir şey olmasın:

 Dim ar As New ArrayList
    Dim numToGet As Integer = 5
    'hard code just to test
    ar.Add("12")
    ar.Add("11")
    ar.Add("10")
    ar.Add("15")
    ar.Add("16")
    ar.Add("17")

    Dim randomListOfProductIds As New ArrayList

    Dim toAdd As String = ""
    For i = 0 To numToGet - 1
        toAdd = ar(CInt((ar.Count - 1) * Rnd()))

        randomListOfProductIds.Add(toAdd)
        'remove from id list
        ar.Remove(toAdd)

    Next
'sorry i'm lazy and have to write vb at work :( and didn't feel like converting to c#


1

Amaç: Çoğaltma olmadan toplama kaynağındaki N öğe sayısını seçin. Herhangi bir genel koleksiyon için bir uzantı oluşturdum. İşte böyle yaptım:

public static class CollectionExtension
{
    public static IList<TSource> RandomizeCollection<TSource>(this IList<TSource> source, int maxItems)
    {
        int randomCount = source.Count > maxItems ? maxItems : source.Count;
        int?[] randomizedIndices = new int?[randomCount];
        Random random = new Random();

        for (int i = 0; i < randomizedIndices.Length; i++)
        {
            int randomResult = -1;
            while (randomizedIndices.Contains((randomResult = random.Next(0, source.Count))))
            {
                //0 -> since all list starts from index 0; source.Count -> maximum number of items that can be randomize
                //continue looping while the generated random number is already in the list of randomizedIndices
            }

            randomizedIndices[i] = randomResult;
        }

        IList<TSource> result = new List<TSource>();
        foreach (int index in randomizedIndices)
            result.Add(source.ElementAt(index));

        return result;
    }
}

0

Geçenlerde bunu Tyler'ın 1. noktasına benzer bir fikir kullanarak projemde yaptım .
Bir sürü soru yükleyip rastgele beş tane seçiyordum. Sıralama bir IComparer kullanılarak gerçekleştirildi .
aTüm sorular Soru Listesi listesine yüklenmiştir; bu sorular daha sonra Listenin Sırala işlevi ve seçilen ilk k öğeleri kullanılarak sıralanmıştır .

    private class QuestionSorter : IComparable<QuestionSorter>
    {
        public double SortingKey
        {
            get;
            set;
        }

        public Question QuestionObject
        {
            get;
            set;
        }

        public QuestionSorter(Question q)
        {
            this.SortingKey = RandomNumberGenerator.RandomDouble;
            this.QuestionObject = q;
        }

        public int CompareTo(QuestionSorter other)
        {
            if (this.SortingKey < other.SortingKey)
            {
                return -1;
            }
            else if (this.SortingKey > other.SortingKey)
            {
                return 1;
            }
            else
            {
                return 0;
            }
        }
    }

Kullanımı:

    List<QuestionSorter> unsortedQuestions = new List<QuestionSorter>();

    // add the questions here

    unsortedQuestions.Sort(unsortedQuestions as IComparer<QuestionSorter>);

    // select the first k elements

0

İşte benim yaklaşımım (tam metin burada http://krkadev.blogspot.com/2010/08/random-numbers-without-repetition.html ).

O, O (N) yerine O (K) içinde çalışmalıdır, burada K istenen eleman sayısıdır ve N, seçim yapılacak listenin boyutudur:

public <T> List<T> take(List<T> source, int k) {
 int n = source.size();
 if (k > n) {
   throw new IllegalStateException(
     "Can not take " + k +
     " elements from a list with " + n +
     " elements");
 }
 List<T> result = new ArrayList<T>(k);
 Map<Integer,Integer> used = new HashMap<Integer,Integer>();
 int metric = 0;
 for (int i = 0; i < k; i++) {
   int off = random.nextInt(n - i);
   while (true) {
     metric++;
     Integer redirect = used.put(off, n - i - 1);
     if (redirect == null) {
       break;
     }
     off = redirect;
   }
   result.add(source.get(off));
 }
 assert metric <= 2*k;
 return result;
}

0

Bu, kabul edilen çözüm kadar zarif veya verimli değildir, ancak hızlı bir şekilde yazılır. İlk olarak, diziye rastgele izin verin, ardından ilk K öğelerini seçin. Python'da,

import numpy

N = 20
K = 5

idx = np.arange(N)
numpy.random.shuffle(idx)

print idx[:K]

0

Bir uzantı yöntemi kullanırdım.

    public static IEnumerable<T> TakeRandom<T>(this IEnumerable<T> elements, int countToTake)
    {
        var random = new Random();

        var internalList = elements.ToList();

        var selected = new List<T>();
        for (var i = 0; i < countToTake; ++i)
        {
            var next = random.Next(0, internalList.Count - selected.Count);
            selected.Add(internalList[next]);
            internalList[next] = internalList[internalList.Count - selected.Count];
        }
        return selected;
    }

0
public static IEnumerable<T> GetRandom<T>(this IList<T> list, int count, Random random)
    {
        // Probably you should throw exception if count > list.Count
        count = Math.Min(list.Count, count);

        var selectedIndices = new SortedSet<int>();

        // Random upper bound
        int randomMax = list.Count - 1;

        while (selectedIndices.Count < count)
        {
            int randomIndex = random.Next(0, randomMax);

            // skip over already selected indeces
            foreach (var selectedIndex in selectedIndices)
                if (selectedIndex <= randomIndex)
                    ++randomIndex;
                else
                    break;

            yield return list[randomIndex];

            selectedIndices.Add(randomIndex);
            --randomMax;
        }
    }

Bellek: ~ sayma
Karmaşıklık: O (sayma 2 )


0

N çok büyük olduğunda, N sayılarını rastgele karıştıran ve diyelim ki ilk k sayılarını seçen normal yöntem, alan karmaşıklığı nedeniyle engelleyici olabilir. Aşağıdaki algoritma hem zaman hem de alan karmaşıklıkları için sadece O (k) gerektirir.

http://arxiv.org/abs/1512.00501

def random_selection_indices(num_samples, N):
    modified_entries = {}
    seq = []
    for n in xrange(num_samples):
        i = N - n - 1
        j = random.randrange(i)

        # swap a[j] and a[i] 
        a_j = modified_entries[j] if j in modified_entries else j 
        a_i = modified_entries[i] if i in modified_entries else i

        if a_i != j:
            modified_entries[j] = a_i   
        elif j in modified_entries:   # no need to store the modified value if it is the same as index
            modified_entries.pop(j)

        if a_j != i:
            modified_entries[i] = a_j 
        elif i in modified_entries:   # no need to store the modified value if it is the same as index
            modified_entries.pop(i)
        seq.append(a_j)
    return seq

0

LINQ'yu büyük listelerle kullanma (her bir öğeye dokunmanın maliyeti yüksek olduğunda) VE çoğaltma olasılığı ile yaşayabiliyorsanız:

new int[5].Select(o => (int)(rnd.NextDouble() * maxIndex)).Select(i => YourIEnum.ElementAt(i))

Benim kullanımım için 100.000 elementin bir listesi vardı ve onlar bir DB I çekildi nedeniyle tüm listede bir rnd ile karşılaştırıldığında yaklaşık yarıya (veya daha iyi) zaman.

Büyük bir listeye sahip olmak, kopyaların oranlarını büyük ölçüde azaltacaktır.


Bu çözüm tekrarlanan elementlere sahip olabilir !! Delik listesindeki rastgele olmayabilir.
AxelWass

Hmm. Doğru. Nerede kullandığım önemli değil. Cevabı, bunu yansıtacak şekilde düzenledi.
Wolf5

-1

Bu sorununuzu çözecektir

var entries=new List<T>();
var selectedItems = new List<T>();


                for (var i = 0; i !=10; i++)
                {
                    var rdm = new Random().Next(entries.Count);
                        while (selectedItems.Contains(entries[rdm]))
                            rdm = new Random().Next(entries.Count);

                    selectedItems.Add(entries[rdm]);
                }

Bu soruya cevap verebilirken, bu kod bloğunun soruya nasıl cevap verdiğine dair bir açıklama eklemek için cevabınızı düzenlemelisiniz . Bu, bağlam sağlamaya yardımcı olur ve yanıtınızı gelecekteki okuyucular için çok daha kullanışlı hale getirir.
Hoppeduppeanut
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.