LINUX.ORG.RU

Быстрый ассоциативный массив для чтения

 ,


3

5

Задача сделать констаный асоциативный массив строка -> число для С++. Т.е. после создания он изменяться не будет.

Далее

using обьект = "констаный асоциативный массив строка -> число";

Условия:

  • Обьект создается только один раз
  • Можно чтоб обьект был инициализирован до вызова main
  • Ключи (строки) и значения (числа) известны уже на этапе компиляции и не изменяются с этого момента.
  • Ключи как и значения уникальны (т.е. значения не повторяются, а одному ключу соответствует только одно значение)
  • На 90% ключей состоят ровно из 3 символов: большие буквы A-Z и/или цифры 0-9. Остальные 10% ключей не превышают в длинне 15 символов (однако могут иметь также и маленькие буквы a-z).
  • Ключи можно вычислять из строки, главное чтоб выполнялось:
    f(x) != f(y) для (x != y), (x, y) на всем множестве ключей
    f(x) == f(x) для x на всем множестве ключей
    
  • Приоритетом является быстрый доступ к значению по точному ключу
  • Требуется механизм проверки присутствует ли ключ (т.е. входные данные не всегда валидны)
  • Необходима возможность добавления новых пар ключ-значение в последующих компиляциях (ну, чтоб по крайней мере это не было адски сложно)
  • Стандарт C++14 (и можно использовать фичи из C++17)

Желательно (но не обязательно):

  • Сборка обьекта в compile-time (напр. constexpr)
  • Возможность итерации по всем ключам обьекта
  • Чтоб значения не были слишком сильно разбросаны

Чем можно пренебречь:

  • Размером конечного обьекта (может быть ну очень большим)
  • Временем компиляции (т.е. может быть адская шаблоно-лапша, главное чтоб соответствовало условиям выше)

Сейчас, чтоб сильно не заморачиватся, используется

using namespace std::string_view_literals;

const std::unordered_map<std::string_view, int> map{
    { "key"sv, 42 },
    // ...
};

Однако появилось время покопаться, по этому прошу подсказать, как можно улучшить?

Т.е. было бы прикольно даже иметь хеш-функцию f(str) -> int которая бы гарантировала отсутствие коллизий и не слишком разбросанные значения.

Тебе нужен gperf

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

annulen ★★★★★
()
Ответ на: комментарий от xaizek

Прикольно, но re2c в обычном режиме делает то же самое, но у него еще есть оптимизированный режим -b (а так же поддержка сопоставления с регулярками вместо констант, но это уже другая история)

annulen ★★★★★
()
Ответ на: комментарий от annulen

Зато без дополнительного шага сборки и зависимости, но генерацию if-else с константами, наверное, не обгонит.

xaizek ★★★★★
()
Ответ на: комментарий от xaizek

зависимости

Сгенерированные файлы можно распространять с кодом, например ninja так делает

annulen ★★★★★
()

Можешь использовать способ, как я использовал для написании compile-time рефлексии.

Смотри, первым делом я реализовал compile-time счетчик: структура, объявление в классе и способ работы с ним

Суть работы: так как наш counter<N> является ребенком от counter<N-1> то при вызове counter<> (в моем случае N по умолчанию 255) компилятор будет спускаться до каждого предка пытаясь проверить есть ли он, тогда counter<>::value - последнее значение счетчика, тогда делаем что-то на подобии counter<counter<>::value + 1> - увеличили счетчик на единицу, далее.

Далее, пусть у нас к каждому значению счетчика будет идти функция, которая содержит compile-time строку. У меня что-то подобное реализовано тут

После этого, надо «пройтись» по всем таким функциям и сформировать кортеж (tuple) из ct строк. Это реализовано конечно же здесь ( Правда он у меня проходит по всем предкам класса, тебе это не надо будет делать)

Далее ты можешь использовать поиск по сompile-time строкам (так как только у одинаковых строк будет один и тот-же тип, то можно просто искать по типам данных), и получать индекс, что и будет уникальным номер этой строки

И все это будет ЗАКОМПАЙЛТАЙМ Sic!

maxis11
()
Ответ на: комментарий от maxis11

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

maxis11
()

Можно использовать обычный отсортированный (по ключу) массив пар и бинарный поиск. Скорость поиска элемента возможно будет даже выше, чем у std::unordered_map из-за локальности данных. Также итерация по элементам будет очень быстрой. Думаю такой массив вполне можно построить в compile-time.

m0rph ★★★★★
()

gperf или другой генератор совершенных хеш-функций, как уже сказано.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.