일반 목록에서 5 개의 임의 요소를 선택하려면 빠른 알고리즘이 필요합니다. 예를 들어,에서 5 개의 임의 요소를 얻고 싶습니다 List<string>
.
일반 목록에서 5 개의 임의 요소를 선택하려면 빠른 알고리즘이 필요합니다. 예를 들어,에서 5 개의 임의 요소를 얻고 싶습니다 List<string>
.
답변:
각 요소에 대해 반복하여 선택 확률 = (필수) / (왼쪽 수)
따라서 40 개의 아이템이 있다면 첫 번째는 5/40의 확률로 선택 될 것입니다. 만약 그렇다면, 다음은 4/39의 기회가 있고, 그렇지 않으면 5/39의 기회가 있습니다. 당신이 마지막에 도착할 때 당신은 당신의 5 개의 품목을 가질 것이고, 종종 당신은 그 전에 모든 것을 가질 것입니다.
linq 사용하기 :
YourList.OrderBy(x => rnd.Next()).Take(5)
YourList
항목을 많이 가지고 있지만 당신은 단지 몇 가지를 선택합니다. 이 경우 효율적인 방법이 아닙니다.
많은 수학적으로 수정 된 솔루션이 실제로 모든 가능성에 도달하지 못하기 때문에 이는 실제로는 생각보다 어려운 문제입니다 (아래에서 자세히 설명).
먼저, 구현하기 쉽고 정확한 경우 임의 숫자 생성기가 있습니다.
(0) Kyle의 대답은 O (n)입니다.
(1) n 쌍 [(0, rand), (1, rand), (2, rand), ...]의 목록을 생성하고 두 번째 좌표로 정렬하고 첫 번째 k를 사용하십시오 (k는 = 5) 임의의 부분 집합을 구하는 지수. O (n log n) 시간이지만 구현하기가 쉽다고 생각합니다.
(2) 빈 목록 s = []를 초기화하여 k 개의 랜덤 원소의 인덱스가되도록합니다. 무작위로 {0, 1, 2, ..., n-1}에서 숫자 r을 선택하고 r = rand % n을 s에 추가하십시오. 다음으로 r = rand % (n-1)을 취하고 s를 고수하십시오. 충돌을 피하기 위해 s보다 # 요소를 r에 더하십시오. 다음으로 r = rand % (n-2)를 취하고 s에 k 개의 고유 한 요소가있을 때까지 동일한 작업을 수행하십시오. 최악의 경우 실행 시간이 O (k ^ 2)입니다. k << n의 경우 더 빠를 수 있습니다. 정렬을 유지하고 인접한 간격을 추적하면 O (k log k)로 구현 할 수 있지만 더 많은 작업입니다.
@Kyle-네 말이 맞아, 나는 네 대답에 동의한다고 생각했다. 나는 처음에 급히 그것을 읽었고, 실수로 고정 확률 k / n을 가진 각 요소를 순차적으로 선택한다고 잘못 생각하고 있었지만 잘못 생각했을 것입니다. 미안합니다.
자, 이제 키커 : 무증상 (고정 k, n 증가)에는 n ^ k / k가 있습니다! n 개의 요소 중 k 개의 요소 서브 세트의 선택 [이것은 근사치 (n choose k)]입니다. n이 크고 k가 그다지 작지 않으면이 숫자는 엄청납니다. 표준 32 비트 난수 생성기에서 기대할 수있는 최상의 사이클 길이는 2 ^ 32 = 256 ^ 4입니다. 따라서 1000 개의 요소 목록이 있고 무작위로 5를 선택하려는 경우 표준 난수 생성기가 모든 가능성을 달성 할 방법이 없습니다. 그러나 더 작은 세트에 대해 잘 작동하고 항상 "임의로"보이는 선택에 만족하는 경우 이러한 알고리즘은 정상입니다.
부록 :이 글을 쓴 후에는 아이디어 (2)를 올바르게 구현하는 것이 까다 롭다는 것을 깨달았 으므로이 대답을 분명히하고 싶었습니다. O (k log k) 시간을 얻으려면 O (log m) 검색 및 삽입을 지원하는 배열과 유사한 구조가 필요합니다. 균형 잡힌 이진 트리가이를 수행 할 수 있습니다. 이러한 구조를 사용하여 s라는 배열을 작성하면 의사 파이썬이 있습니다.
# 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
위의 영어 설명을 효율적으로 구현하는 방법을 확인하기 위해 몇 가지 샘플 사례를 검토하는 것이 좋습니다.
선택한 답변이 정확하고 꽤 달콤하다고 생각합니다. 결과를 무작위 순서로 원했기 때문에 다르게 구현했습니다.
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);
}
방금이 문제에 부딪 쳤고 더 많은 Google 검색으로 인해 목록을 무작위로 섞는 문제가 발생했습니다. http://en.wikipedia.org/wiki/Fisher-Yates_shuffle
목록을 임의로 무작위로 섞으려면 다음과 같이하십시오.
n 개의 요소 a 배열을 셔플하려면 (0..n-1 표시) :
for i from n − 1 downto 1 do
j ← random integer with 0 ≤ j ≤ i
exchange a[j] and a[i]
처음 5 개 요소 만 필요한 경우 n-1에서 1까지 i를 실행하는 대신 n-5 만 실행하면됩니다 (예 : n-5).
k 개의 아이템이 필요하다고하자.
이것은 다음과 같습니다.
for (i = n − 1; i >= n-k; i--)
{
j = random integer with 0 ≤ j ≤ i
exchange a[j] and a[i]
}
선택된 각 항목은 배열의 끝으로 바뀝니다. 따라서 선택된 k 개의 요소는 배열의 마지막 k 요소입니다.
시간 O (k)가 필요합니다. 여기서 k는 임의로 선택한 요소의 수입니다.
또한 초기 목록을 수정하지 않으려는 경우 모든 스왑을 임시 목록에 기록하고 해당 목록을 뒤집은 다음 다시 적용하여 역 스왑 세트를 수행하고 변경하지 않고 초기 목록을 반환 할 수 있습니다 O (k) 실행 시간
마지막으로, 실제 stickler의 경우 (n == k) 인 경우 무작위로 선택한 정수가 항상 0이므로 nk가 아닌 1에서 정지해야합니다.
이것을 사용할 수 있지만 주문은 클라이언트 측에서 이루어집니다
.AsEnumerable().OrderBy(n => Guid.NewGuid()).Take(5);
에서 알고리즘의 드래곤 , C #의 해석 :
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--;
}
이 알고리즘은 항목 목록의 고유 한 인덱스를 선택합니다.
var
결과를 사용 needed
하고 available
정수를 사용하므로 needed/available
항상 0이
그룹에서 N 개의 랜덤 아이템을 선택 하는 것은 주문 과 관련이 없습니다 ! 무작위성은 예측 불가능성에 관한 것이며 그룹의 입장을 섞는 것이 아닙니다. 어떤 순서를 다루는 모든 대답은 그렇지 않은 것보다 덜 효율적입니다. 여기에는 효율성이 핵심이므로 항목 순서를 크게 변경하지 않는 내용을 게시하겠습니다.
1) 진정한 임의의 값이 필요한 경우 선택할 요소에 제한이 없음을 의미합니다 (예 : 선택한 항목을 다시 선택할 수 있음).
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;
}
예외 플래그를 해제하면 임의의 항목을 여러 번 선택할 수 있습니다.
{1, 2, 3, 4}가있는 경우 3 개 항목에 {1, 4, 4}, {1, 4, 3} 등을 줄 수 있으며 {1, 4, 3, 2, 4} 5 개 아이템!
확인할 것이 없기 때문에 이것은 매우 빠릅니다.
2) 반복하지 않고 그룹의 개별 구성원 이 필요한 경우 사전에 의존합니다 (많은 사람들이 이미 지적했듯이).
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();
}
코드는 여기에 추가 할뿐만 아니라 목록에서 제거하기 때문에 두 개의 루프가 있기 때문에 다른 사전 접근법보다 약간 길다. 여기에서 내가 같을 때 아무것도 다시 정렬 하지 않은 것을 볼 수 있습니다 . 무작위성이 전체적으로 반환 세트 에 있어야 한다고 믿기 때문 입니다. 당신이 원한다면 내 말은 5 개 임의 항목에서 ,이 문제의 경우 안 또는 당신이 필요로하는 경우, 만 4 개 동일한 세트에서 항목을, 다음은 예측할 수에 양보한다 , , 등 둘째, 임의 항목의 수는 수하는 경우 반환 된 그룹은 원래 그룹의 절반 이상이며 제거하기가 더 쉽습니다.count
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
source.Count - count
항목을 추가하는 것보다 그룹의count
항목. 성능상의 이유로 remove 메서드에서 임의의 인덱스를 얻는 source
대신 사용 했습니다 sourceDict
.
따라서 {1, 2, 3, 4}가 있으면 3 개의 항목에 대해 {1, 2, 3}, {3, 4, 1} 등으로 끝날 수 있습니다.
3) 원래 그룹의 복제본을 고려하여 그룹과 완전히 다른 임의의 값이 필요한 경우 위와 동일한 방법을 사용할 수 있지만 a HashSet
는 사전보다 가볍습니다.
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
변수는가된다 HashSet
중복 드문 드문 사례 첨가되지 않도록하기 위해 Random.Next
입력리스트가 작고, 특히, 같은 값을 얻을 수있다.
따라서 {1, 2, 2, 4} => 3 개의 임의 항목 => {1, 2, 4}, 절대 {1, 2, 2}
{1, 2, 2, 4} => 4 개의 랜덤 아이템 => 예외 !! 또는 플래그 세트에 따라 {1, 2, 4}입니다.
내가 사용한 확장 방법 중 일부는 다음과 같습니다.
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);
}
목록에있는 항목의 1000 수십와의 모든에 대한 성능은 10000 번 반복 할 필요가 있다면, 당신은 할 수 있습니다 빠른 임의의 클래스 이상을 System.Random
,하지만 난 그게 가장 아마 후자를 고려 큰 문제가 결코 아니다 생각하지 않는다 병목 현상, 충분히 빠릅니다 ..
편집 : 반환 된 항목의 순서를 다시 정렬 해야하는 경우 dhakim의 Fisher-Yates 접근법을 능가 할 수있는 것은 없습니다 -짧고 달콤하고 단순합니다.
@JohnShedletsky의 의견에 대해 (paraphrase) 에 대해 허용 된 답변 에 대해 생각하고있었습니다 .
O (originalList.Length) 대신 O (subset.Length)에서이 작업을 수행 할 수 있어야합니다.
기본적으로 subset
임의의 인덱스 를 생성 한 다음 원래 목록에서 추출 할 수 있어야 합니다.
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()));
}
}
당신이 훨씬 더 효율적으로 원한다면, 당신은 아마 사용하는 것이 HashSet
의 인덱스를 ,하지 (복잡한 유형 또는 고가의 비교있어 경우) 실제 목록 요소;
충돌 등이 없는지 확인합니다.
[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);
}
}
}
위의 답변 중 몇 가지를 결합하여 지연 평가 확장 방법을 만들었습니다. 내 테스트에 따르면 Kyle의 접근 방식 (Order (N))은 drzaus가 무작위 지수를 선택하도록 제안하는 세트를 사용하는 것보다 몇 배 느립니다 (Order (K)). 전자는 난수 생성기에 대해 더 많은 호출을 수행하고 항목에 대해 더 많은 시간을 반복합니다.
구현의 목표는 다음과 같습니다.
1) IList가 아닌 IEnumerable이 제공된 경우 전체 목록을 인식하지 마십시오. 일련의 zillion 항목이 주어지면 메모리가 부족하지 않습니다. 온라인 솔루션에는 Kyle의 접근 방식을 사용하십시오.
2) 그것이 IList라고 말할 수 있다면 drzaus의 접근 방식을 사용하십시오. K가 N의 절반 이상이면 랜덤 인덱스를 여러 번 반복해서 선택하고 건너 뛰면 스 래싱 위험이 있습니다. 따라서 나는 유지하지 않기 위해 지수 목록을 작성합니다.
3) 상품이 발생한 순서대로 반환됩니다. Kyle의 알고리즘은 변경하지 않아도됩니다. drzaus의 알고리즘은 임의의 인덱스가 선택된 순서대로 항목을 방출하지 않아야했습니다. 모든 인덱스를 SortedSet에 모은 다음 정렬 된 인덱스 순서로 항목을 내 보냅니다.
4) K가 N에 비해 크고 세트의 의미를 뒤집 으면 모든 항목을 열거하고 인덱스가 세트에 없는지 테스트합니다. 이것은 주문 (K) 런타임을 잃어 버렸음을 의미하지만이 경우 K는 N에 가깝기 때문에 많이 잃지 않습니다.
코드는 다음과 같습니다.
/// <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;
}
}
}
}
특수 난수 생성기를 사용하지만 원하는 경우 C #의 난수를 사용할 수 있습니다 . ( FastRandom는 콜린 그린에 의해 작성 및 SharpNEAT의 일부가되었습니다. 그것은 많은 RNG에보다 더 2 ^ 128-1의 기간이 있습니다.)
다음은 단위 테스트입니다.
[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)
(가) 내부에있는 방법을 if
invertSet
항상 false
논리가 사용되지 않습니다 것을하는 수단.
@ers의 답변에서 확장하여 OrderBy의 다른 구현에 대해 걱정이된다면 이것이 안전해야합니다.
// 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
이것이 첫 번째 컷에서 생각해 낼 수있는 최선입니다.
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;
}
1-전체 목록 수 범위 내에서 임의의 목록을 사용하고 목록에서 해당 항목을 가져 오는 것이 가장 좋은 방법 인 것처럼 보였지만 독창성을 보장하기 위해 사전을 사용하는 것이 여전히 고민하고 있습니다.
또한 문자열 목록을 사용하고 필요에 따라 교체하십시오.
내가 사용하는 간단한 해결책 (아마도 큰 목록에는 적합하지 않음) : 목록을 임시 목록에 복사 한 다음 루프에서 임시 목록에서 항목을 임의로 선택하고 임시 목록에서 제거하면서 선택한 항목 목록에 넣으십시오 (따라서 할 수 없습니다) 다시 선택).
예:
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++;
}
여기에는 John Shedletsky가 지적한 것처럼 알고리즘 복잡도가 O (n) 인 Fisher-Yates Shuffle을 기반으로 한 구현이 있습니다. 여기서 n은 목록 크기 대신 하위 집합 또는 샘플 크기입니다.
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의 답변을 바탕으로 내 C # 구현이 있습니다.
/// <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 ;
}
왜 이런식이 아닌지 :
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#
생각보다 훨씬 어렵습니다. Jeff 의 위대한 기사 "Shuffling" 을 참조하십시오 .
나는 C # 코드를 포함하여 그 주제에 대해 매우 짧은 기사를 썼다 :
주어진 배열의 N 요소의 무작위 서브 세트를 반환
목표 : 컬렉션 소스에서 중복없이 N 개의 항목을 선택합니다. 모든 일반 컬렉션에 대한 확장을 만들었습니다. 내가 한 방법은 다음과 같습니다.
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;
}
}
필자는 최근 타일러의 포인트 1 과 비슷한 아이디어를 사용하여 프로젝트 에서이 작업을 수행했습니다 .
나는 많은 질문을로드하고 무작위로 5를 선택했습니다. 정렬은 IComparer를 사용하여 달성되었습니다 .
a 모든 질문은 QuestionSorter 목록에로드 된 다음 List의 정렬 기능 과 선택된 첫 k 요소를 사용하여 정렬 되었습니다.
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;
}
}
}
용법:
List<QuestionSorter> unsortedQuestions = new List<QuestionSorter>();
// add the questions here
unsortedQuestions.Sort(unsortedQuestions as IComparer<QuestionSorter>);
// select the first k elements
여기 내 접근 방식이 있습니다 ( http://krkadev.blogspot.com/2010/08/random-numbers-without-repetition.html 여기에 전체 텍스트 ).
O (N) 대신 O (K)로 실행해야합니다. 여기서 K는 원하는 요소 수이고 N은 선택할 목록의 크기입니다.
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;
}
확장 방법을 사용합니다.
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;
}
}
메모리 : ~ count
Complexity : O (count 2 )
N이 매우 큰 경우, N 개의 숫자를 무작위로 섞어서 첫 번째 k 개의 숫자를 선택하는 일반적인 방법은 공간 복잡성 때문에 엄청나게 비쌀 수 있습니다. 다음 알고리즘은 시간과 공간 복잡성 모두에 O (k) 만 필요합니다.
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 사용 (각 요소를 다루는 데 비용이 많이 드는 경우) 및 중복 가능성으로 살 수있는 경우 :
new int[5].Select(o => (int)(rnd.NextDouble() * maxIndex)).Select(i => YourIEnum.ElementAt(i))
내 용도로는 100.000 요소 목록이 있었고 DB에서 가져온 시간 때문에 전체 목록의 rnd와 비교하여 시간의 절반 정도 (또는 더 나은) 시간을 보냈습니다.
목록이 크면 중복 가능성이 크게 줄어 듭니다.
문제가 해결됩니다
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]);
}