tags:

views:

334

answers:

12

How would I make so that the last player name doesn't have a , so it's:

Player online:
Jim, John, Tony

and not

Player online:
Jim, John, Tony,

My code is:

bool Commands::whoIsOnline(Creature* c, const std::string &cmd, const std::string &param)
{
Player* player = dynamic_cast<Player*>(c);

if (player)
{
    player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Players online: ");
    AutoList<Player>::listiterator iter = Player::listPlayer.list.begin();
    std::string info;
    int count = 0;

    while (iter != Player::listPlayer.list.end())
    {
        info += (*iter).second->getName() + ", ";
        ++iter;
        ++count;

        if (count % 10 == 0)
        {
            player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, info.c_str());
            info.clear();
        }
    }

    if (!info.empty())
        player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, info.c_str());
}

return true;
}
+4  A: 

change

while(iter != Player::listPlayer.list.end())
{
    info += (*iter).second->getName() + ", ";
//...

with:

if(iter != Player::listPlayer.list.end()){
    info += (*iter).second->getName();
    ++iter;
    while(iter != Player::listPlayer.list.end()){
    {
        info += ", " + (*iter).second->getName();     
        //...
    }
    //...
}

alternatively, you can do something like this if you don't want the comma in front of a name after the info.clear():

while(iter != Player::listPlayer.list.end())
{
    info += ", " + (*iter).second->getName();
    // ...
        player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, info.c_str()+2);
Mark E
I'd use this approach, but make the while-loop subordinate to the if-statement. There's no way the while's first test would succeed if the if's didn't.
Boojum
@Boojum, good call, I'll edit appropriately
Mark E
+2  A: 

The easiest way is to simply remove the additional ", " in the end:

if (!info.empty()) {
  info.erase(info.size()-2);
}
sth
A: 

If this is C++ and that is an STL iterator, then if the iterator is a random-access iterator, then you could actually ask

if (iter + 1 == Plaer::listPlayer.list.end())

If you're not allowed to do that, then you probably want to put the code inside the while loop that prints a player's name in a separate function and call that function on the first element before the while loop, then call it inside the while loop. Then put the code that prints the comma before the call to the player name print in the while loop. That way the first call will print just the first name, and then the while loop will always first print a comma and then the player's name, so that the output always ends with a player's name.

David Gladfelter
+7  A: 

Instead of thinking it like player + "," think of it as "," + player

So you could do something like this (psuedo-code):

onFirstName = true
output = ""
for each player in players:
    if onFirstName:
        onFirstName = false
    else:
        output += ", "
    output += player's name

of if your language supports it (Which c++ does):

if length of players > 0:
    output = players[0]
    for each player in players except players[0]:
        output += ", " + player's name
else:
    output = ""

I like the look of that last one, I'll have to invent a language that actually works like that.

Wallacoloo
it'd be much faster to simply iterate only over players 2..end (if they exist). if only someone had suggested that..
Mark E
It would indeed. I've never used C++ iterators, I didn't know you could do that with them.
Wallacoloo
take a look at my answer =) you just need to increment after looking at the first value.
Mark E
almost, I think you mean `for each player in players[1..]`
Mark E
Your second approach needs a check to make sure the list isn't empty.
Boojum
Good point Boojum, I never would have thought of that.
Wallacoloo
@Mark, please define "much". It's not "much faster". You'll shave off right about a nanosecond. Maybe. After iterating through it about a million times
Milan Ramaiya
@Reverend fair enough, it does in fact seem negligible after all... inside: http://ideone.com/DDho1L7Z, outside: http://ideone.com/xXuPikH4
Mark E
+2  A: 

(Borrowing wallacoloo's pseudocode)

output = "" 
for each player in players: 
    if output != "" 
        output += ", " 
    output += player's name 
Carlos Gutiérrez
just like wallacoloo's code, yours would be much faster if you look at the first player outside the loop, that way you don't hit the if statement for every other player...
Mark E
Seriously, "much faster"? How much? More or less than 25%?
Steve Jessop
Yes, but: You would be duplicating similar code outside and inside the loop (imagine you had to lookup or format player's name), and: comparing against null usually is very optimizable.
Carlos Gutiérrez
This doesn't do quite the same as the question says in the title. You've assumed that after the first 10 items, when `info.clear()` has been called, that the next item shouldn't have a comma in front of it.
Steve Jessop
@Steve Jessop- right, it actually detects the first iteration. For the first it just adds the name, for the rest, it adds a `,` and then the name.
Carlos Gutiérrez
A: 

I wrote some sample code awhile ago to demonstrate a few different ways of doing this in C:

http://www.taenarum.com/csua/fun-with-c/delimiter.c

Unfortunately, there is no method that is clearly superior to the others. I personally would go with a conventional approach (explicitly check for either the first or last element) for clarity and to avoid duplicating code. (And definitely avoid using the goto version in C++ code.)

jamesdlin
+1  A: 

If this were my code, I'd probably just check the string at the beginning of the loop and add the comma when it's not empty. It's nice to know how to handle similar situations when that workaround is not available, so here's an alternate:

while (iter != Player::listPlayer.list.end())
{
    info += (*iter).second->getName();
    ++iter;
    if (iter != Player::listPlayer.list.end())
        info += ", ";
    ++count;
    ...
}
Mark Ransom
+2  A: 

You can use string join from .NET or Boost or some other library or write your own. Although it might be overkill for that particular function, it's the kind of thing that you'll probably use elsewhere in that project, and that you'll definitely reuse in another project.

JRL
+1  A: 

Instead of finding the last iteration, find the first iteration. Handle special cases at the beginning of the loop, have a definite "clean" state before doing the "real work," and perform the increment at the end.

while (iter != Player::listPlayer.list.end())
{
    if ( count != 0 )
    {
        info += ", ";

        if (count % 10 == 0)
        {
            player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, info.c_str());
            info.clear();
        }
    }
    // invariant: info is clean and ready to accept data

    info += (*iter).second->getName();
    ++iter;
    ++count;
}
Potatoswatter
+1  A: 

My solution involves a variable that starts out as the empty string and is set to ", " after each iteration (which only has an effect after the first iteration). No special cases need to be checked.

template<class ForwardIterator>
std::string sequence_to_string(ForwardIterator begin, ForwardIterator end)
{
    std::string output;
    const char* delimiter = "";
    for (ForwardIterator it = begin; it != end; ++it)
    {
        output += delimiter;
        output += *it;
        delimiter = ", ";
    }
    return output;
}
FredOverflow
A: 
...    
std::string info;
...
while (iter != Player::listPlayer.list.end())
{
  if(info.size() > 0)
    info += ",";
  info += (*iter).second->getName();
  ......  
}
Liao
A: 

I don't know what I was smoking when I wrote this tripe. I would down mark it myself, but it prevents self voting too.

Chris Becke
http://ideone.com/XftGYajt disagrees with you. Unfortunately, it won't let me take back my up-vote.
Wallacoloo