web-dev-qa-db-ja.com

ここでgotoを使用しないでください。もしそうなら、どのように?

私は手を取り、ペアをチェックする関数をコーディングしています:

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

行10でペアが見つかったら、ペアを見つけた手のカードを削除し、削除されたカードでループ全体を再起動して、2番目のペアがあればそれを探します。私にとって、gotoはこれを行う最も直感的な方法でしたが、この場合、それは本当ですか?

26
Alex

これを試して:

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

これはほとんどあなたのバージョンですが、唯一の違いはgotoの代わりにi--; break;。このバージョンは、ダブルループを1回しか実行しないため、お客様のバージョンよりも効率的です。

もっと明確ですか?まあ、それは個人的な好みです。私はgotoに反対ではありません。現在の「使用しない」ステータスを修正する必要があると思います。 gotoが最適なソリューションである場合があります。


もう1つ、さらに簡単なソリューションを示します。

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

基本的に、ペアを見つけると、only遠くのカードを削除し、ループを中断します。そのため、iに注意する必要はありません。

27
geza

(わずかに)高速なアルゴリズムは、gotoも回避します

_std::vector_からの消去は決して高速ではないため、避けてください。 _std::vector_をコピーする場合も同様です。両方を避けることで、gotoも避けることができます。例えば

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

O(N ^ 2)なので、大きなハンドの場合、このアルゴリズムはまだ低速です。並べ替えはより高速で、その後はペアが隣接するカードである必要があり、O(N logN)アルゴリズムが提供されます。

しかし、より高速なO(N)は、ペアのカードではなく、他のすべてのカードに_unordered_set_を使用することです。

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

hand.size()が十分に小さい場合、sizeof(Card)および/またはそのコンストラクターのコストによっては、上記のコードよりも高速ではない場合があります。同様のアプローチは、 Eric Duminil's answer で提案されているようにdistributionを使用することです。

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

もちろん、ハッシュが不要なときにCardを小さな整数に簡単にマッピングできれば、これらのメソッドをより簡単に実装できます。

21
Walter

ここでの楽しみのために、さらに2つの方法があります。中断も後戻りもない、少し効率的な方法を示します。次に、最初にソートする効率の悪いメソッドを紹介します。

これらの方法は両方とも、読みやすく理解しやすいです。

これらは実際には、他の回答の代替案を示すことを目的としています。私が持っている最初のcontainsPairsメソッドは、0から13の範囲のカード値を必要とし、そうでない場合は壊れますが、私が見た他の答えよりもわずかに効率的です。

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

注:他の回答のいくつかは、手にある3枚の同様のカードを2ペアとして扱います。上記の2つの方法はこれを考慮に入れ、代わりに3種類の1ペアのみをカウントします。同様のカードが4枚ある場合、2ペアとして扱います。

7
M2tM

gotoは唯一の問題です。もう1つの大きな問題は、メソッドが非効率的であることです。

あなたの方法

現在のメソッドは、基本的に最初のカードを調べ、残りを繰り返して同じ値を探します。次に、2番目のカードに戻り、残りのカードと比較します。これはO(n**2)です。

仕分け

実生活でペアをどのように数えますか?あなたはおそらく値でカードをソートし、ペアを探します。効率的にソートする場合、O(n*log n)になります。

配布中

最速の方法は、テーブルに13個のスロットを用意し、額面に従ってカードを配布することです。すべてのカードを配布した後、各スロットのカードを数え、少なくとも2つのカードを保持するスロットがあるかどうかを確認できます。 O(n)であり、3種類または4種類も検出します。

確かに、nがn**2の場合、5nの間に大きな違いはありません。おまけとして、最後の方法は簡潔で、書きやすく、goto- freeです。

6
Eric Duminil

本当にgotoを避けたい場合は、goto [label]行のある場所で関数を再帰的に呼び出して、状態をパラメーターとして保存する変数を渡すことができます。ただし、gotoにこだわることをお勧めします。

3
Cpp plus 1

私は個人的にこれらの2つのループをラムダに配置し、gotoの代わりにこのラムダから戻り、ループを再開する必要があることを示し、ラムダをループで呼び出します。そんな感じ:

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

while (iterate());

小加算:これはデッキでカードのペアを見つけるのに最適なアルゴリズムではないと思います。そのためのより良いオプションがあります。私はむしろ、一度に2つのループに制御を移したり、2つのループから制御を移したりするという遍在する質問に答えます。

2
SergeyA

はい、ここではgotoの使用を避ける必要があります。

アルゴリズムがそれを必要としないため、それはgotoの不必要な使用です。余談ですが、私はgotoを使用しない傾向がありますが、多くの人のようにそれとは反対しません。 gotoは、インターフェイスがRAIIをサポートしていない場合に、ネストされたループを解除したり、関数をきれいに終了したりするための優れたツールです。

現在のアプローチにはいくつかの非効率性があります。

  • 一致するペアが見つかったときに、リストを最初から再検索する理由はありません。すでに以前のすべての組み合わせを検索しました。カードを削除しても、削除されていないカードの相対的な順序は変更されません。また、ペアを追加することもできません。
  • handの中央からアイテムを削除する必要はありません。この問題では、おそらく[5枚のカードのハンドを表すstd::vectorの途中から削除することは問題ではありません。ただし、カードの数が多い場合、これは非効率的です。このような問題では、要素の順序は重要ですか?答えはいいえ、それは問題ではありません。まだペアになっていないカードをシャッフルしても、同じ答えが得られます。

コードの修正バージョンは次のとおりです。

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

必要に応じて、上記のアプローチを修正してイテレータを使用できます。 const参照std::vectorを受け取り、 std::reference_wrapper を使用してコンテナを再ソートすることもできます。

全体的なアルゴリズムを改善するために、各顔の値とそれに対応するカウントの頻度表を作成します。

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

私はおそらく次のようにします:

特徴:

  • 種類の3はペアではありません
  • どの顔が手のペアであるかを示すFaceの降順でカードのベクトルを返します。

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

使用法:

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

顔によるカードのソートが可能であり、許可されている場合は、何も消去せずに1回のパスでペアをカウントできます。

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

gotoに伴う問題の1つは、ラベルが誤ったリファクタリングを行う傾向があることです。 それが基本的に私はそれらが好きではない理由。個人的に、あなたの場合、アルゴリズムをそのまま維持する必要がある場合、gotoを再帰呼び出しにロールします。

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

スタックフレーム作成のオーバーヘッドはごくわずかです。 std::vector操作。これは、呼び出しサイトによっては実用的ではない場合があります。たとえば、匿名テンポラリを使用して関数を呼び出すことはできなくなります。しかし、実際にはペアを識別するためのより良い選択肢があります:手を最適に注文しないのはなぜですか?

0
Bathsheba

他の人が言ったように、gotoを避けるだけでなく、仕事をすることができる標準的なアルゴリズムがある場所に独自のコードを書くことも避けるべきです。私は誰もこの目的のために設計されたユニークなものを提案していないことに驚いています:

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

(StackOverflowの最初の回答-偽物の謝罪!)

0
James Raynard

これまでのその他の回答は、コードを根本的に再構築する方法を扱っています。それらは、あなたのコードがそもそもあまり効率的ではなかったと指摘し、修正するまでに、1つのループから抜けるだけでよいので、とにかくgotoは必要ありません。

しかし、アルゴリズムを根本的に変更せずにgotoを回避する方法の質問に答えます。 (gotoを避けるためによくあることですが)答えは、コードの一部を別の関数に移動し、初期の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;
}

handpairsを内部関数への参照によって渡し、更新できるようにします。これらのローカル変数が多数ある場合、または関数をいくつかの部分に分割する必要がある場合、これは扱いにくいものになります。解決策は、クラスを使用することです:

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

実装は機能しません。3種類は1ペア、4種類は2ペアとしてカウントされます。

私が提案する実装​​は次のとおりです。

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

coluruのデモ

ペアだけでなく、[n of a kind]をカウントすることは、2を微調整することで一般化できます。

0
YSC

ベクター内の要素の順序を変更できますか?はいの場合、単一ループで_adjacent_find_アルゴリズムを使用します。

したがって、gotoを取り除くだけでなく、パフォーマンスが向上し(現在、O(N^2)があります)、正確性が保証されます。

_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

gotoは必要な場合それほどひどくはありませんが、ここでは必要ありません。ペアの数だけを気にするので、それらのペアが何であるかを記録する必要もありません。リスト全体をxorすることができます。

GCCまたはclangを使用している場合、以下が機能します。 MSVCでは、代わりに__popcnt64()を使用できます。

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