views:

283

answers:

3

I often find myself writing quite ugly code when doing string manipulations. The code does what's expected, but I would like to feel satisfied with how it looks as well as it performs. Are there any good books, websites, forums that addresses common string manipulation problems? And how would you code the following example scenarios?

A simple scenario: I have a collection of objects that I want to concatenate the result from a specific method on each object and separate each string with " AND ".

This code would do the trick, but it doesn't give that warm cozy feeling you would get after writing some piece of beautiful code.. ;).

List<MyClass> myColl = ..... // initialize the collection..
StringBuilder sb = new StringBuilder();
bool isFirst = true;
foreach (MyClass obj in myColl) {
    if (!isFirst) {
        sb.Append(" AND ");
    }
    isFirst = false;
    sb.Append(obj.MyStringBuildingMethod());
}

This would maybe be a nicer way if I would use lambda expressions:

List<MyClass> myColl = ..... // initialize the collection..
string result = String.Join(" AND ", myColl.Select(i => i.MyStringBuildingMethod()).ToArray());

And now a more complex scenario: I want to split a string on a delimiter and then split each part into a key/value. The value is enclosed in single quotes, and if the delimiter is inside the value, we should not split on that. The following code does what it should, but it feels like I have used duct tape or something.. ;)

string csvString = "name='Andersson, Per',country='Sweden',city='Stockholm'";
Dictionary<string, string> keyValues = new Dictionary<string, string>();

bool isInsideQuote = false;
int lastPosition = 0;
for (int i = 0; i < csvString.Length; i++) {
    if (csvString[i] == ',' && !isInsideQuote) {
        string[] keyValuePair = csvString.Substring(lastPosition, i - lastPosition).Split("=".ToCharArray(), 2);
        if (keyValuePair.Length == 2) {
            keyValues.Add(keyValuePair[0], keyValuePair[1].TrimStart("'".ToCharArray()).TrimEnd("'".ToCharArray()));
        }
        lastPosition = i + 1;
    }

    if (csvString[i] == '\'') {
        isInsideQuote = !isInsideQuote;
    }
}

string[] lastKeyValuePair = csvString.Substring(lastPosition).Split("=".ToCharArray(), 2);
if (lastKeyValuePair.Length == 2) {
    keyValues.Add(lastKeyValuePair[0], lastKeyValuePair[1].TrimStart("'".ToCharArray()).TrimEnd("'".ToCharArray()));
}
+4  A: 

Your can use the String.Join(string delimiter, string[] words) method. In C# 3.0 syntax, this might look like the following:

var words = new string[] 
                      {
                         "this",
                         "that",
                         "the other"
                      };

var concatenatedResult = String.Join(" and ", words);

//should output "this and that and the other"
Console.WriteLine(concatenatedResult);

If you have a generic list and want to concatenate its values, you can do the following:

var words = new List<object>()
                    {
                      5,
                      DateTime.Now,
                      "something else"
                    };

var concatenatedResult = String.Join(" and ", words.Select(i => i.ToString()).ToArray());
//should output "5 and [system time] and something else"
Console.WriteLine(concatenatedResult);

Console.ReadLine();

Please note that the second example was ripped from another StackOverflow post: http://stackoverflow.com/questions/480399/convert-listof-object-to-listof-string/480416.

I've looked at the IL for the Join method, and it should be reasonably comparable in speed if not quicker than using the StringBuilder class. I wanted to ensure that instances of the String class were not being concatenated in code, which would have been very slow in comparison to the approach shown in the OP.

David Andres
If the collection was of strings, then I could use string.Join (using ToArray() on the collection), but in my example, the collection is of objects that have a method that returns a string that I want to concatenate.
Per
Turn the collection into an array (with a simple method) instead.
Kathy Van Stone
@Per: See my recent edit, second example.
David Andres
Yes, using lambda expressions as you did, I could rewrite my first scenario like this and that looks quite nice... List<MyClass> myColl = // .... init string result = String.Join(" AND ", myColl.Select(i => i.MyStringBuildingMethod()).ToArray());
Per
Hmm.. adding code in comments was obviously not a good idea.. ;).. Trying to add it in my first post instead.
Per
The only aspect of this usage I would be extra cautious about is the memory overhead of, at the very least, creating an additional array of the original list. In this respect, the StringBuilder loop approach may not be the prettiest, but it won't require as much memory to execute.
David Andres
+2  A: 

I would add an And for every record, and then simply strip the last And off at the end...

List<MyClass> myColl = ..... // initialize the collection..
StringBuilder sb = new StringBuilder();
foreach (MyClass obj in myColl)   
    sb.Append(obj.MyStringBuildingMethod() + " AND ");
string s = sb.ToString(0, sb.Length - 5);
Charles Bretana
Adding the delimiter on each concatenation and then stripping is a method that I commonly use, but I don't feel really happy with it since it feels a bit "hackish" ;).. What I'm looking for is common string manipulations that not only works, but also look beautiful.... ;)
Per
+1 for anti-if implementation :) And BTW Per, no if statements is beautiful... see <a href="http://www.antiifcampaign.com/">http://www.antiifcampaign.com/</a>
csharptest.net
A: 

I suppose this is slightly less convoluted than what you have for your second example, although it relies on the string being pretty strictly formatted. String parsing isn't always pretty.

string csvString = "name='Andersson, Per',country='Sweden',city='Stockholm'";

csvString = csvString.Replace("',", "'|");
string[] parts = csvString.Split('|');
foreach (string s in parts)  {
  string[] moreParts = s.Split('=');

  string key = moreParts[0];
  string value = moreParts[1].Trim("'"); 
  // do stuff?
}
Ron Warholic