Как я могу хэшировать std::unordered_map::const_iterator?

Помните мой предыдущий вопрос: Что вызывает гонку данных в std::async здесь?
Несмотря на то, что я успешно распараллелил эту программу, она все еще работала слишком медленно, чтобы ее можно было использовать на практике.
Поэтому я попытался улучшить структуру данных, представляющую шаблон игры Конвея "Жизнь".
Краткое объяснение новой структуры:

class pattern {
    // NDos::Lifecell represents a cell by x and y coordinates.
    // NDos::Lifecell is equality comparable, and has std::hash specialization.
private:
    std::unordered_map<NDos::Lifecell, std::pair<int, bool>> cells_coor;
    std::unordered_set<decltype(cells_coor)::const_iterator> cells_neigh[9];
    std::unordered_set<decltype(cells_coor)::const_iterator> cells_onoff[2];
public:
    void insert(int x, int y) {
        // if coordinate (x,y) isn't already ON,
        // turns it ON and increases the neighbor's neighbor count by 1.
    }
    void erase(int x, int y) {
        // if coordinate (x,y) isn't already OFF,
        // turns it OFF and decreases the neighbor's neighbor count by 1.
    }
    pattern generate(NDos::Liferule rule) {
        // this advances the generation by 1, according to the rule.
        // (For example here, B3/S23)
        pattern result;
        // inserts every ON cell with 3 neighbors to result.
        // inserts every OFF cell with 2 or 3 neighbors to result.
        return result;
    }
    // etc...
};

Короче говоря, pattern содержит ячейки. Он содержит все включенные ячейки и все выключенные ячейки, которые имеют 1 или более включенных соседних ячеек. Он также может содержать запасные ВЫКЛЮЧЕННЫЕ ячейки.
cells_coor напрямую сохраняет ячейки, используя их координаты в качестве ключей, и сопоставляет их с количеством включенных соседних ячеек (хранится как int) и включено ли они (хранится как bool).

cells_neigh и cells_onoff косвенно сохраняют ячейки с помощью итераторов к ним как к ключам.
Число включенных соседей ячейки всегда равно 0 или больше и 8 или меньше, поэтому cells_neigh — это массив размером 9.
cells_neigh[0] хранит ячейки с 0 соседними ячейками ON, cells_neigh[1] хранит ячейки с 1 соседней ячейкой ON и так далее.
Аналогично, ячейка всегда либо OFF, либо ON, поэтому cells_onoff — это массив размера 2.
cells_onoff[false] хранит Ячейки OFF, а cells_onoff[true] хранит ячейки ON.
Ячейки должны быть вставлены или удалены из всех ячеек cells_coor, cells_neigh и cells_onoff. Другими словами, если ячейка вставляется или стирается из одного из них, то это должно быть так же и для других. Из-за этого элементы cells_neigh и cells_onoff std::unordered_set сохраняют итераторы к фактическим ячейкам, обеспечивая быстрый доступ к ячейкам по счетчику соседей или по состоянию OFF/ON.

Если эта структура работает, функция вставки будет иметь среднюю временную сложность O(1), стирание также O(1) и генерацию O(cells_coor.size()), что значительно улучшает временную сложность по сравнению с предыдущей структурой.

Но, как видите, есть проблема: Как я могу хешировать std::unordered_map::const_iterator?
std::hash запрещает им специализацию, поэтому мне нужно сделать собственную.
Выиграл взятие их адреса. не работают, так как они обычно получаются как rvalue или временные значения.
Разыменование их также не сработает, так как есть несколько ячеек, для которых 0 имеет соседние ячейки, или несколько ячеек, которые выключены, и т. д.
Итак. что я могу сделать? Если я ничего не могу сделать, cells_neigh и cells_onoff будут std::vector или что-то в этом роде, что резко ухудшит временную сложность.


person Dannyu NDos    schedule 04.03.2017    source источник
comment
Как вы думаете, может быть, есть причина, по которой std::hash запрещает им специализацию? Думаю, я понимаю, почему вы этого хотите, но IMO это еще одно доказательство того, что хранение итераторов - не всегда лучшая идея. Но у меня нет для тебя альтернативы. Кстати, на вашей карте отсутствует тип значения; ты хотел сказать unordered_set?   -  person Lightness Races in Orbit    schedule 04.03.2017
comment
@LightnessRacesinOrbit Я понятия не имею, почему std::hash делает это, но да, я хочу хранить итераторы, потому что в них должна быть связь. Если ячейка вставлена ​​в одну, она должна быть вставлена ​​и в остальные и т.д.   -  person Dannyu NDos    schedule 04.03.2017
comment
@Tony D Извините, они должны быть std::unordered_set. Исправлено.   -  person Dannyu NDos    schedule 04.03.2017
comment
@DannyuNDos: О каком объеме мы здесь говорим? Вместо этого вы можете использовать std::set, если это всего несколько элементов - вы [вероятно] не заметите разницы.   -  person Lightness Races in Orbit    schedule 04.03.2017
comment
@LightnessRacesinOrbit Не std::unordered_set? Но это будет стоить раз. Да, элементов будет немного, но я ищу миллиарды паттернов «Игры жизни» Конвея. Небольшое улучшение временной сложности — это просто здорово.   -  person Dannyu NDos    schedule 04.03.2017
comment
@TonyD, LightnessRacesinOrbit Я отредактировал вопрос для более конкретного объяснения.   -  person Dannyu NDos    schedule 04.03.2017
comment
Вместо того, чтобы хранить итератор в качестве ключа, вы, вероятно, могли бы сохранить std::unordered_map::value_type*, полученный с помощью &*iter. Или, по крайней мере, вы можете реализовать хэш итератора, хэшируя указатель &*iter.   -  person Igor Tandetnik    schedule 04.03.2017
comment
@IgorTandetnik Это должно сработать. Большое спасибо! Почему бы вам не опубликовать это как фактический ответ?   -  person Dannyu NDos    schedule 04.03.2017
comment
@DannyuNDos: Небольшое улучшение временной сложности — это просто здорово Нет, только если вы знаете, что вам это нужно. Если у вас есть только несколько элементов, нет причин использовать хэш-карту, особенно когда вы… понимаете… не можете. Поиск в дереве из четырех или пяти элементов может быть даже быстрее, чем вычисление хэша! (Возможно, нет, но вы поняли.) Понимание алгоритмической сложности важно, но вы также должны учитывать постоянный фактор в своей программе. Измеряйте фактическую производительность.   -  person Lightness Races in Orbit    schedule 04.03.2017


Ответы (1)


Короткая история: это не сработает (очень хорошо)(*1). Большинство операций, которые вы, вероятно, собираетесь выполнять на карте cells_coor, сделают недействительными любые итераторы (но не указатели, как я узнал) на его элементы.

Если вы хотите сохранить то, что я бы назвал разными «представлениями» в некоторой коллекции, то базовый контейнер, в котором хранятся фактические данные, должен либо не изменяться, либо не должен делать недействительными его итераторы (например, связанный список).

Возможно, я что-то упускаю, но почему бы не оставить 9 наборов ячеек для подсчета соседей и 2 набора ячеек для включения/выключения? (*2) Другими словами: зачем вам эта карта? (*3)


(*1): карта делает недействительными только указатели и итераторы, когда происходит повторное хэширование. Вы можете проверить это:

// Before inserting
(map.max_load_factor() * map.bucket_count()) > (map.size() + 1)

(*2): 9 наборов можно сократить до 8: если ячейка (x, y) не входит ни в один из 8 наборов, то она будет в 9-м наборе. Таким образом, хранить эту информацию не нужно. То же самое для включения/выключения: достаточно хранить включенные ячейки. Все остальные выключены.

(*3): Доступ к количеству соседей без использования карты, а только с наборами ячеек, что-то вроде псевдокода:

unsigned number_of_neighbours(Cell const & cell) {
  for (unsigned neighbours = 9; neighbours > 0; --neighbours) {
    if (set_of_cells_with_neighbours(neighbours).count() == 1) {
      return neighbours;
    }
  }
  return 0;
}

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

person Daniel Jour    schedule 04.03.2017
comment
О боже. Я пропустил это... Хорошо, для объяснения мне нужно быстро получить доступ к ячейкам по координате, количеству соседей или состоянию ВКЛ/ВЫКЛ. cells_coor, cells_neigh и cells_onoff соответствуют им соответственно. - person Dannyu NDos; 04.03.2017
comment
Верно, но какую информацию вы получаете от карты, которую вы не могли бы получить также и от наборов? Количество соседей? Посмотрите на каждый из 9 наборов. Вкл выкл? Если ячейка не включена, значит она выключена, не так ли? Кстати, вы можете оставить только один на съемочной площадке и 8 наборов для соседей. - person Daniel Jour; 04.03.2017
comment
Количество соседей и состояние ВКЛ/ВЫКЛ принадлежат к координате... Если я буду хранить координаты, количество соседей и состояния ВКЛ/ВЫКЛ по отдельности, я не смогу узнать, что чему принадлежит. - person Dannyu NDos; 04.03.2017
comment
Может быть, мне стоит попробовать сохранить `std::reference_wrapper‹decltype(cells_coor)::value_type›? - person Dannyu NDos; 04.03.2017
comment
@Т.С. Действительно, я этого не знал. Исправил эту часть, хотя мне придется подумать о том, может ли исходный подход быть лучше, чем мое предложение. - person Daniel Jour; 06.03.2017