abc123
abc123

Reputation: 18753

Random String of Certain Numbers and Letters - Only a certain number of times

Code:

    public char[] numbers = new char[] { '3', '4', '5' };
    public char[] letters = new char[] { 'X', 'T', 'M', 'W', 'Y', 'L' };

    //Generate a String with only 2 of the above letters and the rest filled 
    //with the numbers the characters shouldn't be always next to one another

I looked at some of the other solutions: var random = new Random();

        for (int i=0; i<50; i++)
        {
            var result = new string(
                Enumerable.Repeat(chars, 8)
                          .Select(s => s[random.Next(s.Length)])
                          .ToArray());
        }

but they just generate random strings not from a smaller character set nor do they limit the number of letters versus number of numbers.

Examples: Valid

X333Y453

X3L45453

XL333333

4L333X333

333L45L3

Invalid

5533Y453

333L45453

XL33L333

Can only have 2 characters and since there are 8 total characters there should be 6 numbers

Upvotes: 0

Views: 1457

Answers (5)

ErikE
ErikE

Reputation: 50201

With just a couple of helper methods:

public static class CombinatoricsHelper {
    private static readonly Random random = new Random();

    public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> enumerable)  {
        List<T> list = enumerable.ToList();
        int position = list.Count;
        while (position > 0) {
            int selectedIndex = random.Next(position);
            yield return list[selectedIndex];
            position -= 1;
            list[selectedIndex] = list[position];
        }  
    }

    public static IEnumerable<T> TakeRandom<T>(this IEnumerable<T> enumerable, int count) {
        List<T> list = enumerable.ToList();
        int remaining = count;
        while (remaining > 0) {
            yield return list[random.Next(list.Count)];
            remaining -= 1;
        }
    }
}

And your arrays:

public char[] numbers = new char[] { '3', '4', '5' };
public char[] letters = new char[] { 'X', 'T', 'M', 'W', 'Y', 'L' };

This becomes very straightforward:

return new string(letters
    .TakeRandom(2)
    .Concat(numbers.TakeRandom(6))
    .Shuffle()
    .ToArray()
);

Sample output:

3X44334L
335W555Y
LX443355
53T3Y333
W443W534
M5444W44
3553T33Y
X4443W45
433M533Y
54L445M4
55X5W444
543443XX

Even though my answer uses more code than other answers, I recommend it because the actual algorithmic part of interest (how you're creating the string) is separated from the implementation details that can be glossed over a bit (Shuffle and TakeRandom). That is, once someone understands what Shuffle and TakeRandom do, not only can they be reused but they make the rest of the code much clearer.

As a case in point to prove my assertion, my original answer here for you didn't allow the 2 selected letters to be the same letter. Because I separated the high-level algorithm from the implementation of the algorithm's logical parts, I fixed that by simply changing letters.Shuffle().Take(2) ... to letters.TakeRandom(2) .... Now you can see the power of using functions (even though they seem big and unwieldy sometimes) instead of just writing a raw implementation that does exactly one thing and nothing else.

Note: my Shuffle method is the Fisher-Yates shuffle in disguise.

A Friendly WARNING To Those Seeking Much Randomness

Please consider this quote from the Wikipedia Fisher-Yates Shuffle page, section:

The built-in pseudorandom number generator provided by many programming languages and/or libraries may often have only 32 bits of internal state, which means it can only produce 232 different sequences of numbers. If such a generator is used to shuffle a deck of 52 playing cards, it can only ever produce a very small fraction of the 52! ≈ 2225.6 possible permutations. It's impossible for a generator with less than 226 bits of internal state to produce all the possible permutations of a 52-card deck.

So be warned. You need to swap in a much more heavy-duty random number generator than the built-in .Net Random if you want to shuffle more than 12 items when running a 32-bit RNG, or more than 20 items with a 64-bit RNG, and somewhat reliably get a relatively equal chance for all possible outcomes (see the chart "Size of PRNG seeds and the largest list where every permutation could be reached" on the right side of the above article). Consider, for example, using the System.Security.Cryptography.RNGCryptoServiceProvider, but still pay attention to how much entropy it can provide! Don't use this to shuffle cards unless you really know what you're doing.

Upvotes: 2

Tonkleton
Tonkleton

Reputation: 547

Here is my solution. It starts by generating a string of 6 numbers, then inserts a random letter into a random index twice. That way, you should have an even distribution for all possible outputs.

An important thing to note about the C# Random class is that you should not reinstantiate it for every generated string or else you will be using the same seed and get the same output every time.

public string GenerateString()
{
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < 6; i++)
    {
        sb.Append(numbers[random.Next(numbers.Length)]);
    }
    int letterIndex = random.Next(6);
    sb.Insert(letterIndex, letters[random.Next(letters.Length)]);
    letterIndex = random.Next(7);
    sb.Insert(letterIndex, letters[random.Next(letters.Length)]);
    return sb.ToString();
}

Upvotes: 0

ideafixxxer
ideafixxxer

Reputation: 474

Here is one line solution if you like it

var result = new string(Enumerable.Repeat(0, 6).Select(o => new {value = numbers[random.Next(numbers.Length)], sort = random.Next()}).
            Union(Enumerable.Repeat(0, 2).Select(o => new {value = letters[random.Next(letters.Length)], sort = random.Next()})).
            OrderBy(o => o.sort).Select(o => o.value).ToArray());

Upvotes: 0

Alexei Levenkov
Alexei Levenkov

Reputation: 100527

Feels like there are 2 groups that need to be merged with particular rules:

  • 2 random characters from one set (letters), possibly with repetition
  • 6 random characters from other set (digits), with repetition
  • sometimes letters should have numbers between them - second group should be split into up to 3 parts: {0-6}, {0-6}, {the rest}

Approximate code:

IEnumerable<char> PickRandom(char[] source, int count)
{
   return Enumerable.Repeat(1, count)
                      .Select(s => source[random.Next(source.Length)]);
}


var randomLetters = PickRandom(letters, 2);
var randomNumbers = PickRandom(numbers, 6);

int lettersGroup1 = random.Next(2);
int group1 = random.Next(6);
int group2 = random.Next(6 - group1);

var result = randomNumbers.Take(group1)
     .Concat(randomLetters.Take(lettersGroup1))
     .Concat(randomNumbers.Skip(group1).Take(group2))
     .Concat(randomLetters.Skip(lettersGroup1))
     .Concat(randomNumbers.Skip(group1 + group2));
var stringResult = String.Join("", result);

Note: the code is strict exercise in LINQ, there are definitely more readable/efficient ways to build string of characters.

Upvotes: 2

Astrogat
Astrogat

Reputation: 1625

var random = new Random();       
for (int i=0; i<50; i++){
        var resultLetters  = new string(
            Enumerable.Repeat(chars, 2)
                      .Select(s => letters[random.Next(letters.Length)]));
    }

Will select two letters from the available pool. You can do the same with the numbers (but 5 times). If you want the order to be random you can do it by just shuffling the array in the end (Collections.shuffle will shuffle an Collection).

Upvotes: 0

Related Questions