views:

251

answers:

5

Assume that you have an idCollection IList<long> and you have a method to get 4 unique ids.Every time you call it, it gives you random 4 unique ids ?

var idCollec = new[] {1,2,3,4,5,6,7,8,9,10,11,12}.ToList();

For example {2,6,11,12}
            {3,4,7,8}
            {5,8,10,12}
            ...
            ..

What is the smartest way to do it ?

Thanks

+1  A: 

If the IList<long> is populated with non unique values, you could use LINQ's Distinct() in combination with Take(), if it already has unique values, just use Take().

List<long> myUniqueIds = //prepoulation
var first4UniqueUnused = myUniqueIds.Take(4);

var next4UniqueUnused = myUniqueIds.Where(l=>!first4UniqueUnused.Contains(l)).Take(4);

another way that is too easy, i think we've been making it too hard:

List<long> myIDs = //prepopulation;
List<long> my4Random = new List<long>();
Random r = new Random();

for(int i=0; i< 4; i++)
{
     int j = r.Next();
     while(j>myIDs.Count || my4Random.Contains(myIDs[j]))
          j = r.Next();

     my4Random.Add(myIDs[j]);
}
Mike_G
Populated ids are unique
Barbaros Alp
This looks great but, it isnt random, is it ?
Barbaros Alp
I think this answer misses the point that the selected ids should be random.
driis
no its not. It would probably be better to populate the initial List<long> with random long numbers.
Mike_G
there really isnt enough info to determine whats "random" or not. What order is the initial list in?
Mike_G
list is not ordered and it isnt important. I have tried your way and takewhile returns 0. Because myUniqeIds count could be 5 or 6.
Barbaros Alp
The TakeWhile() returns 0 in my example because I am using a total of 3 different collections. In order for the TakeWhile to work, you have to first Take() to populate a collection.
Mike_G
I was wrong, you were right about the TakeWhile, it needed to be changed to a Where()
Mike_G
yes i understand that, there would be no meaning if i didnt take the firt Take(). But think about there are just 5 id in the collection. First you take the 4 one than try to take another 4 from the list which are not equal to existing one but there is just one id different from other
Barbaros Alp
A: 
Random random = new Random(); 

long firstOne = idCollection[random.Next(idCollection.Count)];
long secondOne = idCollection[random.NExt(idCollection.Count)];

...and so on

Sergiu Damian
It might not give you unique id collection you could have a repeated id in one of the long variable
Barbaros Alp
Not to mention that while this would work for 4 elements, if you needed to select, say 2000 elements, your code is going to start looking pretty nasty. (Although, if it was an odd case where it was always going to be, say, exactly two elements, this would be fine.)
Beska
+3  A: 

It can be done with a nice LINQ query. The key to doing it without the risk of getting duplicates, is to create a never ending IEnumerable of random integers. Then you can take n distinct values from it, and use them as indexes into the list.

Sample program:

using System;
using System.Collections.Generic;
using System.Linq;

namespace TestRandom
{
    class Program
{
    static void Main(string[] args)
    {
        // Just to prepopulate a list.
        var ids = (from n in Enumerable.Range(0, 100)
                   select (long)rand.Next(0, 1000)).ToList();

        // Example usage of the GetRandomSet method.
        foreach(long id in GetRandomSet(ids, 4))
            Console.WriteLine(id);
    }

    // Get count random entries from the list.
    public static IEnumerable<long> GetRandomSet(IList<long> ids, int count)
    {
        // Can't get more than there is in the list.
        if ( count > ids.Count)
            count = ids.Count;

        return RandomIntegers(0, ids.Count)
            .Distinct()
            .Take(count)
            .Select(index => ids[index]);
    }

    private static IEnumerable<int> RandomIntegers(int min, int max)
    {
        while (true)
            yield return rand.Next(min, max);
    }

    private static readonly Random rand = new Random();
}

}

If you use this approach, make sure you do not try to take more distinct values than there are available in the range passed to RandomIntegers.

driis
Thank you very much for your answer. It is like solid, 100% returns random ids. But did you check Mike_G or Davy8 ? their way looks smart too
Barbaros Alp
Yes, I read the other answers, which are also a fine way to get the job done. All the answers basically does the same thing, just in other ways and in slightly different order. Which one fits your purpose best will be a matter of how the answer fits your current code.
driis
+5  A: 

Seems like easiest way would be to have something like:

if(idCollection.Count <4)
{
    throw new ArgumentException("Source array not long enough");
}
List<long> FourUniqueIds = new List<long>(4);
while(FourUniqueIds.Count <4)
{
    long temp = idCollection[random.Next(idCollection.Count)];
    if(!FourUniqueIds.Contains(temp))
    {
        FourUniqueIds.add(temp);
    }
}
Davy8
ya, yours is easy (I didnt see it before I posted my version). I would add that the only thing missing is bounds checking on the array length of idColllection.
Mike_G
Thanks Dayv8, i think your is the smartest answer :) Thanks againI like this brain storm thing where everybody post answers by their way. Thanks everybody
Barbaros Alp
Actually Davy8 I messed, up I didnt see that random.Next(idCollection.Count) was defining the max, thats where i thought the error would come in. But I could be wrong on this as well, wouldnt it have to be idCollection[random.Next(idCollection.Count) -1] ?
Mike_G
i mean idCollection[random.Next(idCollection.Count -1)]
Mike_G
I guess if it was an ArrayList not an IList it need to be length-1
Barbaros Alp
This solution is fine. With regards to the index, it should not be Count - 1, because random.Next returns the next nonnegative number _below_ the value of the parameter.
driis
This is a little late, but shouldn't if(!idCollection.Contains(temp)) read if(!FourUniqueIds.Contains(temp))?
Steve
@Steve you're right, amazing that it wasn't noticed for over a year
Davy8
Fixed (10 more chars)
Davy8
@Davy8 - No problem. I think anyone who used it would have easily figured it out. Thanks for the code snip though. Came in useful.
Steve
+3  A: 

What about shuffling the set then just taking the first four each time?

public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> e)
{
    var r = new Random();
    return e.OrderBy(x => r.Next());
}

Then something like this? It would probably be faster to use a for loop instead of Take and Except.

 var ordered = new List<int> {1, 2, 3, 4, 5, 6, 7, 8, 10};

 var random = ordered.Shuffle();
 while(random.Count() > 0)
 {
     var ourSet = random.Take(4).ToList();            
     random = random.Except(ourSet);
 }
Mark
This looks awesome, thank you
Barbaros Alp