Карта из целых диапазонов в произвольные одиночные целые числа

Работая в С++ в среде Linux, у меня есть ситуация, когда определено число целочисленных диапазонов, а целые входы сопоставляются с различными произвольными целыми числами, на основе которых они попадают. Ни один из диапазонов не перекрывается, и они не всегда смежны.

"Самый простой" способ решить эту проблему - это набор if-операторов для каждого диапазона, но количество диапазонов, их границ и целевых значений может варьироваться, поэтому if-statements не поддерживаются.

Например, диапазоны могут быть [0, 70], называемые r_a, [101, 150], называть его r_b и [201, 400], называть его r_c. Входы на карте r_a равны 1, на карте r_b - 2, а r_c - на 3. Все, что не в r_a, r_b, r_c, отображается на 0.

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

Есть ли лучший способ выполнить сопоставление, чем алгоритм на основе бинарного поиска? Еще лучше, есть ли еще библиотека С++, которая делает это уже?

Ответы

Ответ 1

Я бы использовал очень простую вещь: std::map.

class Range
{
public:
  explicit Range(int item);  // [item,item]
  Range(int low, int high);  // [low,high]

  bool operator<(const Range& rhs) const
  {
    if (mLow < rhs.mLow)
    {
      assert(mHigh < rhs.mLow); // sanity check
      return true;
    }
    return false;
  } // operator<

  int low() const { return mLow; }
  int high() const { return mHigh; }

private:
  int mLow;
  int mHigh;
}; // class Range

Тогда пусть имеет отображение:

typedef std::map<Range, int> ranges_type;

И напишите функцию, которая выполняет поиск на этой карте:

int find(int item, const ranges_type& ranges)
{
  ranges_type::const_iterator it = ranges.lower_bound(Range(item));
  if (it != ranges.end() && it->first.low() <= item)
    return it->second;
  else
    return 0; // No mapping ?
}

Основные преимущества:

  • Будет проверять, что диапазоны эффективно не перекрываются при вставке в набор (вы можете сделать это так, чтобы он был только в режиме отладки)
  • Поддерживает издание диапазонов "на лету"
  • Быстрый поиск (двоичный поиск)

Если диапазоны заморожены (даже если их значения не указаны), вы можете использовать Loki::AssocVector, чтобы уменьшить накладные расходы на память и немного улучшить производительность (в основном это отсортированный вектор с интерфейсом карты).

Ответ 2

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

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

Например, это один из возможных подходов. Предположим, что мы сопоставляем диапазон [ int, int ] с значением int. Представим наши диапазоны как замкнутые диапазоны, т.е. Если исходный диапазон [0, 70], рассмотрим вместо этого диапазон [0, 71). Кроме того, позвольте использовать значение 0 как "зарезервированное" значение, которое означает "отсутствие сопоставления" (как вы просили в своем вопросе)

const int EMPTY = 0;

Все, что вам нужно сделать, это объявить карту от int до int:

typedef std::map<int, int> Map;
Map map;

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

map[0] = r_a;
map[71] = EMPTY;

map[101] = r_b;
map[251] = EMPTY;

map[260] = r_c; // 260 adjusted from 201
map[401] = EMPTY;

(Я скорректировал ваш последний диапазон, так как в вашем исходном примере он перекрывал предыдущий диапазон, и вы сказали, что ваши диапазоны не перекрываются).

Это для инициализации.

Теперь, чтобы определить, где заданное значение i отображает все, что вам нужно сделать, это

Map::iterator it = map.upper_bound(i);

Если it == map.begin(), то i не находится в любом диапазоне. В противном случае do

--it;

Если it->second (для декрементированного it) - EMPTY, то i не находится в любом диапазоне.

Объединенная проверка пропусков может выглядеть следующим образом

Map::iterator it = map.upper_bound(i);
if (it == map.begin() || (--it)->second == EMPTY)
  /* Missed all ranges */;

В противном случае it->second (для декрементированного it) - ваше отображаемое значение

int mapped_to = it->second;

Обратите внимание, что если исходные диапазоны "касались", как в [40, 60] и [61, 100], тогда диапазоны с закрытым открытием будут выглядеть как [40, 61) и [61, 101), что означает, что значение 61 будет отображается дважды во время инициализации карты. В этом случае важно удостовериться, что значение 61 отображается на нужное значение назначения, а не на значение EMPTY. Если вы сопоставляете диапазоны, как показано выше, в порядке слева (справа), то он будет работать сам по себе.

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


Если вы хотите, вы можете добавить элемент "guard" на карту во время инициализации

map[INT_MIN] = EMPTY;

(это соответствует "отрицательной бесконечности" ), и проверка "пропустить" станет проще

Map::iterator it = map.upper_bound(i);

assert(it != map.begin());
if ((--it)->second == EMPTY)
  /* Missed all ranges */;

но это только вопрос личных предпочтений.

Конечно, если вы хотите вернуть 0 для не отображаемых значений, вам вообще не нужно выполнять какие-либо проверки. Просто возьмите it->second из декрементированного итератора, и вы закончите.

Ответ 3

Не хватит ли простого массива? Вы не говорите, сколько у вас элементов, но, безусловно, самая быстрая структура данных - это простой массив.

Если диапазоны:

  • 0..9 → 25
  • 10..19 → 42

Тогда массив будет выглядеть следующим образом:

[25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42]

Ответ 4

У вас могут быть два отсортированных массива: один для нижних границ, один для верхних границ. Используйте std::lower_bound(lower_bound_array, value) и std::upper_bound(upper_bound_array, value). Если индекс обоих результатов одинаковый, return index + 1. В противном случае return 0.

Если возвращаемые индексы совпадают, это означает, что значение >= нижняя граница и < верхняя граница. Если они этого не сделают, то вы находитесь между диапазонами.

Ответ 5

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

Ответ 6

Простой Связанный список, содержащий записи диапазона, должен быть достаточно быстрым, даже для 50-100 диапазонов. Более того, вы можете реализовать Список пропусков, на верхних границах, чтобы ускорить эти запросы диапазона. Еще одна возможность - Дерево интервала.

В конечном итоге я бы выбрал самый простой: двоичный поиск.

Ответ 7

В вашем примере пробелы перекрываются, но вопрос говорит, что они не будут. Я предполагаю, что диапазон - опечатка. Вы могли бы сохранить хранилища в массиве и использовать индексы в качестве диапазонов. Это довольно легко, но уродливо и не очень удобно. Вам нужно будет инициализировать массив до 0, затем для каждого диапазона, перебрать эти индексы и установить каждый индекс в значение назначения. Очень уродливое, но постоянное время поиска, поэтому, может быть, полезно, если числа не становятся слишком высокими, а диапазоны не меняются очень часто.

Ответ 8

Запишите пределы в set (или map). Когда вы вызываете insert, вы получите возвращаемое значение, которое является парой. Итератор и логическое. Если логическое значение true, то создается новый элемент, который вы должны удалить позже. После этого выполните один шаг с итератором и посмотрите, что вы нашли.

http://www.cplusplus.com/reference/stl/set/insert/ См. Возвращаемое значение

Ответ 9

Это 1-мерный пространственный индекс. Например, двоичное дерево в стиле Quadtree, и есть несколько других широко используемых методов.