Подтвердить что ты не робот

Какой контейнер следует использовать для произвольного доступа, дешевого добавления и удаления (без деления/распределения) с известным максимальным размером?

Мне нужен контейнер меньшего размера, который должен хранить до 128 неподписанных int. Он должен добавлять, редактировать и удалять каждый элемент, быстро обращающийся к нему, без выделения новой памяти каждый раз (я уже знаю, что это будет max 128).

Например:

add int 40 at index 4 (1/128 item used)
add int 36 at index 90 (2/128 item used)
edit to value 42 the element at index 4
add int 36 at index 54 (3/128 item used)
remove element with index 90 (2/128 item used)
remove element with index 4 (1/128 item used)

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

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

Какой контейнер будет правильным кандидатом? Это похоже на очередь "индексов".

4b9b3361

Ответ 1

Как я понимаю, у вас есть две операции

Вставить/заменить элемент значение в ячейке index
Удалить элемент в ячейке index

и один предикат

Является ли ячейка index занятой в данный момент?

Это массив и растровое изображение. Когда вы вставляете/заменяете, вы вставляете значение в ячейку массива и устанавливаете бит битмарта. Когда вы удаляете, вы очищаете битмад-бит. Когда вы спрашиваете, вы запрашиваете битмад-бит.

Ответ 2

Вы можете просто использовать std::vector<int> и сделать vector.reserve(128);, чтобы вектор не выделял память. Это не позволяет вам отслеживать конкретные индексы.

Если вам нужно отслеживать "индекс", вы можете использовать std::vector<std::pair<int, int>>. Это не позволяет использовать произвольный доступ.

Ответ 3

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

Конечно, если вам нужно перебирать все используемые ячейки, вам нужно отсканировать весь массив. Но это компромисс, который вам нужно сделать: либо делать больше работы при добавлении и стирании, либо делать больше работы во время поиска. (Обратите внимание, что .insert() в середине vector<> будет перемещать данные вокруг.)

В любом случае, 128 элементов так мало, что сканирование по всему массиву будет незначительным. И, честно говоря, я думаю, что все более сложное, чем vector, будет полным излишеством.:)

Грубо:

unsigned data[128] = {0}; // initialize
unsigned used[128] = {0};
data[index] = newvalue; used[index] = 1; // set value
data[index] = used[index] = 0;           // unset value
// check if a cell is used and do something 
if (used[index]) { do something } else { do something else } 

Ответ 4

Я бы предложил тандем векторов, один для хранения активных индексов, другой - для хранения данных:

class Container
{
  std::vector<size_t> indices;
  std::vector<int> data;

  size_t index_worldToData(size_t worldIndex) const
  {
    auto it = std::lower_bound(begin(indices), end(indices), worldIndex);
    return it - begin(indices);
  }

public:
  Container()
  {
    indices.reserve(128);
    data.reserve(128);
  }

  int& operator[] (size_t worldIndex)
  {
    return data[index_worldToData(worldIndex)];
  }

  void addElement(size_t worldIndex, int element)
  {
    auto dataIndex = index_worldToData(worldIndex);
    indices.insert(it, worldIndex);
    data.insert(begin(data) + dataIndex, element);
  }

  void removeElement(size_t worldIndex)
  {
    auto dataIndex = index_worldToData(worldIndex);
    indices.erase(begin(indices) + dataIndex);
    data.erase(begin(indices) + dataIndex);
  }

  class iterator
  {
    Container *cnt;
    size_t dataIndex;

  public:
    int& operator* () const { return cnt.data[dataIndex]; }
    iterator& operator++ () { ++dataIndex; }
  };

  iterator begin() { return iterator{ this, 0 }; }
  iterator end() { return iterator{ this, indices.size() }; }
};

(Отказ от ответственности: код не тронут компилятором, проверки предпосылок опущены)

У этого есть логарифмический доступ к временному элементу, линейная вставка и удаление времени и позволяет выполнять итерацию по непустым элементам.

Ответ 5

Вы можете использовать двусвязный список и массив указателей node. Предварительно выделите 128 узлов списка и сохраните их на freelist. Создайте пустой itemlist. Выделите массив из 128 указателей node, называемых items

  • Вставить в i: повернуть голову node с freelist, добавить его в itemlist, установите items[i], чтобы указать на него.
  • Чтобы получить доступ/изменить значение, используйте items[i]->value
  • Чтобы удалить в i, удалите node, на который указывает items[i], вставьте его в 'freelist'
  • Чтобы продолжить, просто ходите itemlist

Все O (1), кроме итерации, которая является O (N active_items). Только оговорка заключается в том, что итерация не находится в порядке индекса.

Freelist может быть односвязным или даже массивом узлов, поскольку все, что вам нужно, это pop и push.

Ответ 6

class Container {
  private:
    set<size_t> indices;
    unsigned int buffer[128];
  public:
    void set_elem(const size_t index, const unsigned int element) {
      buffer[index] = element;
      indices.insert(index);
    }
    // and so on -- iterate over the indices if necessary
};

Ответ 7

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


Наиболее доступным решением является использование нестандартных контейнеров Boost , представляющих особый интерес flat_map. По существу, flat_map предлагает интерфейс map по хранению, предоставляемому динамическим массивом.

Вы можете вызвать его reserve в начале, чтобы избежать выделения памяти.


Несколько более активное решение состоит в том, чтобы закодировать свой собственный распределитель памяти.

Интерфейс allocator относительно прост в обращении, так что кодирование распределителя довольно просто. Создайте пул-распределитель, который никогда не выпустит какой-либо элемент, не разогревает (выделяет 128 элементов), и вы готовы к работе: его можно подключить в любую коллекцию, чтобы сделать его свободным от размещения.

Особый интерес здесь, конечно, std::map.


Наконец, есть дорога по-своему. Гораздо больше задействовано, совершенно очевидно: количество операций, поддерживаемых стандартными контейнерами, просто... огромно.

Тем не менее, если у вас есть время или вы можете жить только подмножеством этих операций, то у этой дороги есть одно неоспоримое преимущество: вы можете адаптировать контейнер именно к вашим потребностям.

Особый интерес представляет идея иметь std::vector<boost::optional<int>> из 128 элементов... за исключением того, что, поскольку это представление довольно неэффективно, мы используем Data-Oriented Design, чтобы вместо этого сделать два вектора: std::vector<int> и std::vector<bool>, который является гораздо более компактным или даже...

struct Container {
    size_t const Size = 128;

    int array[Size];
    std::bitset<Size> marker;
}

который является компактным и не требует выделения.

Теперь итерация требует итерации битового набора для существующих элементов, что может показаться расточительным вначале, но указанный битсет имеет длину всего 16 байтов, так что это бриз! (потому что в таком масштабе область памяти превосходит сложность большого О)

Ответ 8

Почему бы не использовать std::map<int, int>, он обеспечивает произвольный доступ и разрежен.

Ответ 9

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