2009-11-24 2 views
1

Я пытаюсь использовать STL для решения следующей проблемы (я не хочу реализовывать свою собственную структуру данных, если мне это не нужно). Я придумал рабочую реализацию, но я надеюсь, что есть что-то быстрее ... или какой код у меня есть лучший способ сделать это?Сортировка std :: map с помощью другого ключа?

У меня есть большой набор данных, в котором каждая запись содержит два элемента: ключ и размер. В наборе данных есть несколько записей с одним и тем же ключом. Мне нужно знать: для каждого ключа, сколько из этих ключей находится в наборе данных, а для каждого ключа - общий размер их. Например, если этот набор данных (ключ, размер):

(1, 3) 
(3, 27) 
(7, 7) 
(3, 2) 
(1, 1) 

Я хочу, чтобы сгенерировать этот вывод, отсортированный по размеру по возрастанию:

Key 1: Size 4, Count 2 
Key 7: Size 7, Count 1 
Key 3: Size 29, Count 2 

Поскольку набор данных полностью несортированный, я первая потребность для объединения ключей для подсчета их и суммирования размера. Затем мне нужно применить эту структуру данных к общему размеру для получения конечного результата. Это код, который я придумал, чтобы выполнить задачу, используя зЬй :: Карты и зЬй :: вектора:

struct Node 
{ 
    int Size; 
    int Count; 

    Node() 
     : Size(0), Count(0) 
    { 
    } 

    Node(int size) 
     : Size(size), Count(1) 
    { 
    } 
}; 

void map_insert(std::map<int, Node> &map, int key, int size) 
{ 
    std::map<int, Node>::iterator itr = map.find(key); 

    if (itr != map.end()) 
    { 
     itr->second.Count++; 
     itr->second.Size += size; 
    } 
    else 
    { 
     map[key] = Node(size); 
    } 
} 

bool compare(const std::pair<int, Node> &a1, const std::pair<int, Node> &a2) 
{ 
    return a1.second.Size < a2.second.Size; 
} 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    std::map<int, Node> _map; 

    map_insert(_map, 1, 3); 
    map_insert(_map, 3, 27); 
    map_insert(_map, 7, 7); 
    map_insert(_map, 3, 2); 
    map_insert(_map, 1, 1); 

    std::vector<std::pair<int, Node>> v(_map.begin(), _map.end()); 
    std::sort(v.begin(), v.end(), compare); 

    return 0; 
} 

Минус выходной код, это дает правильный вид. Я ненавижу использование двух отдельных структур данных, но, похоже, не существует способа «прибегнуть» к дереву на основе другого ключа. Существуют ли какие-то грубые неэффективности, которых я могу избежать? Может ли кто-нибудь подумать о лучшем способе сделать это?

Обратите внимание, что я предполагаю, что использование экземпляров Node (вместо указателей Node) будет быстрее, чем new'ing и delete'ing каждого узла, который используется здесь. Является ли это разумным предположением или вы считаете, что новое/удаление будет быстрее, чем копирование этих небольших структур?

Редактировать: Интересно, что я никогда не знала о мультимапе, но используя реализацию, представленную ниже (спасибо Naveen), похоже, что Multimap хуже работает. (Обратите внимание, мое намерение здесь была быстрая реализация, память не является проблемой, я бы указал на это.) С помощью этой реализации:

class Timer 
{ 
public: 
    Timer() 
     : mStart(0) 
    { 
    } 

    void Start() 
    { 
     mStart = std::clock(); 
    } 

    double Mark() 
    { 
     std::clock_t curr = std::clock(); 
     double f = (curr - mStart)/((double)CLOCKS_PER_SEC); 
     mStart = curr; 
     return f; 
    } 

private: 
    std::clock_t mStart; 
}; 

struct Node 
{ 
    int Size; 
    int Count; 

    Node() 
     : Size(0), Count(0) 
    { 
    } 

    Node(int size) 
     : Size(size), Count(1) 
    { 
    } 


}; 

void map_insert(std::map<int, Node> &map, int key, int size) 
{ 
    std::map<int, Node>::iterator itr = map.find(key); 

    if (itr != map.end()) 
    { 
     itr->second.Count++; 
     itr->second.Size += size; 
    } 
    else 
    { 
     map[key] = Node(size); 
    } 
} 

bool compare(const std::pair<int, Node> &a1, const std::pair<int, Node> &a2) 
{ 
    return a1.second.Size < a2.second.Size; 
} 

int make_size(int i, int size_max) 
{ 
    return (7 * i) % size_max; 
} 

int make_key(int i, int key_max) 
{ 
    return (11 * i) % key_max; 
} 

void first_impl(int max, int size_max, int key_max) 
{ 
    std::cout << "first_impl:" << std::endl; 
    double total = 0; 
    double curr = 0; 
    Timer t; 
    t.Start(); 

    { 
     std::map<int, Node> _map; 

     for (int i = 0; i < max; ++i) 
      map_insert(_map, make_key(i, key_max), make_size(i, size_max)); 

     total += curr = t.Mark(); 
     std::cout << "\tinsert: " << curr << std::endl; 

     std::vector<std::pair<int, Node>> v(_map.begin(), _map.end()); 

     total += curr = t.Mark(); 
     std::cout << "\tcreate: " << curr << std::endl; 

     std::sort(v.begin(), v.end(), compare); 

     total += curr = t.Mark(); 
     std::cout << "\tsort: " << curr << std::endl; 
    } 

    total += curr = t.Mark(); 
    std::cout << "\tcleanup: " << curr << std::endl; 

    std::cout << "\ttotal: " << total << std::endl; 
} 

void second_impl(int max, int size_max, int key_max) 
{ 
    std::cout << "second_impl:" << std::endl; 
    double total = 0; 
    double curr = 0; 
    Timer t; 
    t.Start(); 

    { 
     std::map<int, Node> res; 
     typedef std::multimap<int, int> MultiMap; 
     MultiMap mMap; 

     for (int i = 0; i < max; ++i) 
      mMap.insert(std::make_pair(make_key(i, key_max), make_size(i, size_max))); 

     total += curr = t.Mark(); 
     std::cout << "\tinsert: " << curr << std::endl; 

     std::multimap<int, int>::iterator iter = mMap.begin(); 
     std::multimap<int, int>::iterator endIter = mMap.end(); 
     for(; iter != endIter; ++iter) 
     { 
      int val = iter->first; 
      if(res.find(val) != res.end()) 
      { 
        continue; 
      } 

      std::pair<MultiMap::iterator, MultiMap::iterator> iterPair = mMap.equal_range(val); 
      Node n; 
      n.Size = val; 
      n.Count = mMap.count(val); 

      int size = 0; 
      for(; iterPair.first != iterPair.second; ++iterPair.first) 
      { 
        size += iterPair.first->second;     
      } 

      res[size] = n; 
     } 
     total += curr = t.Mark(); 
     std::cout << "\tsort: " << curr << std::endl; 
    } 

    total += curr = t.Mark(); 
    std::cout << "\tcleanup: " << curr << std::endl; 

    std::cout << "\ttotal: " << total << std::endl; 
} 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    const int size_max = 31; 
    const int key_max = 1019; 
    const int max = 1000000; 
    first_impl(max, size_max, key_max); 
    second_impl(max, size_max, key_max); 

    return 0; 
} 

Результаты выглядеть примерно так:

first_impl: 
     insert: 0.094 
     create: 0 
     sort: 0 
     cleanup: 0 
     total: 0.094 
second_impl: 
     insert: 1.653 
     sort: 46.894 
     cleanup: 66.081 
     total: 114.628 

во второй реализация явно медленнее. Похоже, что общее количество ключей составляет far меньше, чем общее количество элементов (общее число уникальных ключей, составляющих около 1000, является представителем моего набора данных) делает std :: map победителем здесь, потому что он быстро достигает стабильное состояние, в котором не требуется больше узлов. I полностью пропустил этот факт, прежде чем я сделал это вторичное расследование.

Похоже, что моя первоначальная реализация лучше мультимапа, и поскольку я не хочу зависеть от Boost, я думаю, что у меня есть мой ответ. Спасибо всем!

+0

На втором взгляде на код, который использует multimap, я не совсем уверен, что эта реализация верна, однако тот факт, что требуется больше времени вставлять в multimap, чем для полного расчета того, что я ищу с помощью карты/vector/sort, это все еще кажется ясным, что первоначальная реализация - это та, с которой я иду. – LCC

+0

Для большей скорости вы можете посмотреть hash_map (или tr1: unordered_map). – Joel

+0

Да, итерации очень медленные, поэтому MultiMap вышел, и это технически некорректно, так как вам не нужно держать размер и подсчет, просто подведите итог, как вы правильно сделали. Хэш-таблица неупорядоченной карты STL примерно в 3 раза быстрее, чем карта, но для вашего скромного размера данных не стоит хлопот. Интригующей идеей было бы использовать 1 карту для суммирования данных и вторую карту для ее сортировки - не то, что qsort() работает не очень хорошо, как только вы суммируете данные, но вторая карта предоставит отсортированное резюме в в режиме реального времени. – user2548100

ответ

6

A multimap<> может вам помочь.

В наборе данных с одним и тем же ключом имеется несколько записей.
multimap<> может обрабатывать дубликаты ключей, карты не могут.

Сколько каждого ключа есть в наборе данных
multimap<>::count() принимает ключ и возвращает количество совпадающих элементов.

Для каждого ключа, что общий размер их
multimap<>::equal_range() принимает ключ и возвращает std::pair< multimap<>::iterator, multimap<>::iterator >, где первый итератор на первый элемент, соответствующий ключ и второе является последним. их можно повторить, считая, что они начинаются и заканчиваются. Таким образом, используя их, это будет простой цикл для вычисления общего размера для каждого ключа.

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

+0

Используя код Naveen ниже, он выглядит так, как будто map/vector/sort быстрее из-за того, что мое общее количество уникальных ключей является низким по сравнению с общим количеством записей в наборе данных. Тестирование этого в отношении реализации с несколькими режимами дало мне больше уверенности в том, что исходное решение, которое я придумал, должно быть достаточно хорошим для моих целей. Спасибо за информацию! – LCC

+0

Любой ответ с использованием MultiMap НЕПРАВИЛЬНО. Нет необходимости хранить дискретные данные для последующего суммирования. Это неправильно, потому что он использует много ресурсов для удовлетворения несуществующих требований. Подход OPs правильный. – user2548100

+0

Я согласен с тем, что мультимап расточительный, однако не сказал бы, что это неправильно. Многомачтовая версия проще реализовать и, вероятно, будет иметь меньше ошибок. Простота реализации - это абсолютно эффективная сделка, которая позволяет снизить производительность.Все зависит от ваших потребностей. – 0xC0DEFACE

2

В зависимости от сложности, у вас есть два варианта.

Первый должен использовать контейнер multimap для сохранения значений и итеративно использовать equal_range для генерации вывода. Здесь вы получите быструю вставку, но медленный выход.

Второй вариант заключается в использовании boost::multi_index с функциями-членами как индексы, которые будут вычислять значения суммы и количества вставки.Здесь вы получите медленную вставку, но быстрый выход.

2

Ниже приведен пример кода для его реализации с использованием std::multimap и std::map, а затем с использованием equal_range с count методом этих классов.

std::map<int, Node> res; 
    typedef std::multimap<int, int> MultiMap; 
    MultiMap mMap; 
    mMap.insert(std::make_pair(1,3)); 
    mMap.insert(std::make_pair(3,27)); 
    mMap.insert(std::make_pair(7,7)); 
    mMap.insert(std::make_pair(3,2)); 
    mMap.insert(std::make_pair(1,1)); 


std::multimap<int, int>::iterator iter = mMap.begin(); 
std::multimap<int, int>::iterator endIter = mMap.end(); 
while(iter != endIter) 
{ 
    int val = iter->first; 
    std::pair<MultiMap::iterator, MultiMap::iterator> iterPair = mMap.equal_range(val); 
    Node n; 
    n.val = val; 
    n.count = mMap.count(val); 

    int size = 0; 
    for(; iterPair.first != iterPair.second; ++iterPair.first) 
    { 
     size += iterPair.first->second;   
    } 

    res[size] = n; 

    iter = iterPair.second; 
} 

узла определяется как:

struct Node 
{ 
    int val; 
    int count; 

    Node() : val(0), count(0) 
    { 
    } 
}; 

Обратите внимание, что ключ для результата карты является size и не count.

+0

Спасибо за код. Это действительно помогло сузить тот факт, что map/vector/sort работает лучше в моем конкретном случае. Я бы поднял тебя, но у меня нет репутации. =/ – LCC

0

Замечание: В VS2008, по крайней мере, при назначении map[key] = Node(size); вы фактически создаете три отдельных экземпляра Node. Результатом является то, что в стек создается только тот, который вы объявляете, - два других создаются в куче, поэтому, используя эту версию, вы на самом деле выполняете вдвое больше накладных расходов, если бы использовали указатель и принимали на себя ответственность за удаление все ваши экземпляры в конце.

2

Если вы можете использовать Boost, вы можете использовать Boost.Multiindex. Он позволяет иметь контейнер с двумя упорядоченными индексами (в вашем примере индекс по ключу и индекс по размеру). Что касается эффективности памяти или неэффективностью говорится, что в Boost.MultiIndex ordered indices node compression был реализован, и результат таков:

Размер узла заголовков упорядоченные индексы были снижены на 25% на большинстве платформ

Также взгляните на этот пример и его результат: Results for 2 ordered indices. Поэтому даже когда вы просто используете boost :: multiindex с упорядоченными индексами, он использует меньше памяти, чем std :: multiset от MS VS 8.0 или gcc.

Что касается вашего решения, я думаю, вы можете ожидать, что Boost.Multiindex будет использовать меньше памяти по сравнению с вашей реализацией. Однако, если вы хотите сравнить ровно два решения, вы можете это сделать. Напишите свой собственный счетчик, добавьте его в свои контейнеры и узнайте, сколько памяти было использовано. Затем сделайте то же самое, используя Boost.Multiindex с вашим счетным распределителем. Это номер example of allocator.Вам нужно немного изменить его, чтобы подсчитать количество байтов, которое было выделено и освобождено benn.

2

std :: map - ассоциативный контейнер, поэтому карта будет в порядке сортировки относительно ключа. И здесь, поскольку вы используете дубликаты ключей, поэтому multimap решит вашу цель.

0

stl::map<Key, Data, Compare, Alloc> есть Compare, просто предоставьте функцию для слабого заказа там.

struct Node 
{ 
    int Size; 
    int Count; 
}; 

bool compareNode(const Node& a, const Node& b) { 
    return a.Size < b.Size; 
} 

stl::map<Node, stlstring, compareNode> xxx; 
1

Здесь небольшое усовершенствование вашего решения. Более короткие и избежать повторного поиска ключа при вставке нового (обратите внимание, что он опирается на 0s своего конструктора узла)

void map_insert(std::map<int, Node> &map, int key, int size) { 
    Node & n = map[key]; 
    ++n.Count; 
    n.Size+=size; 
} 

Но оптимальный способ, вероятно, зависит от диапазона ключей. Если всегда маленький (скажем, 1..1000), простой вектор - лучший выбор. Если больше, то hash_map дает лучший результат, потому что вам, похоже, не нужен порядок ключей (используемый картой).

Я тестировал и, кажется, дает разумное улучшение для вашего случая с 1000 ключами, но это также зависит от вашего распределения ключей. Вам просто нужно заменить std::map на std::hash_map, а затем исправить заголовок. Однако у std::hash_map может возникнуть проблема с переносимостью. Вы все равно можете написать свою собственную систему хэширования (и даже адаптировать ее к вашему распределению ключей).

EDIT: unordered_map представляется будущим стандартом для hash_map. По крайней мере, если исправление предупреждения об отказе от gcc 4.3.

+0

Ах хороший звонок. Я пропустил лучший шаблон вставки и hash_map/unordered_map. Благодаря! – LCC

+0

Прочитайте требование OP ..... «Я хочу сгенерировать этот вывод, отсортированный по размеру по возрастанию:». Это исключает использование всего хэша. – user2548100

0

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

Фактически, вставка карты вызывает перебалансировки, поэтому это хуже, чем пересылка списка.

Смежные вопросы