tags:

views:

1239

answers:

12

I am in the process of learning more about LINQ and Lambda expressions but at this stage, I simply don't "Get" Lambda expressions.

Yes ... I am a newbie to these new concepts.

I mean, every example I see illustrates how to add or subtract to parameters.

What about something a little more complex?

To help me gain a better understanding I have posted a small challenge for anyone who wishes to participate. I have the following method which will take any string and will put spaces in between any upper case characters and their preceding neighbour (as shown below).

i.e.
"SampleText" = "Sample Text"
"DoesNotMatterHowManyWords" = "Does Not Matter How Many Words"

Here is the code;

public static string ProperSpace(string text)
{
    var sb = new StringBuilder();
    var lowered = text.ToLower();

    for (var i = 0; i < text.Length; i++)
    {
        var a = text.Substring(i, 1);
        var b = lowered.Substring(i, 1);
        if (a != b) sb.Append(" ");
        sb.Append(a);
    }

    return sb.ToString().Trim();
}

I am sure that the method above can be re-written to use with LINQ or a Lambda expression. I am hoping that this exercise will help open my eyes to these new concepts.

Also, if you have any good links to LINQ or Lambda tutorials, please provide.


EDIT

Thanks to everyone who has contributed. Although the current method does do the job, I am happy to see it can be modified to utilize a lambda expression. I also acknowledge that this is perhaps not the best example for LINQ.

Here is the newly updated method using a Lambda expression (tested to work);

public static string ProperSpace(string text)
{
    return text.Aggregate(new StringBuilder(), (sb, c) =>
    {
        if (Char.IsUpper(c)) sb.Append(" ");
        sb.Append(c);
        return sb;
    }).ToString().Trim();
}

I also appreciate the many links to other (similar) topics.

In particular this topic which is so true.

+4  A: 

Personally, I think your method is simple and clear, and I would stick with it (I think I might have even written the exact same code somewhere along the lines).

UPDATE:

How about this as a starting point?

public IEnumerable<char> MakeNice(IEnumerable<char> str)
{
  foreach (var chr in str)
  {
    if (char.ToUpper(chr) == chr)
    {
      yield return ' ';
    }
    yield return chr;
  }
}

public string MakeNiceString(string str)
{
  return new string(MakeNice(str)).Trim();
}
leppie
thanks leppie ... I was just hoping to be able to use this method to see how either LINQ or Lambda could improve it. This exercise will help me grasp these new concepts ... stay tuned to see what people some up with.
Fleming
I updated it, perhaps that is a bit cleaner :)
leppie
No lambdas here though?
Benjol
That code will add a leading space character, and doesn't return a string.Any chance of updating it?
Richard Ev
Updated for string (thought it was obvious ;p). @Benjol, mine is even better, no side effects! :)
leppie
Sorry, but I cannot see why this (or the original) solution would be cearer/simpler or even more performant than the LINQ-One-Liners below...
MartinStettner
There are no LINQ one liners below :) hehe.It is more understandable due to one fact. No side effects :)
leppie
+2  A: 

I would use RegularExpressions for this case.

public static string ProperSpace(string text)
{
  var expression = new Regex("[A-Z]");
  return expression.Replace(text, " $0");
}

If you want to use a lambda you could use:

public static string ManipulateString(string text, Func<string, string> manipulator)
{
    return manipulator(text);
}
// then
var expression = new Regex("[A-Z]");
ManipulateString("DoesNotMatterHowManyWords", s => expression.Replace(text, " $0"));

Which is essentially the same as using an anonyous delegate of

var expression = new Regex("[A-Z]");
ManipulateString("DoesNotMatterHowManyWords", delegate(s) {
  return expression.Replace(text, " $0")
});
bendewey
Not sure about your regexp there, ([a-z]+)([A-Z]+) -> $1 $2 works for me.
Benjol
I switched it to $0, it should work now.
bendewey
+2  A: 

Like leppie, I'm not sure this is a good candidate for LINQ. You could force it, of course, but that wouldn't be a useful example. A minor tweak would be to compare text[i] against lowered[i] to avoid some unnecessary strings - and maybe default the sb to new StringBuilder(text.Length) (or a small amount higher):

if (text[i] != lowered[i]) sb.Append(' ');
sb.Append(a);

Other than that - I'd leave it alone;

Marc Gravell
+1  A: 

Here is a way of doing it:

string.Join("", text.Select((c, i) => (i > 0 && char.IsUpper(c)) ? " " + c : c.ToString()).ToArray());

But I don't see where the improvement is. Just check this very recent question...

EDIT : For those who are wondering: yes, I intentionnaly picked an ugly solution.

ybo
A: 

I'm curious why a simple regular expression replace wouldn't suffice. I wrote one for someone else that does exactly this:

"[AI](?![A-Z]{2,})[a-z]*|[A-Z][a-z]+|[A-Z]{2,}(?=[A-Z]|$)"

I already posted this on another bulleting board here: http://bytes.com/topic/c-sharp/answers/864056-string-manupulation-net-c. There's one bug that requires a post regex trim that I haven't had the opportunity to address yet, but maybe someone else can post a fix for that.

Using the replace pattern: "$0[space]" where you replace [space] with an actual space would cut the code down immensely.

It handles some special cases which might be outside the scope of what you're trying to do but the bulletin board thread will give you the info on those.

Edit: P.S. A great way to start learning some of the applications of LINQ is to check out the GOLF and CODE-GOLF tags and look for the C# posts. There's a bunch of different and more complex uses of LINQ-to-Objects which should help you to recognise some of the more useful(?) and amusing applications of this technology.

BenAlabaster
My problem with regex is that they're incredibly cryptic. Once you know regex it's fine, but then the next guy who's maintaining your code doesn't know regex and you end up with issues. Code is less efficient but more readable.
Cameron MacFarland
That *can* be true, and I agree, to an extent. There are many great resources for learning regex and the shortcomings of future developers shouldn't be a reason to stifle your creativity and productivity by avoiding certain technology - like Regex, LINQ, Lambda Expressions etc...
BenAlabaster
That's like saying you should avoid utilizing the finer points of astro-physics because the guy who may replace you isn't going to understand it... if everyone followed this path as a general rule, society would still be feudal and we'd all still be fighting for riches and power.
BenAlabaster
Hmmm, good point. I still find Regex cryptic though :)
Cameron MacFarland
Oh, I'm not arguing *that* point... I've been writing regex for a while and I *still* find it a little cryptic at times, especially when you start getting into stacked look-arounds for things like password complexity etc.
BenAlabaster
I think RegExs are ok for this kind of thing, it's when you start trying to do proper validation of emails and urls that things go bad.
Benjol
I'm not sure, if regexes are more performant than a simple loop (or a well-crafted linq expression). And as I've mentioned before they do not take into account the countless upper-case letters that exist outside the english world (ÄÖÜÉÈÝ to mention only a few ...)
MartinStettner
A: 

For usefullness of linq (if you need convincing), you could check out this question.

I think one first step is to get used to the dot syntax, and only then move on to the 'sql' syntax. Otherwise it just hurts your eyes to start with. I do wonder whether Microsoft didn't slow uptake of linq by pushing the sql syntax, which made a lot of people think 'yuck, DB code in my C#'.

As for lambdas, try doing some code with anonymous delegates first, because if you haven't done that, you won't really understand what the fuss is all about.

Benjol
+1  A: 
public static string ProperSpace(string text)
{
    return text.Aggregate("", (str, c) => 
                                    str += Char.IsUpper(c) ? " " + c : c.ToString()).Trim();
}
Hasan Khan
+3  A: 
public static string ProperSpace(string text)
{
    return text.Aggregate(new StringBuilder(), (sb, c) =>
        {
            if (Char.IsUpper(c) && sb.Length > 0)
                sb.Append(" ");

            sb.Append(c);
            return sb;
        }).ToString();
}
Lee
A: 

I've got a Regex solution that's only 8 times slower than your current loop[1], and also harder to read than your solution[2].

return Regex.Replace(text, @"(\P{Lu})(\p{Lu})", "$1 $2");

It matches unicode character groups, in this case non-uppercase followed by an uppercase, and then adds a space between them. This solution works better than other regex-based solutions that only look for [A-Z].

[1] With reservations that my quickly made up test may suck.
[2] Anyone actually know the unicode character groups without googling? ;)

Simon Svensson
Okay, I've been doing regex a while and I've never come across unicode char groups and had no idea regex was capable of that. I'm going to have to figure that out. Of course, if it's 8 times slower than the loop it would have to be a purely academic exercise...
BenAlabaster
A: 

You can use existing LINQ functions to make this work but it's probably not the best approach. The following LINQ expression would work but is inneficient because it generates a lot of extra strings

public static string ProperCase(string text)
{
    return text.Aggregate(
        string.Empty,
        (acc, c) => Char.ToLower(c) != c ? acc + " " + c.ToString() : acc + c.ToString())
        .Trim();
}
JaredPar
+3  A: 

This is doing the same as the original code and even avoids the generation of the second (lower case) string.

var result = text.Aggregate(new StringBuilder(), 
    (sb, c) => (Char.IsUpper(c) ? sb.Append(' ') : sb).Append(c));
MartinStettner
A: 

Have you ever thought of using the Aggregate function ...

For instance, let’s say I have an array called routes and I want to set all the Active fields to false. This can be done as follow:

routes.Aggregate(false, (value, route) => route.Active = false); - Routes is the name of the table. - The first false is simply the seed value and needs to be the same type as the value that is being set. It’s kind of… redundant. - value is also redundant and is basically the first value. - route is the aggregate value (each individual element from the sequence)

No more redundant foreach loops… I don't know Lambda expression all that well either... but i'm sure there is q genius out there somewhere that can abuse this to do that...

Pieter