The shuffle suggestion from the comments is mostly the correct approach here. Generally, you start with the full set, and then shuffle the set and take the first n
shuffled values. However, we can improve on that; since we only need a few numbers we can bail on the shuffle early. So we have this answer, based on adapting a Fisher-Yates shuffle to only do as much as needed:
class Program
{
static Random rand = new Random();
static IEnumerable<int> generateSet(int max, int count)
{
var corpus = Enumerable.Range(1, max).ToArray();
for (int i = 0; i < count; i++)
{
var nextIndex = rand.Next(i, max);
yield return corpus[nextIndex];
corpus[nextIndex] = corpus[i];
}
}
static void Main()
{
Console.Write("enter line amount: ");
int choice = int.Parse(Console.ReadLine());
var result = generateSet(49, choice);
Console.WriteLine(string.Join(" ", result));
Console.ReadKey(true);
}
}
See it here:
https://dotnetfiddle.net/aiJnS2
Note how this removes any interaction with the user from the part that does the work. It's better structure to separate those concerns. Also note how this moves the Random
instance up to the class, instead of the method. Random
works much better when you re-use the same instance over time.
The exception to this is when the set is truly large or unbounded. Then you need a different algorithm that checks every generated random value against the ones that came before, and tries again while (that word was a hint) it finds a collision. However, it's worth noting this is usually slower on average. Checking prior numbers works out to O(n log n)
, while generate+shuffle is only O(n)
. But if the value of n
is unknown or truly large (many thousands), while the number of random items is small, it can be more effective.