#pragma once #include /** Замена хэш-таблицы для маленького количества (единицы) ключей. * Реализована в виде массива с линейным поиском. * Массив расположен внутри объекта. * Интерфейс является подмножеством интерфейса HashTable. * * Вставка возможна только если метод full возвращает false. * При неизвестном количестве различных ключей, * вы должны проверять, не заполнена ли таблица, * и делать fallback в этом случае (например, использовать полноценную хэш-таблицу). */ template < typename Key, typename Cell, size_t capacity > class SmallTable : private boost::noncopyable, protected Cell::State { protected: friend class const_iterator; friend class iterator; friend class Reader; using Self = SmallTable; using cell_type = Cell; size_t m_size = 0; /// Количество элементов. Cell buf[capacity]; /// Кусок памяти для всех элементов. /// Найти ячейку с тем же ключём или пустую ячейку, начиная с заданного места и далее по цепочке разрешения коллизий. const Cell * ALWAYS_INLINE findCell(const Key & x) const { const Cell * it = buf; while (it < buf + m_size) { if (it->keyEquals(x)) break; ++it; } return it; } Cell * ALWAYS_INLINE findCell(const Key & x) { Cell * it = buf; while (it < buf + m_size) { if (it->keyEquals(x)) break; ++it; } return it; } public: using key_type = Key; using value_type = typename Cell::value_type; class Reader final : private Cell::State { public: Reader(DB::ReadBuffer & in_) : in(in_) { } Reader(const Reader &) = delete; Reader & operator=(const Reader &) = delete; bool next() { if (!is_initialized) { Cell::State::read(in); DB::readVarUInt(size, in); if (size > capacity) throw DB::Exception("Illegal size"); is_initialized = true; } if (read_count == size) { is_eof = true; return false; } cell.read(in); ++read_count; return true; } inline const value_type & get() const { if (!is_initialized || is_eof) throw DB::Exception("No available data", DB::ErrorCodes::NO_AVAILABLE_DATA); return cell.getValue(); } private: DB::ReadBuffer & in; Cell cell; size_t read_count = 0; size_t size; bool is_eof = false; bool is_initialized = false; }; class iterator { Self * container; Cell * ptr; friend class SmallTable; public: iterator() {} iterator(Self * container_, Cell * ptr_) : container(container_), ptr(ptr_) {} bool operator== (const iterator & rhs) const { return ptr == rhs.ptr; } bool operator!= (const iterator & rhs) const { return ptr != rhs.ptr; } iterator & operator++() { ++ptr; return *this; } value_type & operator* () const { return ptr->getValue(); } value_type * operator->() const { return &ptr->getValue(); } Cell * getPtr() const { return ptr; } }; class const_iterator { const Self * container; const Cell * ptr; friend class SmallTable; public: const_iterator() {} const_iterator(const Self * container_, const Cell * ptr_) : container(container_), ptr(ptr_) {} const_iterator(const iterator & rhs) : container(rhs.container), ptr(rhs.ptr) {} bool operator== (const const_iterator & rhs) const { return ptr == rhs.ptr; } bool operator!= (const const_iterator & rhs) const { return ptr != rhs.ptr; } const_iterator & operator++() { ++ptr; return *this; } const value_type & operator* () const { return ptr->getValue(); } const value_type * operator->() const { return &ptr->getValue(); } const Cell * getPtr() const { return ptr; } }; const_iterator begin() const { return iteratorTo(buf); } iterator begin() { return iteratorTo(buf); } const_iterator end() const { return iteratorTo(buf + m_size); } iterator end() { return iteratorTo(buf + m_size); } protected: const_iterator iteratorTo(const Cell * ptr) const { return const_iterator(this, ptr); } iterator iteratorTo(Cell * ptr) { return iterator(this, ptr); } public: /** Таблица переполнена. * В переполненную таблицу ничего нельзя вставлять. */ bool full() { return m_size == capacity; } /// Вставить значение. В случае хоть сколько-нибудь сложных значений, лучше используйте функцию emplace. std::pair ALWAYS_INLINE insert(const value_type & x) { std::pair res; emplace(Cell::getKey(x), res.first, res.second); if (res.second) res.first.ptr->setMapped(x); return res; } /** Вставить ключ, * вернуть итератор на позицию, которую можно использовать для placement new значения, * а также флаг - был ли вставлен новый ключ. * * Вы обязаны сделать placement new значения, если был вставлен новый ключ, * так как при уничтожении хэш-таблицы для него будет вызываться деструктор! * * Пример использования: * * Map::iterator it; * bool inserted; * map.emplace(key, it, inserted); * if (inserted) * new(&it->second) Mapped(value); */ void ALWAYS_INLINE emplace(Key x, iterator & it, bool & inserted) { Cell * res = findCell(x); it = iteratorTo(res); inserted = res == buf + m_size; if (inserted) { new(res) Cell(x, *this); ++m_size; } } /// То же самое, но вернуть false, если переполнено. bool ALWAYS_INLINE tryEmplace(Key x, iterator & it, bool & inserted) { Cell * res = findCell(x); it = iteratorTo(res); inserted = res == buf + m_size; if (inserted) { if (res == buf + capacity) return false; new(res) Cell(x, *this); ++m_size; } return true; } /// Скопировать ячейку из другой хэш-таблицы. Предполагается, что такого ключа в таблице ещё не было. void ALWAYS_INLINE insertUnique(const Cell * cell) { memcpy(&buf[m_size], cell, sizeof(*cell)); ++m_size; } void ALWAYS_INLINE insertUnique(Key x) { new(&buf[m_size]) Cell(x, *this); ++m_size; } iterator ALWAYS_INLINE find(Key x) { return iteratorTo(findCell(x)); } const_iterator ALWAYS_INLINE find(Key x) const { return iteratorTo(findCell(x)); } void write(DB::WriteBuffer & wb) const { Cell::State::write(wb); DB::writeVarUInt(m_size, wb); for (size_t i = 0; i < m_size; ++i) buf[i].write(wb); } void writeText(DB::WriteBuffer & wb) const { Cell::State::writeText(wb); DB::writeText(m_size, wb); for (size_t i = 0; i < m_size; ++i) { DB::writeChar(',', wb); buf[i].writeText(wb); } } void read(DB::ReadBuffer & rb) { Cell::State::read(rb); m_size = 0; size_t new_size = 0; DB::readVarUInt(new_size, rb); if (new_size > capacity) throw DB::Exception("Illegal size"); for (size_t i = 0; i < new_size; ++i) buf[i].read(rb); m_size = new_size; } void readText(DB::ReadBuffer & rb) { Cell::State::readText(rb); m_size = 0; size_t new_size = 0; DB::readText(new_size, rb); if (new_size > capacity) throw DB::Exception("Illegal size"); for (size_t i = 0; i < new_size; ++i) { DB::assertChar(',', rb); buf[i].readText(rb); } m_size = new_size; } size_t size() const { return m_size; } bool empty() const { return 0 == m_size; } void clear() { if (!__has_trivial_destructor(Cell)) for (iterator it = begin(); it != end(); ++it) it.ptr->~Cell(); m_size = 0; } size_t getBufferSizeInBytes() const { return sizeof(buf); } }; struct HashUnused {}; template < typename Key, size_t capacity > using SmallSet = SmallTable, capacity>; template < typename Key, typename Cell, size_t capacity > class SmallMapTable : public SmallTable { public: using key_type = Key; using mapped_type = typename Cell::Mapped; using value_type = typename Cell::value_type; mapped_type & ALWAYS_INLINE operator[](Key x) { typename SmallMapTable::iterator it; bool inserted; this->emplace(x, it, inserted); new(&it->second) mapped_type(); return it->second; } }; template < typename Key, typename Mapped, size_t capacity > using SmallMap = SmallMapTable, capacity>;