22

To start with I'll say that I agree that goto statements are largely made irrelevant by higher level constructs in modern programming languages and shouldn't be used when a suitable substitute is available.

I was re-reading an original edition of Steve McConnell's Code Complete recently and had forgotten about his suggestion for a common coding problem. I had read it years ago when I was first getting started and don't think I realized how useful the recipe would be. The coding problem is the following: when executing a loop you often need to execute part of the loop to initialize state and then execute the loop with some other logic and ending each loop with the same initialization logic. A concrete example is implementing String.Join(delimiter, array) method.

I think everybody's first take on the problem is this. Assume the append method is defined to add the argument to your return value.

bool isFirst = true;
foreach (var element in array)
{
  if (!isFirst)
  {
     append(delimiter);
  }
  else
  {
    isFirst = false;
  }

  append(element);
}

Note: A slight optimization to this is to remove the else and put it at the end of the loop. An assignment usually being a single instruction and equivalent to an else and decreases the number of basic blocks by 1 and increases the basic block size of the main part. The result being that execute a condition in each loop to determine if you should add the delimiter or not.

I've also seen and used other takes on dealing with this common loop problem. You can execute the initial element code first outside the loop, then perform your loop from the second element to the end. You can also change the logic to always append the element then the delimiter and once the loop is completed you can simply remove the last delimiter you added.

The latter solution tends to be the one that I prefer only because it doesn't duplicate any code. If the logic of the initialization sequence ever changes, you don't have to remember to fix it in two places. It does however require extra "work" to do something and then undo it, causing at least extra cpu cycles and in many cases such as our String.Join example requires extra memory as well.

I was excited then to read this construct

var enumerator = array.GetEnumerator();
if (enumerator.MoveNext())
{
  goto start;
  do {
    append(delimiter);

  start:
    append(enumerator.Current);
  } while (enumerator.MoveNext());
}

The benefit here being that you get no duplicated code and you get no additional work. You start your loop half way into the execution of your first loop and that is your initialization. You are limited to simulating other loops with the do while construct but the translation is easy and reading it is not difficult.

So, now the question. I happily went to try adding this to some code I was working on and found it didn't work. Works great in C, C++, Basic but it turns out in C# you can't jump to a label inside a different lexical scope that is not a parent scope. I was very disappointed. So I was left wondering, what is the best way to deal with this very common coding problem (I see it mostly in string generation) in C#?

To perhaps be more specific with requirements:

  • Don't duplicate code
  • Don't do unnecessary work
  • Don't be more than 2 or 3 times slower than other code
  • Be readable

I think readability is the only thing that might arguably suffer with the recipe I stated. However it doesn't work in C# so what's the next best thing?

* Edit * I changed my performance criteria because of some of the discussion. Performance is generally not a limiting factor here, so the goal more correctly should be to not be unreasonable, not to be the fastest ever.

The reason I dislike the alternate implementations I suggest is because they either duplicate code which leaves room for changing one part and not the other or for the one I generally choose it requires "undoing" the operation which requires extra thought and time to undo the thing that you just did. With string manipulation in particular this usually leaves you open for off by one errors or failing to account for an empty array and trying to undo something that didn't happen.

Mat
  • 202,337
  • 40
  • 393
  • 406
Peter Oehlert
  • 16,368
  • 6
  • 44
  • 48
  • But once you know of String.Join, how often do you find yourself actually writing such loops anymore? – Kirk Woll Aug 29 '10 at 20:03
  • 1
    I find all kinds of reasons not to use it. Presently what I'm working on something that does a lot of string manipulation with a tree of objects. I have to pass a stringbuilder around to not have lots of unnecessary allocations as I translate an arbitrary tree graph into a string. Using this overload isn't possible because it doesn't exist for stringbuilder, and even if it did you couldn't use something like sb.Join("delim", Children.Select(child => child.Build(sb)) because the children would be appended to the stringbuilder in the wrong order. – Peter Oehlert Aug 29 '10 at 20:31
  • 2
    @Peter Oehlert: You can write a function that returns IEnumerable, then use yield return to return your results in whatever order you wish. – Merlyn Morgan-Graham Aug 29 '10 at 20:42
  • 1
    @Peter Oehlert: If you've already made yourself comfortable with the idea of using `goto` in this case, ask yourself why you're so adamant about using a `do`/`while` loop at all. We all know that `for`, `while`, `do`, etc. are just convenient wrappers for `goto` anyway, right? Replace your `do`/`while` with the equivalent `goto` and you're left with exactly the construct that McConnell proposed. See [my answer](http://stackoverflow.com/questions/3596348/other-ways-to-deal-with-loop-initialization-in-c/3596929#3596929) for a demonstration of what I mean. – Dan Tao Aug 29 '10 at 22:54
  • Never mind, [Jordao](http://stackoverflow.com/questions/3596348/other-ways-to-deal-with-loop-initialization-in-c/3596760#3596760) already suggested the same thing. – Dan Tao Aug 29 '10 at 23:24
  • I don't see a problem with doing the first element outside the loop: only `append(element)` is duplicated, and so changes to `append()` apply everywhere. I think "append always, then remove final delimiter" is just as bad (or worse), since it may not duplicate any single line of code literally but it does repeat the logic, and in a funny way. "DRY" and "OAOO" usually are intended to apply to program logic, not literal lines of text. – Ken Aug 30 '10 at 16:31
  • Again, the supplied code was an EXAMPLE only. I agree, append is relatively innocuous. The initialization code doesn't have to be that simple though. If what you're doing is more complicated, it becomes more onerous to duplicate and then keep in sync. I would also disagree though that DRY doesn't apply at the line level. – Peter Oehlert Aug 31 '10 at 01:08
  • @Peter: if the initialization code is complex, refactor it into a method. Then call the method in both places. – John Saunders Aug 31 '10 at 02:50

9 Answers9

18

Personally I like Mark Byer's option, but you could always write your own generic method for this:

public static void IterateWithSpecialFirst<T>(this IEnumerable<T> source,
    Action<T> firstAction,
    Action<T> subsequentActions)
{
    using (IEnumerator<T> iterator = source.GetEnumerator())
    {
        if (iterator.MoveNext())
        {
            firstAction(iterator.Current);
        }
        while (iterator.MoveNext())
        {
            subsequentActions(iterator.Current);
        }
    }
}

That's relatively straightforward... giving a special last action is slightly harder:

public static void IterateWithSpecialLast<T>(this IEnumerable<T> source,
    Action<T> allButLastAction,
    Action<T> lastAction)
{
    using (IEnumerator<T> iterator = source.GetEnumerator())
    {
        if (!iterator.MoveNext())
        {
            return;
        }            
        T previous = iterator.Current;
        while (iterator.MoveNext())
        {
            allButLastAction(previous);
            previous = iterator.Current;
        }
        lastAction(previous);
    }
}

EDIT: As your comment was concerned with the performance of this, I'll reiterate my comment in this answer: while this general problem is reasonably common, it's not common for it to be such a performance bottleneck that it's worth micro-optimizing around. Indeed, I can't remember ever coming across a situation where the looping machinery became a bottleneck. I'm sure it happens, but that isn't "common". If I ever run into it, I'll special-case that particular code, and the best solution will depend on exactly what the code needs to do.

In general, however, I value readability and reusability much more than micro-optimization.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 2
    +1 for showing the correct way to use an enumerator directly: `using`. – R. Martinho Fernandes Aug 29 '10 at 20:14
  • 1
    But calling a delegate is rather expensive action (delegate `Action`) so this solution is meant to be legible but it is on the expense of performance. Am I right or is there a trick? – MartyIX Aug 29 '10 at 20:16
  • 1
    @MartyIX: Your question talks about a *common* coding problem, but also specifies performance as a key requirement. Just how often does this crop up in such a way that the cost of executing a delegate is going to be a bottleneck? I can't think of that *ever* happening to me. If I ever ran into it, I'd treat that specific situation as an anomaly, and write code specific to it. I wouldn't try to think of a general purpose solution to such an unusual situation. "General purpose" is often counter to the kind of micro-optimization it looks like you're after. – Jon Skeet Aug 29 '10 at 20:28
  • 1
    Is calling a delegate an expensive action? Since .NET 2 it is on par with calling a method on an interface. – R. Martinho Fernandes Aug 29 '10 at 20:29
  • I was just curious I though that delegates are way slower than direct method calls but it seems they're not: http://stackoverflow.com/questions/304770/does-using-delegates-slow-down-my-net-programs (Also from Jon :)) – MartyIX Aug 29 '10 at 20:38
  • @Jon: To justify my question note that in OP question there is the performance clearly the requirement. – MartyIX Aug 29 '10 at 20:43
  • @MartyIX: Oops, for some reason I thought you were the OP. It was a reasonable comment given the question... but I don't think the question's entirely reasonable to start with :) – Jon Skeet Aug 29 '10 at 20:43
12

For your specific example there is a standard solution: string.Join. This handles adding the delimiter correctly so that you don't have to write the loop yourself.

If you really want to write this yourself an approach you can use is the following:

string delimiter = "";
foreach (var element in array)
{
    append(delimiter);
    append(element);
    delimiter = ",";
}

This should be reasonably efficient and I think it is reasonable to read. The constant string "," is interned so this won't result in a new string being created on each iteration. Of course if performance is critical for your application you should benchmark rather than guess.

Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
  • to use `String.Join()` you first need to have an array of strings, if you don't have that given then you are looking at building `List` then converting it to to `string[]` which is more ugliness than he started with. – Ilia G Aug 29 '10 at 20:11
  • Like this. Always annoyed by "first"... How sure can you be that the compiler will optimize away the repeated assignments to delimiter? – Nicolas78 Aug 29 '10 at 20:13
  • 2
    @liho1eye: With .NET 4.0 it is no longer true that you need an array of strings: http://msdn.microsoft.com/en-us/library/dd992421.aspx – Mark Byers Aug 29 '10 at 20:13
  • 1
    +1 This is a great solution for strings. However, there are other examples where using the loop I described would be helpful than just strings. This requires that whatever type you're dealing with in your loop have a signal value like the empty string that will effectively perform the no-op that you're getting for your initial loop. I don't know that this always is true but when it is it's a great answer. – Peter Oehlert Aug 29 '10 at 20:44
7

You are already willing to give up on foreach. So this ought to be suitable:

        using (var enumerator = array.GetEnumerator()) {
            if (enumerator.MoveNext()) {
                for (;;) {
                    append(enumerator.Current);
                    if (!enumerator.MoveNext()) break;
                    append(delimiter);
                }
            }
        }
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • I remember seeing this construct in a language somewhere: a loop with the check, not at the beginning, not at the end, but in the middle. Can't remember what language it was though... – R. Martinho Fernandes Aug 29 '10 at 20:27
  • 1
    +1 This is called a loop-with-exit loop in Code Complete and is my preferred solution for this kind of problem. – Jordão Aug 29 '10 at 21:29
  • 1
    @Martinho: it is possible to have a loop condition (as a distinct construct, rather than `if`/`break`) in the middle of the loop in Sather: `loop do_something; while!(condition); do_something_else; end;`. This is because loop conditions in Sather are not special language constructs, but are a limited form of coroutines: http://www.icsi.berkeley.edu/~sather/Documentation/LanguageDescription/webmaker/DescriptionX2Eiterators-chapte-1.html – Pavel Minaev Aug 30 '10 at 19:42
6

You can certainly create a goto solution in C# (note: I didn't add null checks):

string Join(string[] array, string delimiter) {
  var sb = new StringBuilder();
  var enumerator = array.GetEnumerator();
  if (enumerator.MoveNext()) {
    goto start;
    loop:
      sb.Append(delimiter);
      start: sb.Append(enumerator.Current);
      if (enumerator.MoveNext()) goto loop;
  }
  return sb.ToString();
}

For your specific example, this looks pretty straighforward to me (and it's one of the solutions you described):

string Join(string[] array, string delimiter) {
  var sb = new StringBuilder();
  foreach (string element in array) {
    sb.Append(element);
    sb.Append(delimiter);
  }
  if (sb.Length >= delimiter.Length) sb.Length -= delimiter.Length;
  return sb.ToString();
}

If you want to get functional, you can try using this folding approach:

string Join(string[] array, string delimiter) {
  return array.Aggregate((left, right) => left + delimiter + right);
}

Although it reads really nice, it's not using a StringBuilder, so you might want to abuse Aggregate a little to use it:

string Join(string[] array, string delimiter) {
  var sb = new StringBuilder();
  array.Aggregate((left, right) => {
    sb.Append(left).Append(delimiter).Append(right);
    return "";
  });
  return sb.ToString();
}

Or you can use this (borrowing the idea from other answers here):

string Join(string[] array, string delimiter) {
  return array.
    Skip(1).
    Aggregate(new StringBuilder(array.FirstOrDefault()),
      (acc, s) => acc.Append(delimiter).Append(s)).
    ToString();
}
Jordão
  • 55,340
  • 13
  • 112
  • 144
  • 3
    Replacing a loop with a goto is terribly ugly and should never be used without a *really* good reason, but +1 for thinking outside the box and finding a way to implement the construct in C#. – Heinzi Aug 29 '10 at 22:27
4

Sometimes I use LINQ .First() and .Skip(1) to handle this ... This can give a relatively clean (and very readable) solution.

Using you example,

append(array.First());
foreach(var x in array.Skip(1))
{
  append(delimiter);
  append (x);
}

[This assumes there is at least one element in the array, an easy test to add if that's to be avoided.]

Use F# would be another suggestion :-)

Ian Mercer
  • 38,490
  • 8
  • 97
  • 133
  • I've used a foreach to "test" for the first, and used "Take(1)" instead of first. a little funky, but gets the job done. – Merlyn Morgan-Graham Aug 29 '10 at 20:45
  • I confess I haven't quite gotten around to learning F# (it's on the list); how would this be done in F#? – Peter Oehlert Aug 29 '10 at 20:56
  • F# has pattern matching. The `Cons Pattern` allows you to decompose a list into `head :: tail`. See http://msdn.microsoft.com/en-us/library/dd547125.aspx – Ian Mercer Aug 29 '10 at 21:10
2

There are ways you "can" get around the doubled code, but in most cases the duplicated code is much less ugly/dangerous than the possible solutions. The "goto" solution you quote doesn't seem like an improvement to me - I don't really think you really gain anything significant (compactness, readability or efficiency) by using it, while you increase the risk of a programmer getting something wrong at some point in the code's lifetime.

In general I tend to go for the approach:

  • A special case for the first (or last) action
  • loop for the other actions.

This removes the inefficiencies introduced by checking if the loop is in the first iteration on every time around, and is really easy to understand. For non-trivial cases, using a delegate or helper method to apply the action can minimise code duplication.

Or another approach I use sometimes where efficiency isn't important:

  • loop, and test if the string is empty to determine if a delimiter is required.

This can be written to be more compact and readable than the goto approach, and doesn't require any extra variables/storage/tests to detect the "special case" iteraiton.

But I think Mark Byers' approach is a good clean solution for your particular example.

Jason Williams
  • 56,972
  • 11
  • 108
  • 137
0

I prefer first variable method. It is probably not cleanest but most efficient way. Alternatively you could use Length of the thing you appending to and compare it to zero. Works well with StringBuilder.

Ilia G
  • 10,043
  • 2
  • 40
  • 59
0

Why don't move dealing with first element outside a loop ?

StringBuilder sb = new StrindBuilder()
sb.append(array.first)
foreach (var elem in array.skip(1)) {
  sb.append(",")
  sb.append(elem)
}
Bart
  • 2,167
  • 2
  • 15
  • 21
  • 2
    Actually, Peter briefly mentions this solution in his question already. – R. Martinho Fernandes Aug 29 '10 at 20:16
  • If `array` is actually a DB query, won't this method execute the query *twice*? – Gabe Aug 29 '10 at 20:18
  • @Gabe: yes, as written, it would. In that case, you should probably save the query result beforehand with a call to `ToList()`. – R. Martinho Fernandes Aug 29 '10 at 20:25
  • if array is DB query it shouldn't be hard to execute it only once. Storing in temporary variable or something. But I may be wrong. – Bart Aug 29 '10 at 20:25
  • 2
    The reason I strongly dislike this answer is because of the duplication of code. If your line sb.append(...) were to change in any way, you have to remember to change it twice. In your example above it may not be difficult but with more complex real world code my experience is that it happens frequently. I've fixed a good share of colleagues bugs around this exact problem. – Peter Oehlert Aug 29 '10 at 20:35
  • Ok, so I was confused by performance thing. Because I still think that this is the fastest way (modulo buffering), even overriding delimiter by "," in every loop is wasteing some MSIL instructions. And I agree that it could make some troubles. However you removed your performance demand.. – Bart Aug 30 '10 at 07:02
0

If you want to go the functional route, you could define String.Join like LINQ construct that is reusable across types.

Personally, I would almost always go for code clarity over saving a few opcode executions.

EG:

namespace Play
{
    public static class LinqExtensions {
        public static U JoinElements<T, U>(this IEnumerable<T> list, Func<T, U> initializer, Func<U, T, U> joiner)
        {
            U joined = default(U);
            bool first = true;
            foreach (var item in list)
            {
                if (first)
                {
                    joined = initializer(item);
                    first = false;
                }
                else
                {
                    joined = joiner(joined, item);
                }
            }
            return joined;
        }
    }

    class Program
    {

        static void Main(string[] args)
        {
            List<int> nums = new List<int>() { 1, 2, 3 };
            var sum = nums.JoinElements(a => a, (a, b) => a + b);
            Console.WriteLine(sum); // outputs 6

            List<string> words = new List<string>() { "a", "b", "c" };
            var buffer = words.JoinElements(
                a => new StringBuilder(a), 
                (a, b) => a.Append(",").Append(b)
                );

            Console.WriteLine(buffer); // outputs "a,b,c"

            Console.ReadKey();
        }

    }
}
Sam Saffron
  • 128,308
  • 78
  • 326
  • 506