Сортировка std:: map по значению перед выходом и уничтожением

Я знаю, что карта не готова к сортировке, ее сильно оптимизирован для быстрого и случайного доступа к ключам. И фактически не поддерживает std:: sort.

Моя текущая проблема в том, что у меня есть полный

 map<std::string,int>

который я больше не буду использовать, мне просто нужно извлечь 10 пар в значение (int) и уничтожить его.

Лучше всего, если бы это было возможно, было бы сортировать его на месте, а затем повторять его 10 раз, но это, по-видимому, не является решением.

Я пытаюсь использовать разные решения, проходя через мультимап (чтобы дублировать ключи), но я хотел бы знать, есть ли более элегантное решение, используя алгоритмы stl, насколько это возможно.

EDIT:

Я использую карту, потому что в 99% случаев мне это нужно как карта, быстрый поиск ключей для увеличения значений. Просто нужен хороший способ последующего извлечения в стоимостном порядке, когда мне больше не нужна карта.

Текущий подход:

  • std:: скопировать карту (std::string, int) в вектор (пара (std::string, int))
  • сортировать вектор
  • получить первые 10 значений
  • уничтожить вектор и карту

Ответ 1

Карты хранятся в виде дерева, отсортированного по порядку. Вы хотите получить 10 самых маленьких (или наибольших) целочисленных значений и их ключи?

В этом случае выполните итерацию карты и поместите все пары ключ-значение в вектор пар (std::vector<std::pair<std::string, int> >)). Я думаю, что для этого можно просто использовать конструктор с двумя итераторами-аргументами std::vector. Затем используйте std::partial_sort на векторе. Укажите компаратор для partial_sort, который сравнивает пары, просто сравнивая значение int, игнорируя ключевую строку. Тогда у вас есть 10 пар, которые вы хотите в начале вектора, а остальная часть вектора содержит остальные пары в неуказанном порядке.

Код (непроверенный):

typedef std::pair<std::string, int> mypair;

struct IntCmp {
    bool operator()(const mypair &lhs, const mypair &rhs) {
        return lhs.second < rhs.second;
    }
};


void print10(const std::map<std::string,int> &mymap) {
    std::vector<mypair> myvec(mymap.begin(), mymap.end());
    assert(myvec.size() >= 10);
    std::partial_sort(myvec.begin(), myvec.begin() + 10, myvec.end(), IntCmp());

    for (int i = 0; i < 10; ++i) {
        std::cout << i << ": " << myvec[i].first 
            << "-> " << myvec[i].second << "\n";
    }
}

Обратите внимание, что если есть несколько строк с одинаковым значением, с каждой стороны от 10, то он не определяет, какие из них вы получите. Вы можете контролировать это, если ваш компаратор также смотрит на строку, в тех случаях, когда целые числа равны.

Ответ 2

Для итерации по значению вы можете использовать boost:: multi_index. Он будет выглядеть следующим образом:

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/hashed_index.hpp>
using namespace boost::multi_index;

struct X {
  X( std::string val_str, int val_int ) : val_str(val_str), val_int(val_int) {};
  std::string val_str;
  int         val_int;
};

typedef multi_index_container<
    X,
    indexed_by<
        hashed_unique< member<X, std::string, &X::val_str> >,
        ordered_non_unique< member<X, int, &X::val_int> >
    >
> X_map;

void func()
{
   X_map data;
   data.insert( X("test", 1) );
   // ...

   // search by val_str 
   // complexity is equal to O(1) for hashed index (worst cast O(n) ), 
   // and O(log n) for ordered index
   X_map::const_iterator it = data.find( "test" );
   // ...

   // iterate in order of val_int
   size_t N = 0;
   for ( X_map::nth_index<1>::type::const_iterator it = data.get<1>().begin(); N < 10 && it != data.get<1>().end(); ++it, ++N ) {
     // copy elements somewhere
   }
}

Вы можете использовать любой индекс для итерации (val_str или val_int).

Ответ 3

Если вы итерации используете итератор карты, вы получите элементы, отсортированные по ключу, так как он внутренне использует сбалансированное двоичное дерево для хранения значений. Таким образом, вы можете просто извлечь из него 10 значений с помощью итераторов. Это то, что вы хотите или хотите сделать что-то еще? Просьба уточнить.

EDIT: Вместо использования вектора и сортировки вы можете напрямую использовать set и передавать функцию сравнения. Затем вы можете извлечь 10 лучших элементов. Это мой тестовый код:

typedef std::pair<std::string, int> MyPair;


struct MyTestCompare
{

    bool operator()(const MyPair& firstPair, const MyPair& secondPair) const
    {
        return firstPair.second < secondPair.second;
    }
};

int main()
{
    std::map<std::string, int> m;
    m[std::string("1")] = 10;   
m[std::string("2")] = 40;
m[std::string("3")] = 30;
m[std::string("4")] = 20;



    std::set<MyPair,MyTestCompare> s;
    std::map<std::string, int>::iterator iter = m.begin();
    std::map<std::string, int>::iterator endIter = m.end();
    for(; iter != endIter; ++iter)
    {
        s.insert(*iter);
    }

}

Ответ 4

Может быть, не самый элегантный способ, но вы можете сортировать их по значению в наборе как:

#include <map>
#include <set>
#include <iostream>
#include <string>

using namespace std;

struct sortPairSecond
{
   bool operator()(const pair<string, int> &lhs, const pair<string, int> &rhs)
   {
       return lhs.second < rhs.second;
   }
};


int main (int argc, char *argv[])
{
    cout << "Started...\n";
    map<string, int> myMap;
    myMap["One"]   = 1;
    myMap["Ten"]   = 10;
    myMap["Five"]  = 5;
    myMap["Zero"]  = 0;
    myMap["Eight"] = 8;


    cout << "Map Order:\n---------------\n";
    set<pair<string,int>, sortPairSecond > mySet;
    for(map<string, int>::const_iterator it = myMap.begin(); it != myMap.end(); ++it)
    {
        cout << it->first << " = " << it->second << "\n";
        mySet.insert(*it);
    }

    cout << "\nSet Order:\n--------------\n";
    for(set<pair<string, int> >::const_iterator it = mySet.begin(); it != mySet.end(); ++it)
    {
        cout << it->first << " = " << it->second << "\n";
    }

    return 1;
}


Ответ 5

Другая возможность - создать обратную карту. Для вас это будет std::map<int, std::string>. Записи на обратной карте сортируются по их значению.

В таких случаях я могу использовать следующее:

template< typename TK, typename TV, class TP, class TA, typename T1, typename T2 >
inline void asserted_insert(std::map<TK,TV,TP,TA>& m, const T1& k, const T2& v)
{
  typedef std::map<TK,TV,TP,TA>                   map_type;
  typedef typename map_type::value_type           value_type;
  assert( m.insert(value_type(k,v)).second );
}

template< class TMap > struct reverse_map;
template< typename T1, typename T2 > struct reverse_map< std::map<T1,T2> > {
  typedef std::map<T2,T1>                         result_t;
};

template< typename T1, typename T2, class TP1, class TA1, class TP2, class TA2 >
inline void build_reverse_map(const std::map<T1,T2,TP1,TA1>& map, std::map<T2,T1,TP2,TA2>& reverse_map)
{
  typedef std::map<T1,T2,TP1,TA1>                 map_type;

  for( typename map_type::const_iterator it=map.begin(),
                                        end=map.end(); it!=end; ++it ) {
    asserted_insert( reverse_map, it->second, it->first );
  }
}

Этот код предполагает, что значения тоже уникальны (и выдает утверждение, если это не так). Если это не относится к вашей проблеме, вы можете легко изменить код для использования нескольких карт.