26

I am coding for a function that takes a hand and checks for pairs:

int containsPairs(vector<Card> hand)
{
    int pairs{ 0 };

    loopstart:
    for (int i = 0; i < hand.size(); i++)
    {
        Card c1 = hand[i];
        for (int j = i + 1; j < hand.size(); j++)
        {
            Card c2 = hand[j];
            if (c1.getFace() == c2.getFace())
            {
                pairs++;
                hand.erase(hand.begin() + i);
                hand.erase(hand.begin() + (j - 1));
                goto loopstart;
            }
        }
    }
    return pairs;
}

When it finds pair on line 10, I want to delete the cards in the hand it found the pair with and then restart the whole loop with the deleted cards to find a second pair, if there is one. For me, goto was the most intuitive way to do this, but in this case, is that true?

Alex
  • 620
  • 1
  • 6
  • 20
  • 36
    "Should I avoid using goto here?" *Almost always* the answer to that is going to be "yes". With a few rare exceptions. – Jesper Juhl Nov 09 '17 at 21:22
  • 7
    Have a removePair function that returns bool (instead of goto) and loop while it returns true. That should be goto-less and equivalent. (You can incr pairs by how many times that returned true) – Borgleader Nov 09 '17 at 21:23
  • 1
    Look up the [erase-remove idiom](https://en.wikipedia.org/wiki/Erase%E2%80%93remove_idiom). But in this case before you `erase` the unwanted elements, you count them. – user4581301 Nov 09 '17 at 21:28
  • 16
    Seems like a good use of `goto` to me. – wally Nov 09 '17 at 21:32
  • 3
    Can't you sort cards first by any chance? – user7860670 Nov 09 '17 at 21:39
  • 1
    @JesperJuhl I find goto to be extremely readable (certainly much more readable than some of the workarounds people suggest sometimes). Goto can even make the code more readable -- you can use the find option in your IDE to directly find the labels. – Cpp plus 1 Nov 09 '17 at 21:44
  • 15
    Not a problem with `goto`. In many cases it is better and natural to use `goto` instead of false infinite `while` loops and other tricks only to avoid "The Evil" (goto). – i486 Nov 09 '17 at 22:02
  • 17
    The fundamental problem here is not the *goto*! The fundamental problem is that you modify a data structure in order to run a query on it! This is not only terrible style, it is also *wrong*. A hand `2S, 2H, 3D, 3H, 3S` has four pairs -- 2S/2H, 3D/3H, 3D/3S, 3H/3S -- but you are only counting two of them! – Eric Lippert Nov 09 '17 at 23:48
  • 5
    Start over from the beginning. You wish to know how many pairs there are and not modify the data structure to do it. The naive algorithm is to have an outer loop which runs over each card in the hand, and then an inner loop which runs over each *subsequent* card and checks them for identity. So you'd start with 2S, and determine that it has one match to 2H and zero matches to the rest. Then you do 2H, and see that it has zero matches with 3D, 3H, 3S. Then you do 3D and find two matches, then you do 3H and find one match, and then you're done. – Eric Lippert Nov 09 '17 at 23:50
  • 1
    Still no [`continue – Bergi Nov 10 '17 at 00:24
  • This is pairs from a poker perspective, I belive two-pair and one pair are the only possibilites, this covers both. – Alex Nov 10 '17 at 04:57
  • @AlexRosenbach Also, generally, when evaluating hands, you evaluate many things at once. Have an array or other mapping of ranks and suits and counters. Iterate through and increment each counter. One of them has a 4? Boom you have 4 of a kind. The only particularly difficult thing to check for is a straight flush, but you only need to check for it if any suit counter reaches 5. =) – corsiKa Nov 10 '17 at 06:51
  • 1
    @EricLippert I'm never playing poker with you - "My four pairs beats your full house" ;) – Ergwun Nov 10 '17 at 11:11
  • FYI, [codereview.se] is better suited for questions like this. – Gerald Schneider Nov 10 '17 at 12:51
  • 1
    @JesperJuhl I'm surprised no one has posted the semi-obligatory relevant [XKCD](https://xkcd.com/292/). – Engineer Toast Nov 10 '17 at 14:01
  • @Ergwun: Well I'm definitely playing Cribbage with you! :-) – Eric Lippert Nov 10 '17 at 14:30
  • 1
    @Bergi why? We have `goto` for that ;) – Quentin Nov 10 '17 at 17:13
  • This should be on code review.... – jmoreno Nov 10 '17 at 20:00
  • don't use line numbers here because people don't know which line you're talking about. Put a comment on the line instead – phuclv Nov 13 '17 at 02:36
  • @EricLippert Ha. Old (card) school! – Ergwun Nov 16 '17 at 00:29

16 Answers16

28

Try this:

int containsPairs(vector<int> hand)
{
    int pairs{ 0 };

    for (int i = 0; i < hand.size(); i++)
    {
        int c1 = hand[i];
        for (int j = i + 1; j < hand.size(); j++)
        {
            int c2 = hand[j];
            if (c1 == c2)
            {
                pairs++;
                hand.erase(hand.begin() + i);
                hand.erase(hand.begin() + (j - 1));
                i--;
                break;
            }
        }
    }
    return pairs;
}

This is almost your version, the only difference is that instead of goto, there is i--; break;. This version is more efficient than yours, as it only does the double-loop once.

Is it more clear? Well, that's a personal preference. I'm not against goto at all, I think its current "never use it" status should be revised. There are occasions where goto is the best solution.


Here's another one, even simpler solution:

int containsPairs(vector<int> hand)
{
    int pairs{ 0 };

    for (int i = 0; i < hand.size(); i++)
    {
        int c1 = hand[i];
        for (int j = i + 1; j < hand.size(); j++)
        {
            int c2 = hand[j];
            if (c1 == c2)
            {
                pairs++;
                hand.erase(hand.begin() + j);
                break;
            }
        }
    }
    return pairs;
}

Basically, when it finds a pair, it removes only the farther card, and breaks the loop. So there is no need to be tricky with i.

geza
  • 28,403
  • 6
  • 61
  • 135
  • @VTT: I know. That's why it is more efficient, while returning the same result. There is no need to restart the search from scratch. – geza Nov 09 '17 at 22:06
  • Sorry. Had to downvote for the bits about GOTO. GOTO is at best one of those, "DANGER: EXERCISE EXTREME CAUTIOUS AND AVOID" things. Mostly doing away with it has *vastly* improved the readability of software. For general advice, avoiding GOTO whenever possible is a good rule to code by. – jpmc26 Nov 09 '17 at 22:43
  • @Hawkings: it's not me, it was in the original example. I don't use this syntax. If you're interested, I've asked this question before, and there has no convincing answers arrived yet: https://stackoverflow.com/questions/46403621/why-uniform-initialization-initialization-with-braces-is-recommended – geza Nov 09 '17 at 22:46
  • @geza I'm aware that an OS operates at such a low level that it may need to use jumps directly. It also contains assembly code. If you are working in that realm of problems, yes, you deal with the consequences of needing that flexibility. This does not make the tools any less dangerous. It simply means that because you operate much closer to the bare metal of the machine, the risks are a problem you must manage. Avoiding the risk is a vastly superior solution when it's an option. – jpmc26 Nov 09 '17 at 23:02
  • 2
    @geza Yes, they are limited, more manageable GOTOs; even a `return` is one. I've pointed this out myself before in discussion. The ultimate problem with GOTO is that it's *not* restricted at all. It's like fire. Tend to it properly and keep it in the fireplace, and it warms your house. Let even a few sparks of it out and it burns the entire place down. `break`, `throw`, and `return` are like gas fireplaces: most of the work of carefully tending to them has been eliminated, making them much easier to use safely. – jpmc26 Nov 09 '17 at 23:07
  • @geza In some sense, we are. Where we are not is that "its reputation should be restored." It *should* have a reputation as something dangerous to be avoided, because it should be avoided. I'd much rather new or incompetent programmers be terrified of it until they have the skills to handle it correctly, just like I'd much rather 5 year olds not try to build fires, before they have the attention and skills to handle it properly. – jpmc26 Nov 09 '17 at 23:11
  • @geza Let's just say that I expect developers who have come far enough to use it responsibly to realize that pretty much everything in software is like that. For people who are still immature in their ability to recognize good code from bad, we're better off if they keep thinking it's fundamentally horrible. – jpmc26 Nov 09 '17 at 23:18
  • 1
    The "even simpler solution" is wrong, since it never removes the `i`th element, and removes the incorrect `j`th element. – 1201ProgramAlarm Nov 10 '17 at 01:18
  • @1201ProgramAlarm: oops, I forgot to remove `-1`. Now its correct. Thanks for noticing this! – geza Nov 10 '17 at 01:30
  • 7
    Maybe it's just me, but I'd rather work with a programmer who uses `goto` than one who modifies a loop variable in the middle of a loop (not that I'd want to work with either one). – Bernhard Barker Nov 10 '17 at 06:31
  • 4
    Your second solution is wrong: if a hand contains 3 identical cards this is reported as two pairs – CharlieB Nov 10 '17 at 09:51
  • @Dukeling: these are controversial subjects. I'd work with good programmers. The second version doesn't modify the loop variable. – geza Nov 10 '17 at 11:01
  • @CharlieB: are you sure? I think it correctly reports 1. – geza Nov 10 '17 at 11:02
  • 2
    @geza ah no, you're right: I thought your second one decremented `i` as well, but it doesn't – CharlieB Nov 10 '17 at 11:09
  • @jpmc26 In C++ I'd almost say there is no defensible use of `goto`, to be honest - c++ has exception handling built-in. That pretty much eliminates any legitimate need for `goto`. If you're writing in C, however... that's a different story. – J... Nov 10 '17 at 16:49
  • 1
    @J... `goto` is for flow control. Exceptions should *never* be used for flow control. – Quentin Nov 10 '17 at 17:14
  • 1
    @Quentin Exceptions *are* a kind of flow control. Don't be silly. Exceptions should not be used for *regular* flow control - they are there for *exceptional* flow control. It's a kind of flow control that `goto` is very useful for... if you don't already have exception handling. – J... Nov 10 '17 at 17:21
  • @J... it seems that we're agreeing here -- I did mean regular flow control :) – Quentin Nov 10 '17 at 17:30
  • @Quentin Good, I like agreement ;) – J... Nov 10 '17 at 17:47
  • @J... I prefer, "They are for *failure handling* flow control." Saying they're for "exceptional" flow control always leaves me wondering, "What constitutes 'exceptional'?" But yes, in languages lacking more expressive constructs, it might be necessary. – jpmc26 Nov 11 '17 at 03:32
21

A (slightly) faster algorithm also avoids the goto

Erasing from a std::vector is never fast and should be avoided. The same holds for copying a std::vector. By avoiding both, you also avoid the goto. For example

size_t containsPairs(std::vector<Card> const &hand) // no copy of hand
{
    size_t num_pairs = 0;
    std::unordered_set<size_t> in_pair;

    for(size_t i=0; i!=hand.size(); ++i)
    {
        if(in_pair.count(i)) continue;
        auto c1 = hand[i];
        for(size_t j=i+1; j!=hand.size(); ++j)
        {
            if(in_pair.count(j)) continue;
            auto c2 = hand[j];
            if (c1.getFace() == c2.getFace())
            {
                ++num_pairs;
                in_pair.insert(i);
                in_pair.insert(j);
            }
        }
    }
    return num_pairs;
}

For large hands, this algorithm is still slow, since O(N^2). Faster would be sorting, after which pairs must be adjacent cards, giving a O(N logN) algorithm.

Yet faster, O(N), is to use an unordered_set not for the cards in pairs, but for all other cards:

size_t containsPairs(std::vector<Card> const &hand) // no copy of hand
{
    size_t num_pairs = 0;
    std::unordered_set<Card> not_in_pairs;
    for(auto card:hand)
    {
        auto match = not_in_pairs.find(card));
        if(match == not_in_pairs.end())
        {
            not_in_pairs.insert(card);
        }
        else
        {
            ++num_pairs;
            not_in_pairs.erase(match);
        }   
    }
    return num_pairs;
}

For sufficiently small hand.size(), this may not be faster than the code above, depending on the sizeof(Card) and/or the cost of its constructor. A similar approach is to use distribution as suggested in Eric Duminil's answer:

size_t containsPairs(std::vector<Card> const &hand) // no copy of hand
{
    std::unordered_map<Card,size_t> slots;
    for(auto card:hand)
    {
        slots[card]++;
    }
    size_t num_pairs = 0;
    for(auto slot:slots)
    {
        num_pairs += slot.second >> 1;
    }
    return num_pairs;
}

Of course, these methods can be implemented much more simply if Card can be trivially mapped into a small integer, when no hashing is required.

Walter
  • 44,150
  • 20
  • 113
  • 196
  • 10
    @VTT exactly, there is no need to modify hand to count pairs. The OP's algorithm only modified its local copy. – Walter Nov 09 '17 at 22:07
  • 1
    "Erasing from a std::vector is never fast and should be avoided." It's not that bad these days cf. a typical destructor call. Good OS can move chunks of memory around in a blink of an eye. – Bathsheba Nov 09 '17 at 22:08
  • 2
    But actually, this answer does represent an improvement in execution *and* removes the `goto`. Have an upvote. – Bathsheba Nov 09 '17 at 22:12
  • @VTT, that doesn't matter. The OP's function takes a value copy. – Bathsheba Nov 09 '17 at 22:12
  • @Bathsheba Yes, but it actually seems that removing pairs from vector is primary job of this function. – user7860670 Nov 09 '17 at 22:14
  • 2
    @VTT the primary job was to count pairs. Removing cards was done only to avoid considering a card for more than one pair. Here, I simply avoid that by putting the (indices of) such cards in a set and ignoring cards in that set. Otherwise the same basic O(N^2) algorithm. – Walter Nov 09 '17 at 22:21
  • @Walter thank you for this answer, even though it's not the one I picked. It shows how with some clever maniupulation you can make way a better and more effecient function than I did. Ultimately I did pick the other guy's answer since it only took a couple lines of code to change, but I really appreciate this answer for reference in the future, thanks! – Alex Nov 10 '17 at 04:47
  • Should have used a dictionary here. I see no reason to be slower than O(N). – jetstream96 Nov 12 '17 at 08:16
  • @jetstream96 Using a hash-based container can indeed reduce the costs to O(N), though no need for a dictionary, a set is sufficient. But it's not clear whether that is needed, depends on the actual size of N. – Walter Nov 12 '17 at 16:09
  • @Walter Yeah I understand your point. Nice to see your edit though. – jetstream96 Nov 13 '17 at 14:23
7

For fun here are two more ways, I present a slightly more efficient method with no breaks or goto. I then present a less efficient method which sorts first.

Both of these methods are simple to read and understand.

These are really just meant to show alternatives to the other answers. The first containsPairs method I have requires card values be in the range of 0 to 13 and will break if that is not true, but is very slightly more efficient than any of the other answers I've seen.

int containsPairs(const vector<int> &hand)
{
    int pairs{ 0 };
    std::vector<int> counts(14); //note requires 13 possible card values
    for (auto card : hand){
        if(++counts[card] == 2){
            ++pairs;
            counts[card] = 0;
        }
    }
    return pairs;
}

int containsPairs(const vector<int> &hand)
{
    int pairs{ 0 };

    std::sort(hand.begin(), hand.end());
    for (size_t i = 1;i < hand.size();++i){
        if(hand[i] == hand[i - 1]){
            ++i;
            ++pairs;
        }
    }
    return pairs;
}

Note: several of the other answers will treat 3 similar cards in a hand as 2 pairs. The two methods above take this into account and instead will only count 1 pair for 3 of a kind. They will treat it as 2 pairs if there are 4 similar cards.

M2tM
  • 4,415
  • 1
  • 34
  • 43
6

goto is only one problem. Another big problem is that your method is inefficient.

Your method

Your current method basically looks at the first card, iterates over the rest and look for the same value. It then goes back to the second card and compares it to the rest. This is O(n**2).

Sorting

How would you count pairs in real life? You'd probably sort the cards by value and look for pairs. If you sort efficiently, it would be O(n*log n).

Distributing

The fastest method would be to prepare 13 slots on a table and distribute the cards according to their face value. After distributing every card, you can count the cards on each slot and see if any slot holds at least 2 cards. It's O(n) and it would also detect three of a kind or four of a kind.

Sure, there's not much difference between n**2 and n when n is 5. As a bonus, the last method would be concise, easy to write and goto-free.

Eric Duminil
  • 52,989
  • 9
  • 71
  • 124
3

If you really want to avoid goto, then you can just call the function recursively, where the goto [label] line would be, passing in any variables whose state you want to save as parameters. However, I would recommend sticking to the goto.

Cpp plus 1
  • 990
  • 8
  • 25
2

I would personally put those two loops in a lambda, instead of goto would return from this lambda with indication that the loops should restart, and would call the lambda in a loop. Something like that:

auto iterate = [&hand, &pairs]() {
             {
              ... // your two loops go here, instead of goto return true
             }
             return false;
}

while (iterate());

Small addition: I do not think this is the best algorithm to find pairs of card in a deck. There are much better options for that. I rather answer the omnipresent question of how to transfer control in or out of two loops at once.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • 2
    Don't you think that this is equivalent to adding an extra outer loop (with bold assumption that compiler manages to produce no-overhead lamba)? – user7860670 Nov 09 '17 at 21:29
  • 3
    It is not an equivalent of adding an extra outer loop, because it is exactly adding of the extra outer loop. What is the problem with that? – SergeyA Nov 09 '17 at 21:36
  • 1
    Adding an extra outer loop would require you to use control variables in order to break out of the nested loop, making the code less clear. This does the opposite, making the code more clear, assuming `iterate` was given a more descriptive name, but we can leave that up to the OP. Perhaps, `findAndRemovePair`? – Benjamin Lindley Nov 09 '17 at 21:41
  • 1
    @BenjaminLindley This code contains an extra outer loop with control variable being unnamed. And loop with an empty body can hardly make code more "clear". – user7860670 Nov 09 '17 at 21:55
2

Yes you should avoid using goto here.

It is an unnecessary use of goto specifically because the algorithm does not need it. As an aside, I tend not to use goto, but I am not in staunch opposition of it like many. goto is a great tool to break nested loops or to exit a function cleanly when an interface does not support RAII.

There are a few inefficiencies with your current approach:

  • There is no reason to re-search the list from the beginning when you find a matching pair. You have already searched all prior combinations. Removing cards does not change the relative order of non-removed cards and additionally, it does not provide you any more pairs.
  • There is no need to remove items from the middle of hand. For this problem removing from the middle of an std::vector presumably representing a hand of 5 cards is not a problem. If the number of cards is large however, this can be inefficient. In problems like this you should ask yourself, does the order of elements matter? The answer is no it does not matter. We can shuffle any cards that have not already been paired and still achieve the same answer.

Here is a modified version of your code:

int countPairs(std::vector<Card> hand)
{
    int pairs{ 0 };

    for (decltype(hand.size()) i = 0; i < hand.size(); ++i)
    {
        // I assume getFace() has no side-effects and is a const
        // method of Card.  If getFace() does have side-effects
        // then this whole answer is flawed.
        const Card& c1 = hand[i];
        for (auto j = i + 1; j < hand.size(); ++j)
        {
            const Card& c2 = hand[j];
            if (c1.getFace() == c2.getFace())
            {
                // We found a matching card for card i however we
                // do not need to remove card i since we are
                // searching forward.  Swap the matching card
                // (card j) with the last card and pop it from the
                // back.  Even if card j is the last card, this
                // approach works fine.  Finally, break so we can
                // move on to the next card.
                pairs++;
                std::swap(c2, hand.back());
                hand.pop_back(); // Alternatively decrement a size variable
                break;
            }
        }
    }
    return pairs;
}

You could touch up the above approach to use iterators if desired. You could also take in a const reference std::vector and use std::reference_wrapper to re-sort the container.

For an overall better algorithm build a frequency table of each face value and its corresponding count.

1

I'd probably do it this way:

Features:

  • 3 of a kind is not a pair
  • returns a vector of cards in order of descending Face indicating which faces are pairs in the hand.

 

std::vector<Card> reduceToPair(std::vector<Card> hand)
{
    auto betterFace = [](auto&& cardl, auto&& cardr)
    {
        return cardl.getFace() > cardr.getFace();
    };

    std::sort(begin(hand), end(hand), betterFace);

    auto first = begin(hand);
    while (first != end(hand))
    {
        auto differentFace = [&](auto&& card)
        {
            return card.getFace() != first->getFace();
        };
        auto next = std::find_if(first + 1, end(hand), differentFace);
        auto dist = std::distance(first, next);
        if (dist == 2)
        {
            first = hand.erase(first + 1, next);
        }
        else
        {
            first = hand.erase(first, next);
        }
    }

    return hand;
}

usage:

pairInfo = reduceToPair(myhand);
bool hasPairs = pairInfo.size();
if (hasPairs)
{
  auto highFace = pairInfo[0].getFace();
  if (pairInfo.size() > 1) {
    auto lowFace = pairInfo[1].getFace();
  }
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
1

If sorting of cards by face is possible and allowed we can count pairs using just a single pass without erasing anything:

bool Compare_ByFace(Card const & left, Card const & right)
{
    return(left.Get_Face() < right.Get_Face());
}

size_t Count_Pairs(vector<Card> hand)
{
    size_t pairs_count{0};
    if(1 < hand.size())
    {
        sort(hand.begin(), hand.end(), &Compare_ByFace);
        auto p_card{hand.begin()};
        auto p_ref_card{p_card};
        for(;;)
        {
           ++p_card;
           if(hand.end() == p_card)
           {          
               pairs_count += static_cast< size_t >((p_card - p_ref_card) / 2);
               break;
           }
           if(p_ref_card->Get_Face() != p_card->Get_Face())
           {
               pairs_count += static_cast< size_t >((p_card - p_ref_card) / 2);
               p_ref_card = p_card;
           }
        }
    }
    return(pairs_count);
}
user7860670
  • 35,849
  • 4
  • 58
  • 84
1
#include <vector>
#include <unordered_map>
#include <algorithm>

std::size_t containsPairs(const std::vector<int>& hand)
{
    // boilerplate for more readability
    using card_t = std::decay_t<decltype(hand)>::value_type;
    using map_t = std::unordered_map<card_t, std::size_t>;

    // populate map and count the entrys with 2 occurences
    map_t occurrences;
    for (auto&& c : hand) { ++occurrences[c]; }
    return std::count_if( std::cbegin(occurrences), std::cend(occurrences), [](const map_t::value_type& entry){ return entry.second == 2; });
}
phön
  • 1,215
  • 8
  • 20
0

One problem with goto is that the labels tend to go walkies on errant refactoring. That's fundamentally why I don't like them. Personally, in your case, if you need to keep the algorithm as it is, I'd roll the goto into a recursive call:

int containsPairs(vector<Card>&/*Deliberate change to pass by reference*/hand)
{
    for (int i = 0; i < hand.size(); i++)
    {
        Card c1 = hand[i];
        for (int j = i + 1; j < hand.size(); j++)
        {
            Card c2 = hand[j];
            if (c1.getFace() == c2.getFace())
            {
                hand.erase(hand.begin() + i);
                hand.erase(hand.begin() + (j - 1));
                return 1 + containsPairs(hand); 
            }
        }
    }
    return 0;
}

The overhead in the stack frame creation is negligible cf. the std::vector manipulations. This might be impractical depending on the call site: you can no longer call the function with an anonymous temporary for example. But really there are better alternatives for pair identification: why not order the hand more optimally?

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • This changes the API by taking a reference to the hand and modifying it. The OP's code took a hand by value and modified a local copy. – Walter Nov 09 '17 at 22:09
  • @Walter: Indeed it does. I do qualify the answer with that consideration. The obvious workaround would be a calling stub, but I feel that's a step too far. – Bathsheba Nov 09 '17 at 22:10
  • Your code looks a bit like recursion for the recursion's sake. Are recursions still so popular/loved by CS people? – Walter Nov 09 '17 at 22:17
0

Are you allowed to change the order of elements in a vector? If yes, just use an adjacent_find algorithm in a single loop.

Thus you will not only get rid of goto, but get better performance (currently you have O(N^2)) and guaranteed correctness:

std::sort(hand.begin(), hand.end(), 
    [](const auto &p1, const auto &p2) { return p1.getFace() < p2.getFace(); });
for (auto begin = hand.begin(); begin != hand.end(); )
{
  begin = std::adjacent_find(begin, hand.end(), 
        [](const auto &p1, const auto &p2) { return p1.getFace() == p2.getFace(); });
  if (begin != hand.end())
  {
    auto distance = std::distance(hand.begin(), begin);
    std::erase(begin, begin + 2);  // If more than 2 card may be found, use find to find to find the end of a range
    begin = hand.begin() + distance;
  }
}
0

Your implementation is not working as it counts three of a kind as one pair, four of a kind as two.

Here is an implementation I'd suggest:

int containsPairs(std::vector<Card> hand)
{
    std::array<int, 14> face_count = {0};
    for (const auto& card : hand) {
        ++face_count[card.getFace()]; // the Face type must be implicitly convertible to an integral. You might need to provide this conversion or use an std::map instead of std::array.
    }
    return std::count(begin(face_count), end(face_count), 2);
}

(demo on coliru)

It can be generalized to count not only pairs but also n of a kind by tweaking the 2.

YSC
  • 38,212
  • 9
  • 96
  • 149
0

The other answers so far address how to fundamentally restructure your code. They make the point that your code wasn't very efficient to begin with, and by the time you fixed that you only need to break out of one loop, so you don't need the goto anyway.

But I am going to answer the question of how to avoid goto without fundamentally changing the algorithm. The answer (as is very often the case for avoiding goto) is to move part of your code into a separate function and use an early return:

void containsPairsImpl(vector<Card>& hand, int& pairs)
{
    for (int i = 0; i < hand.size(); i++)
    {
        Card c1 = hand[i];
        for (int j = i + 1; j < hand.size(); j++)
        {
            Card c2 = hand[j];
            if (c1.getFace() == c2.getFace())
            {
                pairs++;
                hand.erase(hand.begin() + i);
                hand.erase(hand.begin() + (j - 1));
                return;
            }
        }
    }
    hand.clear();
}

int containsPairs(vector<Card> hand)
{
    int pairs{ 0 };
    while (!hand.empty()) {
        containsPairsImpl(hand, pairs);
    }
    return pairs;
}

Notice that I pass hand and pairs by reference to the inner function so that they can be updated. If you have a lot of these local variables, or you have to break the function into several pieces, then this can become unwieldy. The solution then is to use a class:

class ContainsPairsTester {
public:
    ContainsPairsTester(): m_hand{}, m_pairs{0} {}

    void computePairs(vector<Card> hand);

    int pairs() const { return m_pairs; }
private:
    vector<Card> m_hand;
    int m_pairs;

    void computePairsImpl(vector<Card> hand);
};

void ContainsPairsTester::computePairsImpl()
{
    for (int i = 0; i < m_hand.size(); i++)
    {
        Card c1 = m_hand[i];
        for (int j = i + 1; j < m_hand.size(); j++)
        {
            Card c2 = m_hand[j];
            if (c1.getFace() == c2.getFace())
            {
                m_pairs++;
                m_hand.erase(m_hand.begin() + i);
                m_hand.erase(m_hand.begin() + (j - 1));
                return;
            }
        }
    }
    m_hand.clear();
}

void ContainsPairsTester::computePairs(vector<Card> hand)
{
    m_hand = hand;
    while (!m_hand.empty()) {
        computePairsImpl();
    }
}
Arthur Tacca
  • 8,833
  • 2
  • 31
  • 49
  • This solution seems to be wrong. It either returns `hand.size()/2` (which is correct), or it does an infinite loop (not what we wanted). – geza Nov 10 '17 at 13:03
  • @geza Thanks for pointing that out. I've now fixed it (I believe). – Arthur Tacca Nov 10 '17 at 13:21
0

As others have remarked, you should not only avoid goto, but also avoid writing your own code where there is a standard algorithm that can do the job. I'm surprised that no-one has suggested unique, which is designed for this purpose:

bool cardCmp(const Card& a, const Card& b) {
    return a.getFace() < b.getFace();
}

size_t containsPairs(vector<Card> hand) {
    size_t init_size = hand.size();

    std::sort(hand.begin(), hand.end(), cardCmp);
    auto it = std::unique(hand.begin(), hand.end(), cardCmp);
    hand.erase(it, hand.end());

    size_t final_size = hand.size();
    return init_size - final_size;
}

(First ever answer on StackOverflow - apologies for any faux pas!)

0

While goto isn't really that awful if you need it, it is not necessary here. Since you only care about the number of pairs, it is also not necessary to record what those pairs are. You can just xor through the entire list.

If you are using GCC or clang, the following will work. In MSVC, you can use __popcnt64() instead.

int containsPairs(vector<Card> hand)
{
    size_t counter = 0;
    for ( Card const& card : hand )
        counter ^= 1ul << (unsigned) card.getFace();

    return ( hand.size() - __builtin_popcountll(counter) ) / 2u;
}
KevinZ
  • 3,036
  • 1
  • 18
  • 26