2015-06-19 4 views
5

Существует boost.containerflat_map и другие, а также Loki AssocVector и многие другие подобные этим, которые сохраняют элементы отсортированными.Есть ли плоская несортированная реализация карты/набора?

Есть ли современная (C++ 11 с поддержкой перемещения и т. Д.) Реализация несортированного вектора, адаптированного как карта/множество?

Идея заключается в том, чтобы использовать его для очень маленьких карт/наборов (менее 20 элементов) и с простыми ключами (для которых хеширование не всегда имеет смысл)

+0

Я не уверен, что вы хотите. Почему важно, чтобы он хранился плоский, то есть в векторе? Чтобы использовать несортированный вектор в качестве карты/набора, вам нужен какой-то отсортированный индекс, планируете ли вы сохранить это внешнее для контейнера? –

+0

как насчет unordered_map? – BeyelerStudios

+0

@JonathanWakely Элементы в векторе могут быть парами ключа и значения - мне не нужен отдельный индекс. – onqtam

ответ

7

Что-то вроде этого?

template<class Key, class Value, template<class...>class Storage=std::vector> 
struct flat_map { 
    struct kv { 
    Key k; 
    Value v; 
    template<class K, class V> 
    kv(K&& kin, V&& vin):k(std::forward<K>(kin)), v(std::forward<V>(vin)){} 
    }; 
    using storage_t = Storage<kv>; 
    storage_t storage; 

    // TODO: adl upgrade 
    using iterator=decltype(std::begin(std::declval<storage_t&>())); 
    using const_iterator=decltype(std::begin(std::declval<const storage_t&>())); 
    // boilerplate: 
    iterator begin() { 
    using std::begin; 
    return begin(storage); 
    } 
    const_iterator begin() const { 
    using std::begin; 
    return begin(storage); 
    } 
    const_iterator cbegin() const { 
    using std::begin; 
    return begin(storage); 
    } 
    iterator end() { 
    using std::end; 
    return end(storage); 
    } 
    const_iterator end() const { 
    using std::end; 
    return end(storage); 
    } 
    const_iterator cend() const { 
    using std::end; 
    return end(storage); 
    } 
    size_t size() const { 
    return storage.size(); 
    } 
    bool empty() const { 
    return storage.empty(); 
    } 
    // these only have to be valid if called: 
    void reserve(size_t n) { 
    storage.reserve(n); 
    } 
    size_t capacity() const { 
    return storage.capacity(); 
    } 
    // map-like interface: 
    // TODO: SFINAE check for type of key 
    template<class K> 
    Value& operator[](K&& k){ 
    auto it = find(k); 
    if (it != end()) return it->v; 
    storage.emplace_back(std::forward<K>(k), Value{}); 
    return storage.back().v; 
    } 
private: // C++14, but you can just inject the lambda at point of use in 11: 
    template<class K> 
    auto key_match(K& k) { 
    return [&k](kv const& kv){ 
     return kv.k == k; 
    }; 
    } 
public: 
    template<class K> 
    iterator find(K&& k) { 
    return std::find_if(begin(), end(), key_match(k)); 
    } 
    template<class K> 
    const_iterator find(K&& k) const { 
    return const_cast<flat_map*>(this)->find(k); 
    } 
    // iterator-less query functions: 
    template<class K> 
    Value* get(K&& k) { 
    auto it = find(std::forward<K>(k)); 
    if (it==end()) return nullptr; 
    return std::addressof(it->v); 
    } 
    template<class K> 
    Value const* get(K&& k) const { 
    return const_cast<flat_map*>(this)->get(std::forward<K>(k)); 
    } 
    // key-based erase: (SFINAE should be is_comparible, but that doesn't exist) 
    template<class K, class=std::enable_if_t<std::is_converible<K, Key>{}>> 
    bool erase(K&& k) { 
    auto it = std::remove(
     storage.begin(), storage.end(), key_match(std::forward<K>(k)) 
    ); 
    if (it == storage.end()) return false; 
    storage.erase(it, storage.end()); 
    return true; 
    } 
    // classic erase, for iterating: 
    iterator erase(const_iterator it) { 
    return storage.erase(it); 
    } 
    template<class K2, class V2, 
    class=std::enable_if_t< 
     std::is_convertible< K2, Key >{}&& 
     std::is_convertible< V2, Value >{} 
    > 
    > 
    void set(K2&& kin, V2&& vin) { 
    auto it = find(kin); 
    if (it != end()){ 
     it->second = std::forward<V2>(vin); 
     return; 
    } else { 
     storage.emplace_back(std::forward<K2>(kin), std::forward<V2>(vin)); 
    } 
    } 
}; 

Я оставил тип контейнера в качестве аргумента шаблона, поэтому вы можете использовать векторную структуру SBO, если вы выберете.

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

+0

@nwp nope, вопрос спрашивает для несортированного вектора вы неправильно читаете. Я использую один и тот же вектор для ключей и значений, потому что сохранение вещей смежным обычно стоит затрат на выравнивание. – Yakk

+2

Ничего себе! Ты просто написал это? Он работал после исправления опечатки в '' 'is_converible'''. Он также работал с расширением контейнера SBO '' 'template с использованием mySmallVec = boost :: container :: small_vector ;' ''. Что еще не хватает в этом классе? Не могли бы вы подумать о том, чтобы сделать его публичным в репо? – onqtam

+1

Мне просто интересно, почему другие контейнеры (например, '' boost :: container :: flat_map'''), предлагающие внутренний тип контейнера в качестве параметра шаблона ... были бы настолько полезны! – onqtam

1

Там в std::unordered_set и std::unordered_map, но, насколько я знают, что они не реализованы с использованием векторов.

Возможный вариант - написать собственный хэш-вектор и хэш-ключ с помощью std::hash<Key>, а затем проиндексировать полученное число по модулю длины вектора, но тогда вам придется выяснить способ обработки столкновений и всех возникающие проблемы вручную. Не уверен, что я рекомендовал это.

В качестве альтернативы можно передать пользовательский аллокатор к std::unordered_set и std::unordered_map, которые выполняют распределение на векторе (например, путем обладания внутреннего вектора), как это было предложено @BeyelerStudios.

+0

, вы можете предоставить свой unordered_map распределитель на основе вектора – BeyelerStudios

+0

@BeyelerStudios, такой распределитель не будет работать хорошо, если он не имеет фиксированного максимального размера, который предварительно выделен, и в этом случае вы могли бы просто использовать массив. Если он не имеет фиксированного максимального размера, тогда, когда он вырастет, ему необходимо перераспределить и переместить все элементы, что нарушает гарантии стабильности ссылки 'unordered_map'. Вставка нового элемента не должна приводить к недействительности ссылок на существующие элементы. –

+0

@ JonathanWakely да, это звучит так, как будто это тот случай (фиксированный максимальный размер) - существует фиксированный максимальный размер (который вы можете вычислить) почти для каждой проблемы, с которой вы сталкиваетесь – BeyelerStudios

4

Если наборы обязательно маленькие, вы можете просто использовать std::vector (или std::deque) и выполнять поиск с использованием линейных поисков. Линейный поиск O (n) по малым векторам может быть быстрее, чем поиск O (log (n)) в более сложной структуре, такой как красно-черное дерево.

Значок телефона vector Вы можете просто положить элементы в vector, не сортируя их. Вам все равно нужно будет перетасовать, если вы удалите элементы, но это всегда будет верно для плоской векторной структуры, независимо от того, сортируется она или нет, если только вы не удаляете только элемент назад. Почему в любом случае важно, чтобы он был плоским?

+0

Важно, чтобы избежать распределения и иметь кэш-память. – onqtam

+0

Тогда вам нужно идти на компромисс при перетасовке. У вас не может быть и того, и другого, если вы на самом деле не стираете элементы, просто как-то пометите их как мертвые, а затем повторно используйте мертвый элемент в следующей вставке. –

+1

@JonathanWakely Удаление элемента из несортированного вектора O (1) - просто поменяйте его на '.back()', а затем '.pop_back()'. –

1

Евгений Панасюк прав, я считаю, что вы хотите Открыть адрес Hash Map.
Это соответствует точному требованию, только 1 плоский буфер, отсутствие выделения узлов, отсутствие указателей и несортирование.

В противном случае у вас также есть flat_map/AssocVector, но они отсортированы, в отличие от ваших требований.

Для OAHM, у меня есть реализацию STL-как родового один здесь:
https://sourceforge.net/projects/cgenericopenaddresshashmap/

Также вы можете взглянуть на базовую страницу flat_map:
boost::flat_map and its performance compared to map and unordered_map
OAHM выполняет очень близко к flat_map во всех тестах, кроме итераций.

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