147

Is there a way to determine if the loop is iterating for the last time. My code looks something like this:

int[] array = {1, 2, 3...};
StringBuilder builder = new StringBuilder();

for(int i : array)
{
    builder.append("" + i);
    if(!lastiteration)
        builder.append(",");
}

Now the thing is I don't want to append the comma in the last iteration. Now is there a way to determine if it is the last iteration or am I stuck with the for loop or using an external counter to keep track.

John Strood
  • 1,859
  • 3
  • 26
  • 39
Mercurious
  • 3,228
  • 6
  • 24
  • 20
  • 3
    Yeah! It is funny, I just wanted to ask the exact same question! – PhiLho Nov 12 '08 at 22:10
  • The same kind of question returns (and will so). Now why would you want to create a loop if one element needs a different treatment? http://stackoverflow.com/questions/156650/does-the-last-element-in-a-loop-deserve-a-separate-treatment – xtofl Nov 14 '08 at 13:14
  • Since you have a fixed array why use the enhanced for? for(int i = 0; i< array.length; i++ if(i < array.lenth),,, – AnthonyJClink Jun 15 '17 at 02:45

22 Answers22

225

Another alternative is to append the comma before you append i, just not on the first iteration. (Please don't use "" + i, by the way - you don't really want concatenation here, and StringBuilder has a perfectly good append(int) overload.)

int[] array = {1, 2, 3...};
StringBuilder builder = new StringBuilder();

for (int i : array) {
    if (builder.length() != 0) {
        builder.append(",");
    }
    builder.append(i);
}

The nice thing about this is that it will work with any Iterable - you can't always index things. (The "add the comma and then remove it at the end" is a nice suggestion when you're really using StringBuilder - but it doesn't work for things like writing to streams. It's possibly the best approach for this exact problem though.)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • I like this solution. It might have a little performance cost, but in most cases it is near zero. – PhiLho Nov 12 '08 at 22:13
  • Just don't add anything to the 'builder' before you append in the loop. While the code is correct, it naively assumes 'builder' is always created just for outputting the array, which as a SO snippet is okay, but in real life is hardly ever the case. – Robert Paulson Nov 12 '08 at 23:08
  • Actually it's almost always been the case in my experience of having used this pattern quite a few times. It's also incredibly obvious when it's not the case. Fortunately the slightly wordier pattern of keeping a boolean "firstIteration" variable is easy to migrate to where necessary. – Jon Skeet Nov 12 '08 at 23:35
  • Well, we all know that we shouldn't copy/paste code snippets, but I'm sure we're all guilty of it. I only bring it up because if I'm writing an array out, I'm usually writing other stuff too, and the StringBuilder is passed as a parameter to the code that writes to it. :) – Robert Paulson Nov 13 '08 at 00:12
  • Neat! For a more general solution, see my `Class Separator` answer. – akuhn Nov 14 '08 at 17:35
  • 2
    Good pattern, but builder.length() != 0 is brittle -- imagine something gets added (conditionally!) to the buffer before your loop. Instead, use an `isFirst` boolean flag. Bonus: faster too. – Jason Cohen Dec 12 '08 at 15:28
  • 2
    @Jason: I certainly use the "isFirst" pattern where the builder won't be empty on the first iteration. However, when it's not needed, it adds a fair amount of bloat to the implementation in my experience. – Jon Skeet Dec 12 '08 at 15:42
  • 1
    This is a good idea here but say you wanted to do a list of String instead and the first Strings could be empty. This could have an unintended result. ;) – Peter Lawrey Mar 21 '09 at 12:22
  • I think this is not the most efficient solution. There are 'n' number of unnecessary if checks. Dinah's solution is better. First add all comas then rollback the addition of last comma. That looks the most efficient. Consider if the array has 1000 elements! – Real Red. Mar 21 '09 at 12:29
  • @Peter: If the first string is empty, I'd imagine you'd still want a comma. Otherwise you're not representing that first empty string. – Jon Skeet Mar 21 '09 at 14:52
  • 3
    @Liverpool (etc): 1000 unnecessary checks is very, *very* unlikely to have any significant impact on performance. I might equally point out that the extra character by Dinah's solution added *might* cause the StringBuilder to have to expand, doubling the size of the final string with (cont) – Jon Skeet Mar 21 '09 at 14:54
  • 2
    unnecessary buffer space. However, the important point is readability. I happen to find my version more readable than Dinah's. If you feel the opposite way, that's fine. I'd only consider the performance impact of the unnecessary "ifs" after finding a bottleneck. – Jon Skeet Mar 21 '09 at 14:55
  • 3
    In addition, my solution is a more generally applicable one, as it only relies on being able to write. You could take my solution and change it to write to a stream etc - where you may not be able to take back the unnecessary data afterwards. I like patterns which are generally applicable. – Jon Skeet Mar 21 '09 at 14:56
148

Another way to do this:

String delim = "";
for (int i : ints) {
    sb.append(delim).append(i);
    delim = ",";
}

Update: For Java 8, you now have Collectors

mkobit
  • 43,979
  • 12
  • 156
  • 150
toolkit
  • 49,809
  • 17
  • 109
  • 135
  • 1
    Had to delete my similar answer - that'll teach me not to post before looking more carefully at other answers. – Michael Burr Nov 12 '08 at 23:34
  • 1
    Also note that this handles the potential issue Robert Paulson mentioned in another comment thread - this technique does not depend on the StringBuilder being empty when the array values are appended. – Michael Burr Nov 12 '08 at 23:38
  • 1
    Although the code is more slick/neat/fun, it is less obvious than the if version. Always use the more obvious/readable version. – Bill K Nov 13 '08 at 19:07
  • 8
    @Bill: That's not great as a hard and fast rule; there are times where the 'clever' solution is the "more correct" solution. More to the point, do you really that this version is difficult to read? Either way a maintainer would need to step through it - I don't think the difference is significant. – Greg Case Nov 15 '08 at 02:31
  • This works although you write to other resource like filesystem's write calls. Good idea. – Tuxman Aug 20 '15 at 00:04
39

It might be easier to always append. And then, when you're done with your loop, just remove the final character. Tons less conditionals that way too.

You can use StringBuilder's deleteCharAt(int index) with index being length() - 1

Dinah
  • 52,922
  • 30
  • 133
  • 149
33

Maybe you are using the wrong tool for the Job.

This is more manual than what you are doing but it's in a way more elegant if not a bit "old school"

 StringBuffer buffer = new StringBuffer();
 Iterator iter = s.iterator();
 while (iter.hasNext()) {
      buffer.append(iter.next());
      if (iter.hasNext()) {
            buffer.append(delimiter);
      }
 }
Omar Kooheji
  • 54,530
  • 68
  • 182
  • 238
16

This is almost a repeat of this StackOverflow question. What you want is StringUtils, and to call the join method.

StringUtils.join(strArr, ',');
Community
  • 1
  • 1
Phil H
  • 19,928
  • 7
  • 68
  • 105
14

Another solution (perhaps the most efficient)

    int[] array = {1, 2, 3};
    StringBuilder builder = new StringBuilder();

    if (array.length != 0) {
        builder.append(array[0]);
        for (int i = 1; i < array.length; i++ )
        {
            builder.append(",");
            builder.append(array[i]);
        }
    }
Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
bruno conde
  • 47,767
  • 15
  • 98
  • 117
  • This is a natural solution except that it depends on the array not being empty. – orcmid Nov 13 '08 at 04:22
  • 5
    @orcmid: If the array is empty, then this still gives the correct output -- an empty string. I'm not sure what your point is. – Eddie Mar 22 '09 at 04:58
7

keep it simple and use a standard for loop:

for(int i = 0 ; i < array.length ; i ++ ){
    builder.append(array[i]);
    if( i != array.length - 1 ){
        builder.append(',');
    }
}

or just use apache commons-lang StringUtils.join()

Gareth Davis
  • 27,701
  • 12
  • 73
  • 106
6

Explicit loops always work better than implicit ones.

builder.append( "" + array[0] );
for( int i = 1; i != array.length; i += 1 ) {
   builder.append( ", " + array[i] );
}

You should wrap the whole thing in an if-statement just in case you're dealing with a zero-length array.

S.Lott
  • 384,516
  • 81
  • 508
  • 779
  • I like this approach because it does not needlessly perform a test each iteration. The only thing I would add is that builder can append integers directly so there is no need to use "" + int, just append(array[0]). – Josh Nov 12 '08 at 22:13
  • You need one more if around the whole lot to ensure that array has at least one element. – Tom Leys Nov 12 '08 at 22:21
  • 2
    why does everyone keep using examples with string concatenation INSIDE of append? the ", " + x compiles into new StringBuilder(", ").append( x ).toString()... – John Gardner Nov 12 '08 at 22:53
  • @Josh and @John: Just following the n00b example. Don't want to introduce too many things at once. Your point is very good, however. – S.Lott Nov 12 '08 at 23:10
  • @Tom: Right. I think I already said that. No edit required. – S.Lott Nov 12 '08 at 23:10
5

As toolkit mentioned, in Java 8 we now have Collectors. Here's what the code would look like:

String joined = array.stream().map(Object::toString).collect(Collectors.joining(", "));

I think that does exactly what you're looking for, and it's a pattern you could use for many other things.

b1tw153
  • 379
  • 4
  • 3
4

If you convert it to a classic index loop, yes.

Or you could just delete the last comma after it's done. Like so:

int[] array = {1, 2, 3...};
StringBuilder

builder = new StringBuilder();

for(int i : array)
{
    builder.append(i + ",");
}

if(builder.charAt((builder.length() - 1) == ','))
    builder.deleteCharAt(builder.length() - 1);

Me, I just use StringUtils.join() from commons-lang.

Dinah
  • 52,922
  • 30
  • 133
  • 149
sblundy
  • 60,628
  • 22
  • 121
  • 123
3

Here is a solution:

int[] array = {1, 2, 3...};
StringBuilder builder = new StringBuilder();
bool firstiteration=true;

for(int i : array)
{
    if(!firstiteration)
        builder.append(",");

    builder.append("" + i);
    firstiteration=false;
}

Look for the first iteration :)  

deinocheirus
  • 1,833
  • 5
  • 26
  • 44
FallenAvatar
  • 4,079
  • 1
  • 21
  • 24
3

You need Class Separator.

Separator s = new Separator(", ");
for(int i : array)
{
     builder.append(s).append(i);
}

The implementation of class Separator is straight forward. It wraps a string that is returned on every call of toString() except for the first call, which returns an empty string.

akuhn
  • 27,477
  • 2
  • 76
  • 91
3

Based on java.util.AbstractCollection.toString(), it exits early to avoid the delimiter.

StringBuffer buffer = new StringBuffer();
Iterator iter = s.iterator();
for (;;) {
  buffer.append(iter.next());
  if (! iter.hasNext())
    break;
  buffer.append(delimiter);
}

It's efficient and elegant, but not as self-evident as some of the other answers.

13ren
  • 11,887
  • 9
  • 47
  • 64
  • You forgot to include the early return when `(! i.hasNext())`, which is an important part of the robustness of the overall approach. (The other solutions here handle empty collections gracefully, so yours should too! :) – Mike Clark Apr 06 '12 at 19:55
1

Here's a SSCCE benchmark I ran (related to what I had to implement) with these results:

elapsed time with checks at every iteration: 12055(ms)
elapsed time with deletion at the end: 11977(ms)

On my example at least, skipping the check at every iteration isn't noticeably faster especially for sane volumes of data, but it is faster.

import java.util.ArrayList;
import java.util.List;


public class TestCommas {

  public static String GetUrlsIn(int aProjectID, List<String> aUrls, boolean aPreferChecks)
  {

    if (aPreferChecks) {

      StringBuffer sql = new StringBuffer("select * from mytable_" + aProjectID + " WHERE hash IN ");

      StringBuffer inHashes = new StringBuffer("(");
      StringBuffer inURLs = new StringBuffer("(");

      if (aUrls.size() > 0)
      {

      for (String url : aUrls)
      {

        if (inHashes.length() > 0) {
        inHashes.append(",");
        inURLs.append(",");
        }

        inHashes.append(url.hashCode());

        inURLs.append("\"").append(url.replace("\"", "\\\"")).append("\"");//.append(",");

      }

      }

      inHashes.append(")");
      inURLs.append(")");

      return sql.append(inHashes).append(" AND url IN ").append(inURLs).toString();
    }

    else {

      StringBuffer sql = new StringBuffer("select * from mytable" + aProjectID + " WHERE hash IN ");

      StringBuffer inHashes = new StringBuffer("(");
      StringBuffer inURLs = new StringBuffer("(");

      if (aUrls.size() > 0)
      {

      for (String url : aUrls)
      {
        inHashes.append(url.hashCode()).append(","); 

        inURLs.append("\"").append(url.replace("\"", "\\\"")).append("\"").append(",");
      }

      }

      inHashes.deleteCharAt(inHashes.length()-1);
      inURLs.deleteCharAt(inURLs.length()-1);

      inHashes.append(")");
      inURLs.append(")");

      return sql.append(inHashes).append(" AND url IN ").append(inURLs).toString();
    }

  }

  public static void main(String[] args) { 
        List<String> urls = new ArrayList<String>();

    for (int i = 0; i < 10000; i++) {
      urls.add("http://www.google.com/" + System.currentTimeMillis());
      urls.add("http://www.yahoo.com/" + System.currentTimeMillis());
      urls.add("http://www.bing.com/" + System.currentTimeMillis());
    }


    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 300; i++) {
      GetUrlsIn(5, urls, true);
    }
    long endTime = System.currentTimeMillis();
    System.out.println("elapsed time with checks at every iteration: " + (endTime-startTime) + "(ms)");

    startTime = System.currentTimeMillis();
    for (int i = 0; i < 300; i++) {
      GetUrlsIn(5, urls, false);
    }
    endTime = System.currentTimeMillis();
    System.out.println("elapsed time with deletion at the end: " + (endTime-startTime) + "(ms)");
  }
}
Buffalo
  • 3,861
  • 8
  • 44
  • 69
  • 2
    Please don't roll your own benchmarks and use [OpenJDKs own micro benchmarking harness (jmh)](http://openjdk.java.net/projects/code-tools/jmh/). It will warm up your code and avoid the most problematic pitfalls. – René Feb 25 '16 at 07:12
1

Yet another option.

StringBuilder builder = new StringBuilder();
for(int i : array)
    builder.append(',').append(i);
String text = builder.toString();
if (text.startsWith(",")) text=text.substring(1);
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
1

Many of the solutions described here are a bit over the top, IMHO, especially those that rely on external libraries. There is a nice clean, clear idiom for achieving a comma separated list that I have always used. It relies on the conditional (?) operator:

Edit: Original solution correct, but non-optimal according to comments. Trying a second time:

    int[] array = {1, 2, 3};
    StringBuilder builder = new StringBuilder();
    for (int i = 0 ;  i < array.length; i++)
           builder.append(i == 0 ? "" : ",").append(array[i]); 

There you go, in 4 lines of code including the declaration of the array and the StringBuilder.

Julien Chastang
  • 17,592
  • 12
  • 63
  • 89
0

Another approach is to have the length of the array (if available) stored in a separate variable (more efficient than re-checking the length each time). You can then compare your index to that length to determine whether or not to add the final comma.

EDIT: Another consideration is weighing the performance cost of removing a final character (which may cause a string copy) against having a conditional be checked in each iteration.

Brian
  • 3,457
  • 4
  • 31
  • 41
  • Removing a final character shouldn't cause a string copy to be made. StringBuffer's delete/deleteCharAt methods eventually call the following method inside the System class identical source and destination arrays: System -> public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length); – Buffalo Aug 01 '14 at 14:51
0

Two alternate paths here:

1: Apache Commons String Utils

2: Keep a boolean called first, set to true. In each iteration, if first is false, append your comma; after that, set first to false.

Hulk
  • 6,399
  • 1
  • 30
  • 52
Paul Brinkley
  • 6,283
  • 3
  • 24
  • 33
0

If you're only turning an array into a comma delimited array, many languages have a join function for exactly this. It turns an array into a string with a delimiter between each element.

Dinah
  • 52,922
  • 30
  • 133
  • 149
0

Since its a fixed array, it would be easier simply to avoid the enhanced for... If the Object is a collection an iterator would be easier.

int nums[] = getNumbersArray();
StringBuilder builder = new StringBuilder();

// non enhanced version
for(int i = 0; i < nums.length; i++){
   builder.append(nums[i]);
   if(i < nums.length - 1){
       builder.append(",");
   }   
}

//using iterator
Iterator<int> numIter = Arrays.asList(nums).iterator();

while(numIter.hasNext()){
   int num = numIter.next();
   builder.append(num);
   if(numIter.hasNext()){
      builder.append(",");
   }
}
AnthonyJClink
  • 1,008
  • 2
  • 11
  • 32
0

In this case there is really no need to know if it is the last repetition. There are many ways we can solve this. One way would be:

String del = null;
for(int i : array)
{
    if (del != null)
       builder.append(del);
    else
       del = ",";
    builder.append(i);
}
fastcodejava
  • 39,895
  • 28
  • 133
  • 186
0

You can use StringJoiner.

int[] array = { 1, 2, 3 };
StringJoiner stringJoiner = new StringJoiner(",");

for (int i : array) {
    stringJoiner.add(String.valueOf(i));
}

System.out.println(stringJoiner);
Hari Krishna
  • 3,658
  • 1
  • 36
  • 57