2

I'm trying to do a simple string generation based on pattern.
My idea was to use Regex to do simple replace. I've started with simple method:

private static string parseTemplate(string template)
{
    return Regex.Replace(template, @"(\[d)((:)?([\d]+)?)\]", RandomDigit());
}

private static string RandomDigit()
{
    Random r = new Random();
    return r.Next(0, 9).ToString();
}

What this does for now is replacing groups like [d], [d:3] with what supposed to be random digit.
Unfortunately every group is replaced with the same digit, for example if I put test [d][d][d:3] my method will return test 222.
I would like to get different digit in every place, like test 361.

Second problem I have is way to handle length:

right now I must specify [d] for every digit I want, but it would be easier to specify [d:3] and get the same output.

I know that there is a project called Fare, but I would like to do this without this library

For now I only search for [d], but is this method will work fine there won't be a problem to add other groups for example: [s] for special characters or any other type of patters.

Edit1

As it was suggested I changed Random to a static variable like so:

private static string parseTemplate(string template)
    {
        return Regex.Replace(template, @"(\[d)((:)?([\d]+)?)\]", RandomDigit());
    }

    private static Random r = new Random();

    private static string RandomDigit()
    {
        return r.Next(0, 9).ToString();
    }

Problem is that when I call my code like so:

Console.WriteLine(parseTemplate("test [d:2][d:][d]"));
Console.WriteLine(parseTemplate("test [d:2][d:][d]")); 

I get output like this

test 222
test 555

I would like output like this (for example):

test 265
test 962

I think that problem is with Regex.Replace which calls my RandomDigit only once.

Misiu
  • 4,738
  • 21
  • 94
  • 198
  • This is an issue with your use of the Random class, you need to initialize it with a different seed or wait for some time between calls to make sure it returns a different value. – Ravi Y Jan 18 '13 at 10:05
  • Check this [answer](http://stackoverflow.com/a/4613724/674700). – Alex Filipovici Jan 18 '13 at 10:06
  • 1
    Make your Random static: http://stackoverflow.com/questions/4855756/random-number-generation-same-number-returned – Jan Van Herck Jan 18 '13 at 10:07
  • 1
    static Random is not an issue here :) I've added some code to my question. Regex.Replace seems to call my random method only once. – Misiu Jan 18 '13 at 10:14

3 Answers3

3

For your first issue: When you call new Random() you are seeding with the same value every time you call the function - initialise a static Random member variable once then return r.Next(0,9).ToString();

Edit:

In answer to your comment, try using MatchEvaluator with a delegate, something like the following (untested):

static string RandomReplacer(Match m)
{
    var result = new StringBuilder();
    foreach (char c in m.ToString())
        result.Append(c == 'd' ? RandomDigit() : c);
    return result.ToString()
}

private static string parseTemplate(string template)
{
    return Regex.Replace(template, @"(\[d)((:)?([\d]+)?)\]", new MatchEvaluator(RandomReplacer));
}

You can then extend this approach to match [d:3] and parse it in your MatchEvaluator accordingly, solving your second issue.

Community
  • 1
  • 1
Alex
  • 7,639
  • 3
  • 45
  • 58
  • 2
    static Random isn't my problem. It looks like `Regex.Replace` is calling my random function only once. – Misiu Jan 18 '13 at 10:18
  • Updated answer (the [msdn article](http://msdn.microsoft.com/en-us/library/ht1sxswy.aspx) on the `Regex.Replace(String, String, MatchEvaluator)` overload has a good example of using Random()) – Alex Jan 18 '13 at 10:29
  • thanks for that :) Got it even simpler with lambda: `return Regex.Replace(template, @"(\[d)((:)?([\d]+)?)\]", m => RandomDigit());` – Misiu Jan 18 '13 at 10:31
1

Assumnig [d:3] means "three random digits", the following MatchEvaluator uses the length (read from group 4) to generate a random digit string:

static string ReplaceSingleMatch(Match m)
{
    int length;
    if (!int.TryParse(m.Groups[1].Value, out length))
        length = 1;
    char[] chars = new char[length];
    for (int i = 0; i < chars.Length; i++)
        chars[i] = RandomDigit()[0];
    return new string(chars);
}

You can then call this as follows:

private static string parseTemplate(string template)
{
    return Regex.Replace(template, @"\[d(?::(\d+))?\]", ReplaceSingleMatch);
}

You might want to then change RandomDigit to return a single char rather than a string, or to take an int and return multiple characters.

Rawling
  • 49,248
  • 7
  • 89
  • 127
  • You may need to wrap the delegate call in a `new MatchEvaluator()` – Alex Jan 18 '13 at 10:32
  • @AlexG I didn't need to, but maybe you do in an earlier version of .NET. – Rawling Jan 18 '13 at 10:35
  • @Rawling - Thanks for that :) awesome work. One more question - how should I change that regex so it will be valid only for `[d]` and `[d:3]` (any number after colon) but NOT for `[d:]` - after colon there is no digit. – Misiu Jan 18 '13 at 10:38
  • @Misiu I'm not sure how to _modify_ your pattern to do this, but if you use `@"\[d(?::(\d+))?\]"` and read the length from group `1` not group `4`, that should work. – Rawling Jan 18 '13 at 10:43
  • @Rawling - that works like a charm. Looks like I have to learn a lot about Regex. Don't know why there is double colon, but I'll find out. Thanks for help :) – Misiu Jan 18 '13 at 10:50
  • The `(?:foo)` groups things together (so we can apply the "optional" `?` afterwards) without _capturing_ the group into `Groups`. The second `:` is a literal colon to match in the input. Check [this site](http://www.regular-expressions.info/) out to learn all about regular expressions :) – Rawling Jan 18 '13 at 10:56
  • @Rawling - I definitely will learn them :) I know that is bit out of that question, but how should I extend my code to parse other patterns? Like `[s:3]` for random string? Can I do that with one regex and then inside that match evaluator switch types? I suppose I must add another group into my regex? Could You help me with that? :) – Misiu Jan 18 '13 at 11:05
  • Got this one :) `\[([cds])(?::([\d]+))?\]` - this way I can have 3 groups `c` for char, `d` for digits and `s` for specials. – Misiu Jan 18 '13 at 11:11
  • Yup, good job, beat me to it :D The letter will be in `Groups[1]`, the length in `Groups[2]`. You can then switch on the letter in the match evaluator. – Rawling Jan 18 '13 at 11:12
  • 1
    @Rawling again thanks for help, maybe for You it's not something special, but for me it's great piece of code :) – Misiu Jan 18 '13 at 11:19
1
private static string GenerateMask(string mask)
{
    StringBuilder output = new StringBuilder();
    for (int i = 0; i < mask.Length; i++)
    {
        if (mask[i] == 'd' && mask[i - 1] != '\\')
        {
            int quantifier = 1;

            if (mask[i + 1] == ':')
                Int32.TryParse(mask[i + 2].ToString(), out quantifier);

            output.Append(GetRandomDigit(quantifier));
            i += 2;
        }
        else
        {
            if(mask[i] != '\\')
            output.Append(mask[i]);
        }
    }

    return output.ToString();
}

private static string GetRandomDigit(int length)
{
    Random random = new Random();
    StringBuilder output = new StringBuilder();
    while (output.Length != length)
        output.Append(random.Next(0, 9));
    return output.ToString();
}

There's a custom algorithm I just put together for fun mostly and here's the implementation:

Console.WriteLine(GenerateMask(@"Hey Da\d, meet my new girlfrien\d she's d:2"));
//Output: Hey Dad, meet my new girlfriend she's 44
Caster Troy
  • 2,796
  • 2
  • 25
  • 45
  • I'll check this out :) Seems that there is an option to do this without regex. Could You show some examples? What do You pass as mask? What do You think would be better? – Misiu Jan 18 '13 at 10:53
  • I updated the original post. Although fun, if the other guys' methods are working for you then I would use theirs myself. – Caster Troy Jan 18 '13 at 10:57
  • their seems to work better, but Your is easier to extend - add other pattern like `c` for random character or `s` for special and so on :) – Misiu Jan 18 '13 at 11:02
  • Watch out for the ArgumentOutOfRangeError when your string starts with a lowercase 'd' – Alex Jan 18 '13 at 12:24