Reputation: 101
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.
Upvotes: 5
Views: 2473
Reputation: 11
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...
Upvotes: 0
Reputation: 39874
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.
Upvotes: 0
Reputation: 117360
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();
}
Upvotes: 5
Reputation: 29174
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));
Upvotes: 5
Reputation: 66637
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.
Upvotes: 0
Reputation: 755587
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();
}
Upvotes: 0
Reputation: 19791
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? ;)
Upvotes: 0
Reputation: 1115
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();
}
Upvotes: 3
Reputation: 40265
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")
});
Upvotes: 2
Reputation: 17162
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.
Upvotes: 1
Reputation: 1064244
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;
Upvotes: 3