views:

283

answers:

7

This problem has bugged me for years, and I always feel like I'm coming up with a hack when there's a much better solution. The issue at hand occurs when you want to do something to all items in a list and then add something inbetween those items. In short, I want to:

  • Do something to every item in the list.
  • Do something else to all but the last item in the list (in effect, do something "inbetween" the items in the list).

For example, let's say I have a class called Equation:

public class Equation
{
    public string LeftSide { get; set; }
    public string Operator { get; set; }
    public string RightSide { get; set; }
}

I want to iterate over a list of Equations and return a string that formats these items together; something like the following:

public string FormatEquationList(List<Equation> listEquations)
{
    string output = string.Empty;
    foreach (Equation e in listEquations)
    {
        //format the Equation
        string equation = "(" + e.LeftSide + e.Operator + e.RightSide + ")";

        //format the "inbetween" part
        string inbetween = " and ";

        //concatenate the Equation and "inbetween" part to the output
        output += equation + inbetween;
    }
    return ouput;
}

The problem with the above code is that it is going to include and at the end of the returned string. I know that I could hack some code together, replace the foreach with a for loop, and add the inbetween element only if it's not the last item; but this seems like a hack.

Is there a standard methodology for how to deal with this type of problem?

+6  A: 

You can use String.Join().

String.Join(" and ",listEquations.Select(e=>String.Format("({0}{1}{2})",e.LeftSide,e.Operator,e.RightSide).ToArray());
Gregoire
Nice solution, tested your code, need a bit modification :-)string.Join(" and ",lists.Select(e=>String.Format("({0}{1}{2})",e.LeftSide,e.Operator,e.RightSide)).ToArray());
J.W.
@J.W. thanks I will do the modification
Gregoire
+2  A: 

Using a for loop with counter is perfectly reasonable if you don't want a foreach loop. This is why there is more than one type of looping statement.

If you want to process items pairwise, loop at LINQ's Aggregate operator.

Richard
"for real" ;) +1
cmsjr
+3  A: 

You can do this with LINQ's Aggregate operator:

public string FormatEquationList(List<Equation> listEquations)
{
    return listEquations.Aggregate((a, b) => 
        "(" + a.LeftSide + a.Operator + a.RightSide + ") and (" + 
              b.LeftSide + b.Operator + b.RightSide + ")");
}
Tim S. Van Haren
+1  A: 

I generally try to prefix separators based on a condition rather than add them to the end.

string output = string.Empty;
for (int i = 0; i < 10; i++)
{
   output += output == string.Empty ? i.ToString() : " and " + i.ToString();
}

0 and 1 and 2 and 3 and 4 and 5 and 6 and 7 and 8 and 9

cmsjr
+1  A: 

I like the String.Join method already posted.

But when you're not using an Array this has normally been my solution to this problem:

public string FormatEquationList(List<Equation> listEquations)
{
    string output = string.Empty;
    foreach (Equation e in listEquations)
    {
        // only append " and " when there's something to append to
        if (output != string.Empty)
            output += " and ";

        output += "(" + e.LeftSide + e.Operator + e.RightSide + ")";
    }
    return output;
}

Of course, it's usually faster to use a StringBuilder:

public string FormatEquationList(List<Equation> listEquations)
{
    StringBuilder output = new StringBuilder();
    foreach (Equation e in listEquations)
    {
        // only append " and " when there's something to append to
        if (output.Length > 0)
            output.Append(" and ");

        output.Append("(");
        output.Append(e.LeftSide);
        output.Append(e.Operator);
        output.Append(e.RightSide);
        output.Append(")");
    }

    return output.ToString();
}
Steve Wortham
+3  A: 

I usualy add it before the condition, and check if its the 1st item.

public string FormatEquationList(List<Equation> listEquations) 
{ 
    string output = string.Empty;
    foreach (Equation e in listEquations) 
    {
        //use conditional to insert your "between" data:
        output += (output == String.Empty) ? string.Empty : " and "; 

        //format the Equation 
        output += "(" + e.LeftSide + e.Operator + e.RightSide + ")"; 

    } 
    return ouput; 
}

I have to say I would look at the string.Join() function as well, +1 for Linqiness on that. My example is a more of a traditional solution.

Tj Kellie
+8  A: 

You basically have a few different strategies for dealing with this kind problem:

  1. Process the first (or last) item outside of the loop.
  2. Perform the work and then "undo" the extraneous step.
  3. Detect that your're processing the first or last item inside the loop.
  4. Use a higher-level abstraction that allows you to avoid the situation.

Any of these options can be a legitimate way to implement a "between the items" style of algorithm. Which one you choose depends on things like:

  • which style you like
  • how expensive "undoing work" is
  • how expensive each "join" step is
  • whether there are any side effects

Amongst other things. For the specific case of string, I personally prefer using string.Join(), as I find it illustrates the intent most clearly. Also, in the case of strings, if you aren't using string.Join(), you should try to use StringBuilder to avoid creating too many temporary strings (a consequence of strings being immutable in .Net).

Using string concatentation as the example, the different options break down into examples as follows. (For simplicity, assume Equation has ToString() as: "(" + LeftSide + Operator + RightSide + ")"

public string FormatEquation( IEnumerable<Equation> listEquations )
{
    StringBuilder sb = new StringBuilder();

    if( listEquations.Count > 0 )
        sb.Append( listEquations[0].ToString() );
    for( int i = 1; i < listEquations.Count; i++ )
        sb.Append( " and " + listEquations[i].ToString() );
    return sb.ToString();
}

The second option looks like:

public string FormatEquation( IEnumerable<Equation> listEquations )
{
    StringBuilder sb = new StringBuilder();
    const string separator = " and ";
    foreach( var eq in listEquations )
        sb.Append( eq.ToString() + separator );
    if( listEquations.Count > 1 )
        sb.Remove( sb.Length, separator.Length );
}

The third would look something like:

public string FormatEquation( IEnumerable<Equation> listEquations )
{
    StringBuilder sb = new StringBuilder();
    const string separator = " and ";
    foreach( var eq in listEquations )
    {
        sb.Append( eq.ToString() );
        if( index == list.Equations.Count-1 )
            break;
        sb.Append( separator );
    }
}

The last option can take multiple forms in .NET, using either String.Join or Linq:

public string FormatEquation( IEnumerable<Equation> listEquations )
{
    return string.Join( " and ", listEquations.Select( eq => eq.ToString() ).ToArray() );
}

or:

public string FormatEquation( IEnumerable<Equation> listEquations ) 
{
    return listEquations.Aggregate((a, b) => a.ToString() + " and " + b.ToString() );
}

Personally, I avoid using Aggregate() for string concatenation because it results in many intermediate, discarded strings. It's also not the most obvious way to "join" a bunch of results together - it's primarily geared for computing a "scalar" results from a collection in some arbitrary, caller-defined fashion.

LBushkin