web-dev-qa-db-ja.com

最初に値で、次にキーでstd :: mapをソートするにはどうすればよいですか?

std::mapを値で、次にキーでソートする必要があります。マップには次のようなデータが含まれます。

  1  realistically
  8         really
  4         reason
  3     reasonable
  1     reasonably
  1     reassemble
  1    reassembled
  2      recognize
 92         record
 48        records
  7           recs

私は値を順番に取得する必要がありますが、キッカーは値が順番に並んだ後にキーがアルファベット順になる必要があるということです。これどうやってするの?

38
Trevor Hutto

std::mapは、要素をkeysでソートします。ソート時にvaluesを気にしません。

std::vector<std::pair<K,V>>を使用してから、std::sortに続いてstd::stable_sortを使用して並べ替えることができます。

std::vector<std::pair<K,V>> items;

//fill items

//sort by value using std::sort
std::sort(items.begin(), items.end(), value_comparer);

//sort by key using std::stable_sort
std::stable_sort(items.begin(), items.end(), key_comparer);

最初のソートは、nlog(n)であるためstd::sortを使用し、最悪の場合はn(log(n))^2であるstd::stable_sortを使用する必要があります。

パフォーマンス上の理由でstd::sortが選択されていますが、正しい順序付けにはstd::stable_sortが必要であり、値による順序を保持する必要があることに注意してください。


コメントに記載されている@gsfでは、最初にvaluesを比較する比較器を選択し、等しい場合はkeysをソートする場合、onlystd::sortを使用できます。

auto cmp = [](std::pair<K,V> const & a, std::pair<K,V> const & b) 
{ 
     return a.second != b.second?  a.second < b.second : a.first < b.first;
};
std::sort(items.begin(), items.end(), cmp);

それは効率的でなければなりません。

しかし、待ってください、より良いアプローチがあります:std::pair<V,K>の代わりにstd::pair<K,V>を格納し、比較器をまったく必要としません-std::pairの標準比較器で十分です。 first(これはV)最初にsecondK):

std::vector<std::pair<V,K>> items;
//...
std::sort(items.begin(), items.end());

それはうまくいくはずです。

60
Nawaz

std::setの代わりにstd::mapを使用できます。

キーと値の両方をstd::pairに保存でき、コンテナのタイプは次のようになります。

std::set< std::pair<int, std::string> > items;

std::setは、元のキーとstd::mapに格納された値の両方で値をソートします。

15
ks1322

Nawazのanswer で説明されているように、std::mapは並べ替えるので、マップを必要に応じて並べ替えることはできませんキーのみに基づいた要素。そのため、別のコンテナが必要ですが、マップに固執する必要がある場合は、そのコンテンツを(一時的に)別のデータ構造にコピーできます。

最善の解決策は、ks1322'sanswer に示されているように、反転したキーと値のペアを格納するstd::setを使用することだと思います=。 std::setはデフォルトでソートされ、 ペアの順序 は必要なとおりです:

3)lhs.first<rhs.firstの場合、trueを返します。それ以外の場合、rhs.first<lhs.firstの場合、falseを返します。それ以外の場合、lhs.second<rhs.secondの場合、trueを返します。それ以外の場合は、falseを返します。

この方法では、追加の並べ替え手順を必要とせず、結果のコードは非常に短くなります。

std::map<std::string, int> m;  // Your original map.
m["realistically"] = 1;
m["really"]        = 8;
m["reason"]        = 4;
m["reasonable"]    = 3;
m["reasonably"]    = 1;
m["reassemble"]    = 1;
m["reassembled"]   = 1;
m["recognize"]     = 2;
m["record"]        = 92;
m["records"]       = 48;
m["recs"]          = 7;

std::set<std::pair<int, std::string>> s;  // The new (temporary) container.

for (auto const &kv : m)
    s.emplace(kv.second, kv.first);  // Flip the pairs.

for (auto const &vk : s)
    std::cout << std::setw(3) << vk.first << std::setw(15) << vk.second << std::endl;

出力:

  1  realistically
  1     reasonably
  1     reassemble
  1    reassembled
  2      recognize
  3     reasonable
  4         reason
  7           recs
  8         really
 48        records
 92         record

イデオンのコード

注: C++ 17 から 範囲ベースのforループ構造化バインディング とともに使用して、マップを反復処理できます。その結果、マップをコピーするためのコードはさらに短くなり、読みやすくなります。

for (auto const &[k, v] : m)
    s.emplace(v, k);  // Flip the pairs.
2
honk

std::mapは、定義した述語を使用して値を既に並べ替えているか、指定しない場合はstd::lessを使用して値を並べ替えます。 std::setは、定義コンパレータの順序でアイテムも保存します。ただし、セットもマップも、複数のキーを持つことはできません。データ構造のみを使用してこれを達成する場合は、std::map<int,std::set<string>を定義することをお勧めします。また、文字列のstd::lessがアルファベット順ではなく辞書順でソートされることも理解する必要があります。

1
rerun

編集:他の2つの答えは良い点を示しています。私はあなたが他の構造にそれらを注文するか、それらを印刷することを望むと仮定しています。

「最高」とは、さまざまなことを意味します。 「最も簡単」、「最速」、「最も効率的」、「最も少ないコード」、「最も読みやすい」という意味ですか?

最も明白なアプローチは、2回ループすることです。最初のパスで、値を順序付けます:

if(current_value > examined_value)
{
    current_value = examined_value
    (and then swap them, however you like)
}

次に、2番目のパスで、値が一致する場合にのみ、単語をアルファベット順にします。

if(current_value == examined_value)
{
    (alphabetize the two)
}

厳密に言えば、これは「バブルソート」であり、スワップを行うたびに最初からやり直す必要があるため低速です。スワップを行わずにリスト全体を通過すると、1つの「パス」が終了します。

他の並べ替えアルゴリズムもありますが、原則は同じです。値で並べ替えてからアルファベット順にします。

0
Matt