web-dev-qa-db-ja.com

std :: setユーザー定義型、重複を防ぐ方法

だから私は特定の順序を維持する必要があるだけでなく、ユーザー定義の(私によって)タイプの重複を許可しないstd :: setを持っています。これで、タイプに「<」演算子をオーバーロードすることで、正しく機能する順序を取得できます。ただし、このセットは重複を適切に検出せず、正直なところ、これが内部的にどのように行われるのか完全にはわかりません。私は「==」演算子をオーバーロードしましたが、どういうわけかこれがセットが実際に使用しているものかどうかわかりませんか?質問は、値を追加するときにセットが重複をどのように判断するかということです。関連するコードは次のとおりです。

ユーザー定義タイプ:

//! An element used in the route calculation.
struct RouteElem {
    int shortestToHere; // Shortest distance from the start.
    int heuristic;      // The heuristic estimate to the goal.
    Coordinate position;
    bool operator<( const RouteElem& other ) const
    {
        return (heuristic+shortestToHere) < (other.heuristic+other.shortestToHere);
    }
    bool operator==( const RouteElem& other ) const
    {
        return (position.x == other.position.x && position.y == other.position.y);
    }
};

そのため、要素の位置が等しい場合、要素は同等であり、結合された機能が他の要素よりも小さい場合、要素は別の要素よりも小さくなります。ソートは機能しますが、セットは同じ位置の2つの要素を受け入れます。

58
DeusAduro

_operator==_は、_std::set_では使用されません。 !(a < b) && !(b < a)の場合、要素abは等しいと見なされます

102
Paul

std::setは、比較関数の指定をサポートします。デフォルトはlessで、operator <を使用して同等性をチェックします。カスタム関数を定義して同等性をチェックし、代わりにそれを使用できます。

std::set<RouteElem, mycomparefunction> myset; 

比較関数をソート関数から分離することはできないことに注意してください。 std::setは二分木であり、二分木の要素が特定の要素より大きくも小さくもない場合、同じ場所にある必要があります。場所検索アルゴリズムで次のようなことを行います。

if (a < b) {
    // check the left subtree
} else if (b < a) {
    // check the right subtree
} else {
    // the element should be placed here.
}
31
Mehrdad Afshari

rlbondのコンパレータは、等しいと比較する要素の挿入を妨げません。 rlbondは、std :: setがコンパレータの!compare(a,b) && !compare(b,a)を持つ2つの要素を決して含まないことを保証していると思われるため、文字の制限を考えると、コメントでこれを証明するのは難しいようです。ただし、rlbondのコンパレーターは厳密な順序を定義しないため、std :: setの有効なパラメーターではありません。

_#include <set>
#include <iostream>
#include <iterator>
#include <algorithm>

struct BrokenOrder {
    int order;
    int equality;

    public:
    BrokenOrder(int o, int e) : order(o), equality(e) {}

    bool operator<(const BrokenOrder &rhs) const {
        return order < rhs.order;
    }
    bool operator==(const BrokenOrder &rhs) const {
        return equality == rhs.equality;
    }
};

std::ostream &operator<<(std::ostream &stream, const BrokenOrder &b) {
    return stream << b.equality;
}

// rlbond's magic comparator
struct LessThan : public std::binary_function<BrokenOrder, BrokenOrder, bool> {
    bool operator()(const BrokenOrder& lhs, const BrokenOrder& rhs) const
    {
        return !(lhs == rhs) && (lhs < rhs);
    }
};

int main() {
    std::set<BrokenOrder,LessThan> s;
    for (int i = 0; i < 5; ++i) {
        s.insert(BrokenOrder(i,i));
    }
    for (int i = 0; i < 5; ++i) {
        s.insert(BrokenOrder(10-i,i));
    }
    std::copy(s.begin(), s.end(), 
        std::ostream_iterator<BrokenOrder>(std::cout, "\n"));
}
_

出力:

_0
1
2
3
4
3
2
1
0
_

複製。マジックコンパレーターが失敗しました。セット内の異なる要素はequalityの同じ値を持ち、したがって_operator==_と同じ値を比較します。これは、挿入中にセットが新しい要素とその重複を比較しないためです。除外された唯一の重複は4でした。2つの4には並べ替え順序4と6があったためです。

C++標準から:25.3:3「アルゴリズムが正しく機能するためには、compは値に厳密な弱い順序付けを誘導する必要があります」。

25.3:4 "...要件は、compとequivの両方が推移的な関係であることです。

_comp(a,b) && comp(b,c) implies comp(a,c)"
_

ここで、要素a = BrokenOrder(1,1)b = BrokenOrder(2,2)、およびc = BrokenOrder(9,1)、およびcompがもちろん魔法のコンパレータと等しいと考えてください。次に:

  • 1!= 2(等式)および1 <2(順序)であるため、comp(a,b)はtrueです。
  • 2!= 1(等式)および2 <9(順序)であるため、comp(b,c)はtrueです。
  • comp(a,c)は1 == 1(等式)なのでfalseです
7
Steve Jessop

STLセットの実装は、平等を検出するために概念的に次のようなことを行います。

_bool equal = !(a < b) && !(b < a);
_

つまり、2つの要素が両方とも他方より小さくない場合、それらは等しくなければなりません。これを確認するには、operator==()メソッドにブレークポイントを設定し、それがまったく呼び出されないかどうかを確認します。

私は一般に、完全に異なるものをチェックする比較演算子を疑います。 _<_演算子は、_==_演算子の定義方法とは別の2つの点で定義されます。通常、このような比較では一貫した情報を使用する必要があります。

4
Greg Hewgill

次のようなものを試すことができます:

_//! An element used in the route calculation.
struct RouteElem {
    int shortestToHere; // Shortest distance from the start.
    int heuristic;              // The heuristic estimate to the goal.
    Coordinate position;
    bool operator<( const RouteElem& other ) const
    {
      return (heuristic+shortestToHere) < (other.heuristic+other.shortestToHere);
    }
    bool operator==( const RouteElem& other ) const
    {
      return (position.x == other.position.x && position.y == other.position.y);
    }
};

struct CompareByPosition {
    bool operator()(const RouteElem &lhs, const RouteElem &rhs) {
        if (lhs.position.x != rhs.position.x) 
            return lhs.position.x < rhs.position.x;
        return lhs.position.y < rhs.position.y;
    }
};

// first, use std::set to remove duplicates
std::set<RouteElem,CompareByPosition> routeset;
// ... add each RouteElem to the set ...

// now copy the RouteElems into a vector
std::vector<RouteElem> routevec(routeset.begin(), routeset.end());

// now sort via operator<
std::sort(routevec.begin(), routevec.end());
_

明らかに中央にコピーがあり、遅いように見えます。ただし、2つの異なる基準でアイテムにインデックスを付ける構造には、セットと比較して、アイテムごとに何らかのオーバーヘッドが追加されます。上記のコード全体はO(n log n)であり、std :: sortの実装がintrosortを使用すると仮定しています。

持っている場合、このスキームでは、setの代わりに_unordered_set_を使用して、最初の一意化を行うことができます。ハッシュはxとyのみに依存する必要があるため、セットに挿入するために必要なO(log N)比較よりも高速である必要があります。

編集:ソート順を「維持」したいと言っていることに気づいただけで、バッチですべてを処理したいわけではありません。ごめんなさい効率的に順序を維持し、要素を追加するときに重複を除外する場合は、位置に基づいて上記で定義したセットまたは順序付けられていないセット、および_std::multiset<RouteElem>_を維持する_operator<_を使用することをお勧めします注文。新しい要素ごとに、次を実行します。

_if (routeset.insert(elem).second) {
    routemultiset.insert(elem);
}
_

ただし、これは例外を保証するものではないことに注意してください。 2番目の挿入がスローされる場合、ルートセットが変更されているため、状態の一貫性が失われます。だから私は本当にあなたが必要だと思う:

_if (routeset.insert(elem).second) {
    try {
        routemultiset.insert(elem); // I assume strong exception guarantee
    } catch(...) {
        routeset.erase(elem); // I assume nothrow. Maybe should check those.
        throw;
    }
}
_

または、RAIIと同等のもの。コード内にRAIIクラスを使用する場所が1つしかない場合はより冗長になりますが、繰り返しが多い場合はより適切になります。

2
Steve Jessop

これの影響に注意してください。 A *のようなことをしようとしているように見えます。「複製」を挿入しようとすると、「より良い」ルートがあっても無視されます。

注:このソリューションは機能しません。以下の説明を参照してください

struct RouteElem 
{
    int shortestToHere; // Shortest distance from the start.
    int heuristic;              // The heuristic estimate to the goal.
    Coordinate position;
    bool operator<( const RouteElem& other ) const
    {
        return (heuristic+shortestToHere) < (other.heuristic+other.shortestToHere);
    }
    bool operator==( const RouteElem& other ) const
    {
        return (position.x == other.position.x && position.y == other.position.y);
    }
};

struct RouteElemLessThan : public std::binary_function<RouteElem, RouteElem, bool>
{
    bool operator()(const RouteElem& lhs, const RouteElem& rhs) const
    {
        return !(lhs == rhs) && (lhs < rhs);
    }
};

std::set<RouteElem, RouteElemLessThan> my_set;
0
rlbond