开发者

How can I recognize the last iteration in a C++ while loop?

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;
}


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.


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);


(Borrowing wallacoloo's pseudocode)

output = "" 
for each player in players: 
    if output != "" 
        output += ", " 
    output += player's name 


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

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


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.


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;
    ...
}


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;
}


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;
}


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.


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.)


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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜