Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can someone break this lambda expression down for me?

I'm looking at the solution from Token Replacement and Identification:

string result = Regex.Replace(
    text,
    @"\[%RC:(\d+)%\]",
    match => dict[int.Parse(match.Groups[1].Value)]);

And I'm not understanding how the MatchEvaluator was overloaded.

I understand some of the lambda expression. It takes the input match and then looks up a integer from the dictionary?

But where does the value for match come from? and where does the value returned from match => dict[int.Parse(match.Groups[1].Value)]); go?

Edit: Some of you have mentioned Delegate. Surprisingly after three years in university for CS, I haven't come across this term. What is the Delegate and what does it do in this specific situation?

Last edit: I tried writing my own delegate with the following code Where my tokens are in the form of [@someTokenName]

public void CreateScript(Dictionary<string,string> dictionary, string path)
    {
        //used in the regex to identify the string to replace
        var pattern = @"\[@[0-9a-fA-F]+\]";
        //read in the file found at the path
        var lines = File.ReadAllLines(path);
        int lineCounter = 0;
        foreach (string line in lines)
        {
            line = Regex.Replace(line, pattern, match => dictionary.TryGetValue((match.Groups[0].Value)));
        }

But I keep getting a `Cannot convert lambda expression to type 'int' because it is not a delegate type. What's the difference between the line I wrote and the one found in the solution?

like image 918
Wusiji Avatar asked May 21 '13 19:05

Wusiji


1 Answers

This is a regular expression matched with a lambda, so a bit more complicated than your regular old lambda. Here we go:

Given:

String result = Regex.Replace(
    text,
    @"\[%RC:(\d+)%\]",
    match => dict[int.Parse(match.Groups[1].Value)]);

(1) text - the text we're searching through.

(2) @"\[%RC:(\d+)%\]" - means find anything that looks like "[%RC:{number}%]" where {number} is obviously some number (since \d means "a number", and \d+ means "one or more numbers in succession"). Also notice that the {number} or \d+ is surrounded by ( ) as in (\d+). This is important because it means that the number is a "group", which has bearing to our explanation below. Groups are a way of extracting the 'useful' part from a regular expression. That is, we don't want the whole match, just the number value.

(3) When it finds a match, it executes this: match => dict[int.Parse(match.Groups[1].Value)]);

Let's start with this part: match => ..., which is effectively the same as:

public String MatchEval(Match match)
{

}

remember that a lambda expression is essentially just short hand for a regular function (except that the compiler infers the type for match and it's return type based on the delegate it's standing in for - here a MatchEvaluator - more on this in a moment). Here, the input match is passed into the lambda expression as an input. Then you have => which begins the function body similar to the { } that we see in our MatchEval function above. As a result, each time a match is found, the code equivalent to this block is run:

public String MatchEval(Match match)
{
    // Here we grab the value from group (1) (the number in parentasis from our Regex)
    return dict[int.Parse(match.Groups[1].Value)];
}

In short, remember that a lambda is just shorthand notation for a function. If you look at the documentation for Regex.Replace, you'll see that the lambda is standing in for a MatchEvaluator which is defined as:

public delegate string MatchEvaluator(Match match);

which lines up with our function expansion above. In fact, you could simply write:

String result = Regex.Replace(
    text,
    @"\[%RC:(\d+)%\]",
    MatchEval);

(assuming dict was accessible from a separate method) and the function would work just the same proving that a lambda is just a shorter notation for a complete function.

Edit: As for the second part of your question, "What is a delegate", a delegate essentially solves the problem: "I don't know what function I want to use, but I know what signature it has". Consider:

// This allows us to point to a math function with this signature,
// namely, takes two Int32 inputs, and returns an Int32.
public static delegate Int32 MathDelegate(Int32 lhs, Int32 rhs);

public static Int32 Add(Int32 lhs, Int32 rhs)
{
    return lhs + rhs;
}

// Note the variable names aren't important, just their TYPE
public static Int32 Subtract(Int32 a, Int32 b)
{
    return a - b;
}

static void Main()
{
    // We can use a delegate to point to a "real" function
    MathDelegate mathPerformer = Add;

    Console.WriteLine(mathPerformer(2, 3)); // Output : 5

    // Now let's point to "Subtract"
    mathPerformer = Subtract;

    Console.WriteLine(mathPerformer(2, 3)); // Output : -1

    Console.ReadLine();
}

This is useful when you don't know what specific algorithm, or processing technique you want to use until the program is already running. A delegate let's us pick which function we want to point to, and then we can execute it while the program is running.

The way this all relates to the lambda discussion above, is that the MatchEvaluator doesn't know how to handle each of the matches that it finds as it peruses through your string. Instead, by supplying it with a lambda/function you are telling it what algorithm you want to use when a match is found. Basically a delegate is useful for determining at runtime how you want to perform some action.

Edit: If you want to expand your lambda expressions to include more than one 'line' of code, you can use a code block as well. Consider:

String result = Regex.Replace(
    text,
    @"\[%RC:(\d+)%\]",
    match => { 
       return dict[int.Parse(match.Groups[1].Value)]
    });

You'll notice two things different here. (1) Our => is now followed by { } which allows us to put in multiple lines of code. As a consequence though, the compiler doesn't know which value is the return value, and thus can't infer what the return type is. Therefore, (2) we insert an explicit return command to indicate which value is the one that should be returned.

With this simple code base, we could do something like:

String result = Regex.Replace(
    text,
    @"\[%RC:(\d+)%\]",
    match => { 
       // This does the same thing, but with more lines of code.
       // Of course, you could get way more fancy with it as well.
       String numericValueAsString = match.Groups[1].Value;
       Int32 numericValue = Int32.Parse(numericValueAsString);
       String dictionaryValue = dict[numericValue];

       // Same as above
       return dictionaryValue;
    });
like image 70
sircodesalot Avatar answered Sep 28 '22 11:09

sircodesalot