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>
.
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>
.
Yanıtlar:
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.
Linq kullanarak:
YourList.OrderBy(x => rnd.Next()).Take(5)
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.
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.
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);
}
Random random = new Random(DateTime.Now.Millisecond);
her çağrıda kesinlikle yanlış. Random
Her seferinde yeni bir örnek oluşturmak gerçek rastgeleliği azaltır. static readonly
Tercihen varsayılan kurucu ile oluşturulmuş bir örneğini kullanın .
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.
Bunu kullanabilirsiniz, ancak sipariş müşteri tarafında olacaktır
.AsEnumerable().OrderBy(n => Guid.NewGuid()).Take(5);
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.
var
sonuçların kullanılması needed
ve available
her ikisi de tamsayı olması nedeniyle bozulur , bu da needed/available
her zaman 0 yapar.
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 yerinecount
source.Count
1, 2, 3, 4, 5
1, 3, 4, 2, 5
1, 2, 3, 4, 5
1, 2, 3, 4
1, 3, 5, 2
2, 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 - count
count
source
sourceDict
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 HashSet
bir 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();
}
randoms
Değişkeni yapılır HashSet
çiftleri nadir durumlarda nadir ilave önlemek için Random.Next
giriş 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 ..
@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.
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 HashSet
ait endeksleri gerçek liste öğelerini değil, (karmaşık türler veya pahalı karşılaştırmalar olması durumunda);
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);
}
}
}
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));
}
}
if (itemsList != null && k < n/2)
içeride hangi araçlar if
invertSet
her zaman false
mantık hiç kullanılmamış olduğunu hangi araçlar.
@ 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
İ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.
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++;
}
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;
}
}
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 ;
}
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
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#
Birinin düşündüğünden çok daha zor. "Karışık" adlı büyük makaleye bakınJeff'in adlı .
C # kodu da dahil olmak üzere bu konuda çok kısa bir makale yazdım:
Belirli bir dizinin N öğelerinin rastgele alt kümesini döndürmek
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;
}
}
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
İş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;
}
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;
}
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 )
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
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 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]);
}