4

I'd like to assign the variable vStreamID with a random number. This number should be newly generated as long as my dictionary md_StreamDict contains the generated number.

Long version:

vStreamID = (new Random()).Next(1000, 9999).ToString();
while (md_StreamDict.ContainsKey(vStreamID)) {
    vStreamID = (new Random()).Next(1000, 9999).ToString();
}

I would like to see something LINQ style

md_StreamDict.ContainsKey(vStreamID)
    .while( x => x = (new Random())
    .Next(1000, 9999)
    .ToString();

I know the example above is bananas. But I would be happy if there's a real way to achieve this. And no, we're not starting the usual discussion about readability again. ;)

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
Neurodefekt
  • 899
  • 2
  • 10
  • 18
  • 3
    Even the long version could be shorter if you used `do { } while ();` :-) – fero Dec 28 '12 at 13:42
  • So you just need a number in definite diapason, and this number should not be already in dictionary, why you need Random for this? – sll Dec 28 '12 at 13:46
  • I think Neurodefect needs a new item in his dictionary where the key is random and doesn't exist in the dictionary. Why a linq, dunno.. – Ralf de Kleine Dec 28 '12 at 13:48
  • 1
    Never call `new Random()` that many times. Just create one instance of `Random`, and reuse that random number generator. Otherwise, you might create identical `Random` instances (the time might not have changed from one instantiation to the next), and then `.Next(...)` will give the same number for each `Random` instance. – Jeppe Stig Nielsen Dec 28 '12 at 14:06
  • If you want `9999` to be a possible outcome, use `.Next(1000, 10000)` instead. – Jeppe Stig Nielsen Dec 28 '12 at 14:21

5 Answers5

4

You need a way to generate an infinite enumerable for streaming random numbers. You can approximate by using Enumerable.Range(0, int.MaxValue):

var rand = new Random();
var r = Enumerable.Range(0, int.MaxValue)
    .Select(s => rand.Next(1000, 9999))
    .SkipWhile(s => md_StreamDict.ContainsKey(s.ToString()))
    .First();

Then r would contain a new key value not contained in the dictionary.

mellamokb
  • 56,094
  • 12
  • 110
  • 136
  • That won't create an infinite list, and you can't be 100% sure it will contain all the values from 1000-9999. You would have to either do `while(true) yield random.Next()` or create the list of int in that range and shuffle. – Jon B Dec 28 '12 at 13:54
  • @JonB: That is correct. Although I think if you made it all the way to `4294967296` without finding the missing key, you could write a book on statistical anomalies. Not too mention that if your dictionary is that full, you're going to be waiting a long time just to find a single hole with any method. – mellamokb Dec 28 '12 at 13:56
  • 1
    Don't create an instance of `Random` in the loop! See http://stackoverflow.com/a/3053811/880990 – Olivier Jacot-Descombes Dec 28 '12 at 14:02
  • @OlivierJacot-Descombes: Good point. Updated. I was just blindly replicating the OP's original logic in LINQ format. – mellamokb Dec 28 '12 at 14:03
4

If I understand you right You just need a number in well known range and this number should not be already in a dictionary, so do this without Random:

Enumerable.Range(1000, 9999)
          .Where(n => !dict.ContainsKey(n))
          .FirstOrDefault();
sll
  • 61,540
  • 22
  • 104
  • 156
  • @Neurodefekt This always gives the _lowest_ (four-digit or less than 10999) number not in `dict`. – Jeppe Stig Nielsen Dec 28 '12 at 14:12
  • You can even say `.FirstOrDefault(n => !dict.ContainsKey(n))`. In any case, if `dict` contains all keys between 1000 and 10998, this returns 0. Oops, did you realize that `Range(1000, 9999)` gives the numbers 1000 through 10998? – Jeppe Stig Nielsen Dec 28 '12 at 14:16
  • @sll: Let me be more explicit: `(new Random()).Next(1000, 9999)` corresponds in range to `Enumerable.Range(1000, 8999)`, so your answer got it wrong. If the Original Poster really meant `(new Random()).Next(1000, 10000)`, that would be `Enumerable.Range(1000, 9000)`. – Jeppe Stig Nielsen Dec 28 '12 at 15:23
3

Here's some bananas too:

You can create a IEnumerable that would generate random numbers, like:

public class RandomList : IEnumerable<int>
{
    public IEnumerator<int> GetEnumerator()
    {
        Random r = new Random();

        while (true)
            yield return r.Next(1000, 9999);
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

Then you can do:

// I guess one could implement a seed constructor, if one was so inclined
RandomList rl = new RandomList(); 

int vStreamID = rl.SkipWhile(i => md_StreamDict.ContainsKey(i)).First();
SWeko
  • 30,434
  • 10
  • 71
  • 106
2

Now I've read your question 3 times, and I still don't know why (or how) anyone would do this with a LINQ expression. The only shorter solution I see is what I wrote in the comment:

var r = new Random();

do {
    vStreamID = r.Next(1000, 9999).ToString();
} while (md_StreamDict.ContainsKey(vStreamID));
fero
  • 6,050
  • 1
  • 33
  • 56
2

One approach would be to use yield to return the values

 private static Random rand = new Random();

    private static void Test()
    {
        Dictionary<string, string> values = new Dictionary<string, string>();
        string vStreamId = GetNewValues().Where(x => !values.ContainsKey(x)).First();
    }

    public static IEnumerable<string> GetNewValues()
    {
        While(true)
        {
           yield return rand.Next(1000, 9999).ToString();
        }
    }

Note that if you generate a new Random on every loop, you will get the same value every time as it is based on the system clock.

David McNee
  • 137
  • 1
  • 2
  • 10
  • 2
    That would yield a single value. – Jon B Dec 28 '12 at 13:52
  • Not sure I understand you Jon, the GetNewValues() function returns a new value on each call, and the LINQ statement will select the first one that's not in the dictionary? – David McNee Dec 28 '12 at 13:59
  • 2
    Try running it. Get new values will yield one random number and then return. You have to do `while(true) yield return...` – Jon B Dec 28 '12 at 14:02