it-swarm.com.de

Sollte ich vermeiden, hier zu gehen? Wenn das so ist, wie?

Ich codiere für eine Funktion, die eine Hand nimmt und nach Paaren sucht:

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

Wenn es ein Paar in Zeile 10 findet, möchte ich die Karten in der Hand löschen, mit der es das Paar gefunden hat, und dann die gesamte Schleife mit den gelöschten Karten erneut starten, um ein zweites Paar zu finden, falls vorhanden. Für mich war "Gehe zu" der intuitivste Weg, dies zu tun, aber in diesem Fall ist das wahr?

26
Alex

Versuche dies:

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

Dies ist fast Ihre Version, der einzige Unterschied ist, dass anstelle von goto i--; break; vorhanden ist. Diese Version ist effizienter als Ihre, da die Doppelschleife nur einmal ausgeführt wird.

Ist das klarer? Nun, das ist eine persönliche Präferenz. Ich bin überhaupt nicht gegen goto, ich denke, der aktuelle Status "Nie verwenden" sollte überarbeitet werden. Es gibt Gelegenheiten, bei denen goto die beste Lösung ist.


Hier ist eine andere, noch einfachere Lösung:

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

Wenn ein Paar gefunden wird, wird im Grunde genommen nur die weitere Karte entfernt und die Schleife unterbrochen. Es ist also nicht nötig, mit i knifflig zu sein.

27
geza

Ein (etwas) schnellerer Algorithmus vermeidet auch die goto.

Das Löschen aus einem std::vector ist nie schnell und sollte vermieden werden. Dasselbe gilt für das Kopieren eines std::vector. Indem Sie beides vermeiden, vermeiden Sie auch die goto. Zum Beispiel

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

Für große Hände ist dieser Algorithmus immer noch langsam, da O (N ^ 2). Schneller wäre das Sortieren, nach dem Paare benachbarte Karten sein müssen, was einen O (N logN) -Algorithmus ergibt.

Schneller, O(N), ist die Verwendung eines unordered_set nicht für die paarweisen Karten, sondern für alle anderen Karten:

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

Für ausreichend kleine hand.size() ist dies möglicherweise nicht schneller als der obige Code, abhängig von sizeof(Card) und/oder den Kosten seines Konstruktors. Ein ähnlicher Ansatz ist die Verwendung von distribution, wie in Eric Duminils Antwort vorgeschlagen. _:

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

Natürlich können diese Methoden viel einfacher implementiert werden, wenn Card trivial in eine kleine ganze Zahl abgebildet werden kann, wenn kein Hash erforderlich ist.

21
Walter

Zum Spaß gibt es zwei weitere Möglichkeiten: Ich präsentiere eine etwas effizientere Methode ohne Unterbrechungen oder Weiterleitungen. Ich präsentiere dann eine weniger effiziente Methode, die zuerst sortiert.

Beide Methoden sind einfach zu lesen und zu verstehen.

Diese sind eigentlich nur dazu gedacht, Alternativen zu den anderen Antworten aufzuzeigen. Die erste "includePairs" -Methode, die ich habe, setzt voraus, dass die Kartenwerte im Bereich von 0 bis 13 liegen und brechen, wenn dies nicht der Fall ist, jedoch etwas effizienter als alle anderen Antworten, die ich gesehen habe.

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

Hinweis: Bei einigen anderen Antworten werden 3 ähnliche Karten in einer Hand als 2 Paare behandelt. Die beiden oben genannten Methoden berücksichtigen dies und zählen stattdessen nur 1 Paar für 3 Gleiche. Sie werden es als 2 Paare behandeln, wenn 4 ähnliche Karten vorhanden sind.

7
M2tM

goto ist nur ein Problem. Ein weiteres großes Problem ist, dass Ihre Methode ineffizient ist.

Deine Methode

Ihre aktuelle Methode betrachtet grundsätzlich die erste Karte, iteriert den Rest und sucht nach dem gleichen Wert. Dann geht es zurück zur zweiten Karte und vergleicht sie mit der restlichen Karte. Dies ist O(n**2).

Sortierung

Wie würdest du Paare im wirklichen Leben zählen? Sie würden die Karten wahrscheinlich nach Wert sortieren und nach Paaren suchen. Wenn Sie effizient sortieren, wäre dies O(n*log n).

Verteilen

Die schnellste Methode wäre, 13 Slots auf einem Tisch vorzubereiten und die Karten entsprechend ihrem Nennwert zu verteilen. Nachdem Sie jede Karte verteilt haben, können Sie die Karten in jedem Steckplatz zählen und feststellen, ob ein Steckplatz mindestens 2 Karten enthält. Es ist O(n) und würde auch drei oder vier von einer Art erkennen.

Sicher, es gibt keinen großen Unterschied zwischen n**2 und n, wenn n 5 ist. Als Bonus wäre die letzte Methode kurz, einfach zu schreiben und goto-frei.

6
Eric Duminil

Wenn Sie den goto-Befehl wirklich vermeiden möchten, können Sie einfach die Funktion rekursiv aufrufen, in der sich die goto-Zeile [label] befinden würde, und dabei alle Variablen übergeben, deren Status Sie als Parameter speichern möchten. Ich würde jedoch empfehlen, sich an den goto zu halten.

3
Cpp plus 1

Ich würde diese beiden Schleifen persönlich in ein Lambda legen, statt goto würde von diesem Lambda mit dem Hinweis zurückkehren, dass die Schleifen neu starten sollten, und das Lambda in einer Schleife aufrufen würde. Sowas in der Art:

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

while (iterate());

Kleiner Zusatz : Ich glaube nicht, dass dies der beste Algorithmus ist, um Kartenpaare in einem Stapel zu finden. Dafür gibt es viel bessere Möglichkeiten. Ich beantworte lieber die allgegenwärtige Frage, wie man die Kontrolle in oder aus zwei Schleifen gleichzeitig überträgt.

2
SergeyA

Ja, Sie sollten es vermeiden, hier goto zu verwenden.

Dies ist eine unnötige Verwendung von goto, insbesondere weil der Algorithmus dies nicht benötigt. Abgesehen davon, neige ich nicht dazu, goto zu verwenden, aber ich stehe nicht wie viele in festem Gegensatz dazu. goto ist ein hervorragendes Werkzeug, um verschachtelte Schleifen zu unterbrechen oder eine Funktion sauber zu beenden, wenn eine Schnittstelle RAII nicht unterstützt.

Bei Ihrem derzeitigen Ansatz gibt es einige Ineffizienzen:

  • Es gibt keinen Grund, die Liste von Anfang an erneut zu durchsuchen, wenn Sie ein übereinstimmendes Paar finden. Sie haben bereits alle vorherigen Kombinationen gesucht. Durch das Entfernen von Karten wird die relative Reihenfolge nicht entfernter Karten nicht geändert. Außerdem erhalten Sie keine weiteren Paare.
  • Es ist nicht notwendig, Elemente aus der Mitte von hand zu entfernen. Bei diesem Problem ist das Entfernen aus der Mitte eines std::vector, der vermutlich eine Hand mit 5 Karten darstellt, kein Problem. Wenn die Anzahl der Karten jedoch groß ist, kann dies ineffizient sein. Bei solchen Problemen sollten Sie sich fragen, spielt die Reihenfolge der Elemente eine Rolle? Die Antwort ist nein, es spielt keine Rolle. Wir können Karten, die noch nicht gepaart wurden, mischen und trotzdem die gleiche Antwort erzielen.

Hier ist eine modifizierte Version Ihres Codes:

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

Falls gewünscht, können Sie den obigen Ansatz zur Verwendung von Iteratoren verbessern. Sie könnten auch eine const-Referenz std::vector aufnehmen und std::reference_wrapper verwenden, um den Container neu zu sortieren.

Erstellen Sie für einen insgesamt besseren Algorithmus eine Häufigkeitstabelle für jeden Gesichtswert und die entsprechende Anzahl.

2
twohundredping
#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; });
}
1
phön

Ich würde es wahrscheinlich so machen:

Eigenschaften: 

  • 3 von einer Art ist kein Paar
  • gibt einen Kartenvektor in der Reihenfolge der absteigenden Flächen zurück, um anzuzeigen, welche Flächen Paare in der Hand sind.

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

verwendungszweck:

pairInfo = reduceToPair(myhand);
bool hasPairs = pairInfo.size();
if (hasPairs)
{
  auto highFace = pairInfo[0].getFace();
  if (pairInfo.size() > 1) {
    auto lowFace = pairInfo[1].getFace();
  }
}
1
Richard Hodges

Wenn Karten nach Gesicht sortiert werden können und zulässig sind, können wir Paare mit einem einzigen Durchlauf zählen, ohne etwas zu löschen:

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);
}
1
VTT

Ein Problem bei goto ist, dass die Etiketten bei fehlerhaftem Refactoring tendenziell begehbar sind. Das ist Grund warum ich sie nicht mag. Wenn Sie den Algorithmus beibehalten möchten, würde ich in Ihrem Fall die Variable goto in einen rekursiven Aufruf rollen:

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

Der Aufwand bei der Stack-Frame-Erstellung ist vernachlässigbar, vgl. die std::vector-Manipulationen. Dies kann je nach Anrufstelle unpraktisch sein: Sie können die Funktion beispielsweise nicht mehr mit einem anonymen temporären Aufruf aufrufen. Aber es gibt wirklich bessere Alternativen für die Paaridentifikation: Warum sollte man die Hand nicht optimaler bestellen?

0
Bathsheba

Wie andere bereits erwähnt haben, sollten Sie nicht nur das Gehto vermeiden, sondern auch keinen eigenen Code schreiben, bei dem ein Standardalgorithmus die Aufgabe erfüllt. Ich bin überrascht, dass niemand Unique vorgeschlagen hat, das zu diesem Zweck entwickelt wurde:

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

(Erste Antwort auf StackOverflow - Entschuldigung für alle faux pas!)

0
James Raynard

Die anderen Antworten beziehen sich auf die grundlegende Umstrukturierung Ihres Codes. Sie weisen darauf hin, dass Ihr Code anfangs nicht sehr effizient war, und zu der Zeit, zu der Sie das Problem gelöst haben, dass Sie nur aus einer Schleife ausbrechen müssen, benötigen Sie goto sowieso nicht.

Aber ich werde die Frage beantworten, wie goto vermieden werden kann, ohne den Algorithmus grundlegend zu ändern. Die Antwort (wie häufig zur Vermeidung von goto) ist, einen Teil Ihres Codes in eine separate Funktion zu verschieben und eine frühe return zu verwenden:

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

Beachten Sie, dass ich hand und pairs als Referenz an die innere Funktion übergeben, damit sie aktualisiert werden können. Wenn Sie viele dieser lokalen Variablen haben oder die Funktion in mehrere Teile aufteilen müssen, kann dies unhandlich werden. Die Lösung ist dann eine Klasse zu verwenden:

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();
    }
}
0
Arthur Tacca

Ihre Implementierung funktioniert nicht, da sie drei Gleiche als ein Paar und vier Gleiche als zwei zählt.

Hier ist eine Implementierung, die ich vorschlagen würde:

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 auf coliru )

Es kann verallgemeinert werden, nicht nur Paare, sondern auch n einer Art zu zählen, indem der 2 angepasst wird.

0
YSC

Dürfen Sie die Reihenfolge der Elemente in einem Vektor ändern? Wenn ja, verwenden Sie einfach einen adjacent_find-Algorithmus in einer einzelnen Schleife. 

So werden Sie nicht nur goto los, sondern auch eine bessere Leistung (derzeit haben Sie O(N^2)) und garantierte Richtigkeit

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

Während goto nicht wirklich schrecklich ist, wenn Sie es brauchen, ist es hier nicht notwendig. Da Ihnen nur die Anzahl der Paare wichtig ist, ist es nicht erforderlich, aufzuzeichnen, was diese Paare sind. Sie können nur xor durch die gesamte Liste gehen.

Wenn Sie GCC oder Clang verwenden, funktioniert Folgendes. In MSVC können Sie stattdessen __popcnt64() verwenden.

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