Thread: How to get the index of a vector of tuples

  1. #1
    Registered User
    Join Date
    Feb 2015
    Posts
    56

    How to get the index of a vector of tuples

    So in my game the player can use items from their inventory, which is a vector of tuple (
    Code:
    vector<tuple<Item, int>> mInventory{ 0 };
    ) and in my code I noticed an issue, If the player has a Weak Potion which gives 10 health and a Super Potion that gives 25 health, it seems to use both items because its in a range based for loop, but i cannot seem to figure out how to just get one element of the vector and access the tuple outside of an iterator or RBFL. here is the code for that function:

    Code:
    void Character::UseItem(){
        int choice{ 0 };
        int counter{ 1 };
    
    
        while (choice != -1)
        {
    		if (mInventory.empty())
            {
                cout << "You do not have any items to use." << endl;
                choice = -1;
            }
            else
            {
    			cout << "\n################################################################" << endl;
                cout << "                          INVENTORY                        " << endl;
                cout << "################################################################" << endl;
    
    
    			cout << "What item do you want to use? Type -1 to quit\n" << endl;
    			cout << GetName() << "'s current health is: " << mHealth << "\n" << endl;
    
    
                counter = 1;
    
    
                for (auto& i : mInventory)
                {
                    cout << counter++ << ") " << get<0>(i) << " (" << get<1>(i) << ")" << " Effect: " << get<0>(i).GetEffect() << endl;
                }
    
    
                cin >> choice;
    
    
                counter = 1;
    
    
                if (!cin.fail())
                {
                    for (auto& j : mInventory)
                    {
    				    //If amount of items owned is greater than 0, and health is not full
                        if (get<1>(j) > 0 && mHealth != MAX_HEALTH)
                        {
    					    cout << counter++ << ") " << get<0>(j) << " (" << --get<1>(j) << ")" << endl;
                            Heal(get<0>(j).GetEffect());
                            cout << "\n" << GetName() << "'s health restored to " << GetHealth() << "\n" << endl;;
                        }
                        else if (mHealth == MAX_HEALTH)
                        {
                            cout << "\nYou are already at full health.\n" << endl;
                        }
                        else
                        {
                            cout << "\nYou do not have enough " << get<0>(j) << "'s" << " to use\n" << endl;
                        }
                    }
                }
                else
                {
                    cout << "Error, Invalid Input" << endl;
    			    cin.clear();
                    cin.ignore(numeric_limits<streamsize>::max(), '\n');
                }   
            }
        }
    }

  2. #2
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,302
    It sounds like the problem is that you're not actually using the item choice, i.e., you're just looping through the whole inventory after the choice has been made. What I might do is this:

    1. Check that the choice is valid, i.e., that the read was successful and that the value of choice maps to a valid index in the inventory vector. If not, you request that the user make a valid choice.
    2. Access the item at the computed index (i.e., subtract 1) in the inventory vector.
    3. Apply the item's effect. It is here that if the effect is healing, then you heal the player unless the player is already at full health.
    4. If the item has been successfully used (e.g., the effect is healing and the player is not at full health), then you subtract 1 from the number of items.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  3. #3
    Registered User
    Join Date
    Feb 2015
    Posts
    56
    But how does that look in code? thats where im confused. I cant seem to figure out how to get the index of the vector and the tuple, if it was a normal vector i could just do something like:

    mInventory[choice]; without needing a for loop or iterator.

    but that wont work because its a tuple vector. I have to access the index of where the item tuple is and then use it somehow, but how? every single example i have ever seen using a tuple vector, they wither use an iterator or RBFL and I cannot find one example where they just access the value outside of one. Both a standard vector and tuple can be accessed outside of an iterator and RBFL, but when combined, that doesn't seem to wok and that's where im getting mixed up, how do i access the tuples inside the vector without needing to iterate over it?

  4. #4
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,302
    Quote Originally Posted by CH1156
    But how does that look in code? thats where im confused. I cant seem to figure out how to get the index of the vector and the tuple, if it was a normal vector i could just do something like:

    mInventory[choice]; without needing a for loop or iterator.
    It's mInventory[choice - 1] because you numbered the choices starting from 1. So you would write get<0>(mInventory[choice - 1]) to get the Item itself.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  5. #5
    Registered User
    Join Date
    Feb 2015
    Posts
    56
    Ohhhhhhh, thank you so much, I've been having trouble figuring that out.

  6. #6
    Registered User
    Join Date
    Feb 2015
    Posts
    56
    If the player has 3 weak potions and they use alll of them, how would I remove the tuple element of that item? I cant seem to get it working.

    Code:
    for (auto& i : mInventory)                    
    {
           if (get<1>(i) == 0)
           {
                mInventory.erase(mInventory.begin());
            }
    }
    This sort of works but i cant seem to figure out how to get the current element. If the player only uses the first item up then its fine but the player wont use the inventory that way. What am I missing?

  7. #7
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,302
    I think you need to realise that range-based for loops are meant for those times when you want to iterate over the entire container (or range, etc). You seem to be in the habit of reaching for them at every opportunity, even when it doesn't make sense.

    What you have is an inventory usage loop that allows the user to use one amount of an item at one time. This means that when the count for an item reaches zero, you know exactly which item it is by index. Therefore you only need to compute the iterator from the index in order to pass it to erase, and that's easy: begin() + index gives you that iterator (or more generically, there's std::advance).
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  8. #8
    Registered User
    Join Date
    Feb 2015
    Posts
    56
    Yeah I realized that mistake and removed the for loop from my first post and that works fine, but with this I want to remove every element where the tuple has a value of 0 for the second element of the tuple so i thought a for loop was best but i'll just not use a for loop for this.. I have tried these methods and none work:

    Try 1

    Code:
    if (get<1>(i) == 0)
    {
          mInventory.erase(mInventory.begin() + get<1>(mInventory[choice - 1]));
     }
    Try 2:

    Code:
    if (get<1>(i) == 0)
    {
        mInventory.erase(mInventory.begin() + get<1>(i));
    }
    Try 3

    This works but i dont know if there is some hidden problems with it. Please let me know if there is.

    The syntax is difficult to understand sometimes :/

    Code:
    if (get<1>(mInventory[choice - 1]) == 0)
    {
       mInventory.erase(mInventory.begin() + choice - 1);
    }
    Last edited by CH1156; 03-08-2021 at 01:23 AM.

  9. #9
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,302
    Quote Originally Posted by CH1156
    I want to remove every element where the tuple has a value of 0 for the second element of the tuple
    For that, the typical approach is to #include <algorithm> and write:
    Code:
    mInventory.erase(
        std::remove_if(
            mInventory.begin(),
            mInventory.end(),
            [](const auto& item) { return std::get<1>(item) == 0; }
        ),
        mInventory.end()
    );
    remove_if is used because now you want to remove elements from the vector if they satisfy the condition specified in the lambda. However, remove_if "removes" by shifting the elements not removed to the front of the range, returning an iterator to one past the end of the new valid range. So we take this iterator and pass it to mInventory's range-based erase member function, so that it can erase those "removed" elements.

    This approach is suitable if you want the player to be able to use multiple items and have their counts set to 0, and then later you perform clean up to erase all these zero-count items. It wouldn't make sense to use it for the scenario you described in post #6 ("if the player has 3 weak potions and they use alll of them, how would I remove the tuple element of that item") because it would be overkill, i.e., you'll be removing all zero-count items (and you don't know which ones they are) when there is exactly one zero-count item and you know exactly what it is.

    Quote Originally Posted by CH1156
    This works but i dont know if there is some hidden problems with it. Please let me know if there is.
    The "hidden" problem is that you're using the choice variable. In this scenario of removing every element, the choice variable conceptually does not exist: there is no choice because if you're zero, you're removed, no appeal granted. The player is not allowed to choose anything (those choices have already been made and the items used, so the time to choose has passed), hence what you're doing is conceptually wrong.

    However, if you're trying to solve for the problem of "if the player has 3 weak potions and they use alll of them, how would I remove the tuple element of that item", then this is correct, because at this point, the player has chosen the weak potion, and you check its count after usage and find that it is zero, so you erase it. That's what I was talking about in my previous post.
    Last edited by laserlight; 03-08-2021 at 02:06 AM.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 2
    Last Post: 11-08-2016, 08:39 PM
  2. How to find Index positions with a vector STL?
    By codeguy in forum C++ Programming
    Replies: 6
    Last Post: 01-11-2008, 01:07 PM
  3. How to get value at particular index from a vector?
    By ketu1 in forum C++ Programming
    Replies: 14
    Last Post: 01-03-2008, 01:53 PM
  4. How to find index of a particular record in a vector?
    By ketu1 in forum C++ Programming
    Replies: 7
    Last Post: 01-02-2008, 12:22 PM
  5. Illegal vector index problem
    By Maiq in forum C++ Programming
    Replies: 5
    Last Post: 02-28-2003, 06:07 PM

Tags for this Thread