views:

5759

answers:

11

I'm iterating through a HashMap (see my earlier question for more detail) and building a string consisting of the data contained in the Map. For each item, I will have a new line, but for the very last item, I don't want the new line. How can I achieve this? I was thinking I could so some kind of check to see if the entry is the last one or not, but I'm not sure how to actually do that.

Thanks!

+19  A: 

Change your thought process from "append a line break all but the last time" to "prepend a line break all but the first time":

boolean first = true;
StringBuilder builder = new StringBuilder();

for (Map.Entry<MyClass.Key,String> entry : data.entrySet()) {
    if (first) {
        first = false;
    } else {
        builder.append("\n"); // Or whatever break you want
    }
    builder.append(entry.key())
           .append(": ")
           .append(entry.value());
}
Jon Skeet
Thanks! I especially appreciate the reference to a change in thought process. I often forget that there are myriad ways to solve these types of problems (you'd think I'd know better).
Blue
With ordered collections, you can get rid of the boolean by creating the StringBuilder with the first item, then running the loop from i to the end: new StringBuilder(collection.get(0)); for(int i = 1; i < collection.size(); i++). Be sure to guard against empty collections.
Outlaw Programmer
@Blue: +1 for using "myriad" properly. Far too many people say "a myriad" which is like saying "the hoi polloi" :(
Jon Skeet
This is even nicer when factoring the if/else into a Separator Class whose #toString method prints empty string in the first call, and the separator on all subsequent calls.
Adrian
@Jon: +1 (not that you need it <g>). Nicely done.
Ken White
This loop could be optimized to avoid creating 2 strings for each iteration through the loop. builder.append('\n'); and builder.append(':').append(' ');
Fostah
+1  A: 

Assuming your foreach loop goes through the file in order just add a new line to every string and remove the last new line when your loop exits.

Jared
+1  A: 

Not sure if this is the best, but it´s the easier way to do:

loop through all the values and append the \n normally in the stringbuffer. Then, do something like this

sb.setLength(sb.length()-1);
Tiago
this works for linux/unix where line separator char is '\n' and for mac where it is '\r' but for other OSes like windows which is "\r\n" this doesn't really work, you should check the length of the line terminator from the system properties.
Paulo Lopes
The op explicitly said he was appending '\n', so the length is known.
Richard Campbell
Agree with Paulo, not a good assumption to hard-code.
Bill K
+21  A: 

one method (with apologies to Jon Skeet for borrowing part of his Java code):

StringBuilder result = new StringBuilder();

string newline = "";  
for (Map.Entry<MyClass.Key,String> entry : data.entrySet())
{
    result.append(newline)
       .append(entry.key())
       .append(": ")
       .append(entry.value());

    newline = "\n";
}
Joel Coehoorn
Oh that's very nice indeed. I like that a *lot*. So tempting to register more users to upvote more times ;)
Jon Skeet
I do it that way, too *thumbs up*
Peter
Personally I see this as the lesser evil. I use it, but I don't like it. The code is cleaner than an if/else version, but you still have to look at it an extra moment to understand it. And it codes the delimiter at the end of the block rather than the top. I just don't know anything better.
Joel Coehoorn
I use that pattern a lot
martinus
This is not valid Java. Java does not have foreach. You just use for with the same syntax as above.
Martin OConnor
Thanks for spotting that. fixed it.
Joel Coehoorn
Which operation is cheaper among assignment(Joel's) and condition(Jon's) that are done in every iteration?
grayger
If it actually does the operation, I suspect condition. But I also expect the assignment to be jit'd away, so you'll have to experiment/profile to know for sure. Either way, the code clarity is probably more important.
Joel Coehoorn
+5  A: 

Ususally for these kind of things I use apache-commons-lang StringUtils#join. While it's not really hard to write all these kinds of utility functionality, it's always better to reuse existing proven libraries. Apache-commons is full of useful stuff like that!

André
+3  A: 

If you use iterator instead of for...each your code could look like this:

StringBuilder builder = new StringBuilder();

Iterator<Map.Entry<MyClass.Key, String>> it = data.entrySet().iterator();

while (it.hasNext()) {
    Map.Entry<MyClass.Key, String> entry = it.next();

    builder.append(entry.key())
    .append(": ")
    .append(entry.value());

    if (it.hasNext()) {
        builder.append("\n");
    }
}
jan
+1 - whenever I need to special-case the first/last element, I prefer doing it this way rather than using a boolean variable to detect the first character.
Andrzej Doyle
+2  A: 

Here's my succinct version, which uses the StringBuilder's length property instead of an extra variable:

StringBuilder builder = new StringBuilder();

for (Map.Entry<MyClass.Key,String> entry : data.entrySet())
{
    builder.append(builder.length() > 0 ? "\n" : "")
           .append(entry.key())
           .append(": ")
           .append(entry.value());
}

(Apologies and thanks to both Jon and Joel for "borrowing" from their examples.)

LukeH
+4  A: 

What about this?

StringBuilder result = new StringBuilder();

for(Map.Entry<MyClass.Key,String> entry : data.entrySet())
{
    builder.append(entry.key())
       .append(": ")
       .append(entry.value())
       .append("\n");
}

return builder.substring(0, builder.length()-1);

Obligatory apologies and thanks to both Jon and Joel for "borrowing" from their examples.

Jay R.
This is my typical approach to creating delimited strings.
Wes P
this gives a java.lang.StringIndexOutOfBoundsException with a empty list.
pvgoddijn
That exception is easy to fix with a simple if which checks if builder length is greater than 0.
Domchi
+1, this solution has better performance then the repetitive if-else check or the repetitive assignment to the newline string
Yoni
A: 

This is where a join method, to complement split, would come in handy, because then you could just join all the elements using a new line as the separator, and of course it doesn't append a new line to the end of the result; that's how I do it in various scripting languages (Javascript, PHP, etc.).

George Jempty
A: 

If you use Class Separator, you can do

StringBuilder buf = new StringBuilder();
Separator separator = new Separator("\n");
for (Map.Entry<MyClass.Key,String> entry: data.entrySet()) {
    builder.append(separator)
       .append(entry.key())
       .append(": ")
       .append(entry.value());
}

The separator prints in empty string upon its first use, and the separator upon all subsequent uses.

Adrian
A: 
Joel Coehoorn