Posts Tagged Aggregate

C#: Handling Title Case in Strings with Articles and Prepositions

iStock_000002240961XSmall

This was an issue I answered in the forums which a user presented and felt that the response was intricate enough to share with the world as a whole. The user wanted to have a string converted to title case but also wanted to have the first letter of any article or preposition to not be be upper case along with the rest of the sentence. This article discusses how to do that in C#.

For example the user was interested in changing

“ALL QUIET ON THE WESTERN FRONT”

   to

“All Quiet on the Western Front.”

.Net Framework Almost Does It

Thanks to the TextInfo class and a helping hint from a current CultureInfo object we can use the method ToTitleCase to work with our current language. The problem is that when ToTitleCase is called with the original sentence we get this:

“All Quiet On The Western Front”

Give it some Help

The .Net code is not robust enough to ignore the articles and prepositions so we will augment it. The following code using Linq-to-Object and Regex and processes majority of the target articles and prepositions . I have placed it into an extension method below:

/*
using System.Globalization;
using System.Threading;
using System.Text.RegularExpressions;
*/

/// <summary>
/// An Extension Method to allow us t odo "The Title Of It".asTitleCase()
/// which would return a TitleCased string.
/// </summary>
/// <param name="title">Title to work with.</param>
/// <returns>Output title as TitleCase</returns>
public static string asTitleCase ( this string title)
{
    string WorkingTitle = title;

    if ( string.IsNullOrEmpty( WorkingTitle ) == false )
    {
        char[] space = new char[] { ' ' };

        List<string> artsAndPreps = new List<string>()
            { "a", "an", "and", "any", "at", "from", "into", "of", "on", "or", "some", "the", "to", };

        //Get the culture property of the thread.
        CultureInfo cultureInfo = Thread.CurrentThread.CurrentCulture;
        //Create TextInfo object.
        TextInfo textInfo = cultureInfo.TextInfo;

        //Convert to title case.
        WorkingTitle = textInfo.ToTitleCase( title.ToLower() );

        List<string> tokens = WorkingTitle.Split( space, StringSplitOptions.RemoveEmptyEntries ).ToList();

        WorkingTitle = tokens[0];

        tokens.RemoveAt(0);

        WorkingTitle += tokens.Aggregate<String, String>( String.Empty, ( String prev, String input )
                                => prev +
                                    ( artsAndPreps.Contains( input.ToLower() ) // If True
                                        ? " " + input.ToLower()              // Return the prep/art lowercase
                                        : " " + input ) );                   // Otherwise return the valid word.

        // Handle an "Out Of" but not in the start of the sentance
        WorkingTitle = Regex.Replace( WorkingTitle, @"(?!^Out)(Out\s+Of)", "out of" );
    }

    return WorkingTitle;

}
Explanation
  • Line 21: Here is our English list of words not to capitalize. We would have to change this for other languages.
  • Line 25: We get the current culture from the running thread so that ToTitleCase can do its job.
  • Line 30: ToTitleCase does the first run and upper cases all the first letters and drops any following upper case letters if they exist.
  • Line 32: We split the line on space between the words into word tokens and put them in a list.
  • Line 34: We save off the first word because regardless of what it is, it is correct.
  • Line 36: We remove the first word so not to process it.
  • Line 40: Using the Aggregate extension to accumulate each word token we will add a space. We are using the aggregate method in-lieu of string.Join to add spaces to our words (the accumulation), but also to check each word as it goes by which string.Join can’t help us with.
  • Line 42: As the tokens (words) are handed to us, check to see if they are in the list we setup in line 21. If it exists, add a space in front and make the whole word lower case (Line 43) other wise ad a space and just return the word.
  • Line 46: Handle any two word Out Of issues, but ignore if it is the first word as found in “Out of Africa”.
Tests and Results

 

Console.WriteLine( "ALL QUIET ON THE WESTERN FRONT".asTitleCase() );
Console.WriteLine( "Bonfire OF THE Vanities".asTitleCase() );
Console.WriteLine( "The Out-of-Sync Child: Recognizing and Coping with Sensory Processing Disorder".asTitleCase() );
Console.WriteLine( "Out OF AFRICA".asTitleCase() );

/* Results
All Quiet on the Western Front
Bonfire of the Vanities
The Out-Of-Sync Child: Recognizing and Coping With Sensory Processing Disorder
Out of Africa
*/
Share

Tags: , , ,

C#: IEnumerable, Extension Methods, Lambdas to combine a List of Integers into a String

(Updated 3/26/2011 works on .Net 3-4)

In C# 3.0 (.Net 3.5/4.0) one can use the extension methods which work on IEnumerables to help with processing or converting your generic lists. This quick article shows how one can quickly join a list of integers and print them out as one sentance of “1 2 3 4 5”. Obviously there are other ways of doing this, but this gives one an overview.

Select Extension to Project Our List To Strings

Since the generic List<> inherits from IEnumerable we can use its extension methodd to do our dirty work. Look at the code here:

List<int> items = new List<int>() { 1, 2, 3, 4, 5 };    

Console.WriteLine    
    (    
    string.Join    
        ( " ",    
         items.Select(    
         item => item.ToString()    
        ).ToArray()   
    )   
);
  • Line 01: Here is our list of integers which we will want to eventually convert to a string “1 2 3 4 5”.
  • Line 05: The old static string.Join will do our dirty work by joining each of the items with a space for better reading. We just have to get it an oldstyle string array to it though.
  • Line 07: The extension Select method, will project (enumerate) over each item within our integer array and we will specify a new projection of strings using a lambda.
  • Line 08: Here is the lamda function which basically tells the compiler that for each now named item in items; simply convert it to its string counterpart.
  • Line 09: *Sigh* as mentioned above, string.Join needs the old style array, do that conversion here with the help of antoher extension!

Without String.Join Using Another Extension Method Aggregate

To remove the need of string.Join we will use the extension method Aggregate. Aggregate allows us to accumulate or aggregate values. Let me explain in code to achieve the same results as above:

List<int> items = new List<int>() { 1, 2, 3, 4, 5 };    

Console.WriteLine    
(    
   items.Select( item => item.ToString() )    
        .Aggregate<string, string>    
           (    
             string.Empty,    
            ( string prev, string current )   
            => prev +   
                     (prev == String.Empty ? current : " " + current)   
           )   
);
  • Line 05: We again use the Select extension/projection to convert our integers into string numbers.
  • Line 06: The aggregate method applies/projects an accumulator Lambda functon of our specification over the list of strings. Think of the Aggregate method as one which is stateful, for we will receive the results of the previous operation and be able to join it to the current. Line 04 simply tells the compiler that are working with two strings on input and output. One string is the previous aggregation operation and the other is the current item in the list being enumerated over during its processing.
  • Line 08: Since our aggregate takes two strings, we need to seed the process. The first call will use this string.Empty which will act as the first previous value to be married to the first current item in the projection/enumeration.
  • Line 09: This begins our declaration of the lamda function to use, it expects two strings, a previous accumulated string value and the current string item in the projection.
  • Line 10: What ever came before, we are going to marry it to the current. Here is why we need to seed string.Empty into the very first call.
  • Line 11: The previous will be married to the current, but we need to anticipate the very first time it is run where the string will be empty. For that situation, just return the current value. Otherwise return the current value with a space such as ” 2″. That will be then be concatenated (+) to prev in line 8. This is where we simulate the string.Join.

There you go a few ways of using the extension methods of Select and Aggregate and toArray with their specific individual lamda functions in C#.

Share

Tags: , , , , , ,