Преобразование наборов целых чисел в диапазоны
Какой самый идиоматический способ преобразования набора целых чисел в набор диапазонов?
например. учитывая набор {0, 1, 2, 3, 4, 7, 8, 9, 11} Я хочу получить {{0,4}, {7,9}, {11,11}}.
Скажем, мы преобразуем из std::set<int>
в std::vector<std::pair<int, int>>
.
Я рассматриваю диапазоны как включенные с обеих сторон, так как это более удобно в моем случае, но я также могу работать с диапазонами с открытым контуром, если это необходимо.
Я написал следующую функцию, но я чувствую, как изобретать колесо.
Пожалуйста, скажите, может быть, что-то в STL или повысить для этого.
typedef std::pair<int, int> Range;
void setToRanges(const std::set<int>& indices, std::vector<Range>& ranges)
{
Range r = std::make_pair(-INT_MAX, -INT_MAX);
BOOST_FOREACH(int i, indices)
{
if (i != r.second + 1)
{
if (r.second >= 0) ranges.push_back(r);
r.first = i;
}
r.second = i;
}
ranges.push_back(r);
}
Ответы
Ответ 1
Я не думаю, что что-то в STL или Boost это делает.
Одна вещь, которую вы можете сделать, - сделать ваш алгоритм немного более общим:
template<class InputIterator, class OutputIterator>
void setToRanges(InputIterator first, InputIterator last, OutputIterator dest)
{
typedef std::iterator_traits<InputIterator>::value_type item_type;
typedef typename std::pair<item_type, item_type> pair_type;
pair_type r(-std::numeric_limits<item_type>::max(),
-std::numeric_limits<item_type>::max());
for(; first != last; ++first)
{
item_type i = *first;
if (i != r.second + 1)
{
if (r.second >= 0)
*dest = r;
r.first = i;
}
r.second = i;
}
*dest = r;
}
Использование:
std::set<int> set;
// insert items
typedef std::pair<int, int> Range;
std::vector<Range> ranges;
setToRanges(set.begin(), set.end(), std::back_inserter(ranges));
Вы также должны использовать термин interval
вместо range
, потому что последний в STL-выражении означает "любую последовательность объектов, к которым можно получить доступ через итераторы или указатели" (источник).
Наконец, вам, вероятно, стоит взглянуть на Boost Interval Arithmetic Library, который в настоящее время находится в поле зрения для включения Boost.
Ответ 2
Теперь можно использовать interval_set из Boost.ICL(Boost > 1.46)
#include <set>
#include <iostream>
#include <algorithm>
#include <boost/icl/discrete_interval.hpp>
#include <boost/icl/closed_interval.hpp>
#include <boost/icl/interval_set.hpp>
typedef std::set<int> Set;
typedef boost::icl::interval_set<int> IntervalSet;
void setToInterval(const Set& indices, IntervalSet& intervals)
{
Set::const_iterator pos;
for(pos = indices.begin(); pos != indices.end(); ++pos)
{
intervals.insert(boost::icl::construct<boost::icl::discrete_interval<int> >(*pos, *pos, boost::icl::interval_bounds::closed()));
}
}
int main()
{
std::cout << ">>Interval Container Library Rocks! <<\n";
std::cout << "----------------------------------------------------\n";
Set indices = {0, 1, 2, 3, 4, 7, 8, 9, 11};
IntervalSet intervals;
setToInterval(indices, intervals);
std::cout << " intervals joined: " << intervals << "\n";
return 0;
}
Вывод:
intervals joined: {[0,4][7,9][11,11]}
Ответ 3
Я не исправлю сжатое решение, но альтернативный алгоритм.
Сохраняйте свои объекты в битвекторе - O (n), если вы знаете максимальный элемент в начале и предопределите вектор.
Переведите этот вектор в вектор флагов точки перехода - исключительный или битвектор с битовой версией самого себя. Немного неудобно на границе слов, но все же O (n). Логично, что вы получаете новый ключ в старом max + 1 (переход обратно к нулям после исчерпания всех ваших ключей), поэтому рекомендуется учитывать это при преассоляции вектора.
Затем, итерация через битвектор, нахождение заданных бит. Первый бит набора указывает начало диапазона, второй - конец, третий - начало следующего диапазона и так далее. Может быть полезной следующая функция бит-возиться (предполагая 32-битный int)...
int Low_Bit_No (unsigned int p)
{
if (p == 0) return -1; // No bits set
int l_Result = 31;
unsigned int l_Range = 0xffffffff;
unsigned int l_Mask = 0x0000ffff;
if (p & l_Mask) { l_Result -= 16; } else { l_Mask ^= l_Range; }
l_Range &= l_Mask;
l_Mask &= 0x00ff00ff;
if (p & l_Mask) { l_Result -= 8; } else { l_Mask ^= l_Range; }
l_Range &= l_Mask;
l_Mask &= 0x0f0f0f0f;
if (p & l_Mask) { l_Result -= 4; } else { l_Mask ^= l_Range; }
l_Range &= l_Mask;
l_Mask &= 0x33333333;
if (p & l_Mask) { l_Result -= 2; } else { l_Mask ^= l_Range; }
l_Mask &= 0x55555555;
if (p & l_Mask) { l_Result -= 1; }
return l_Result;
}
Ответ 4
Я бы использовал adjacent_find
с предикатом, который определяет "смежность" как два элемента, которые не являются последовательными. Это решение не зависит от INT_MAX. Все еще чувствует себя довольно неуклюжим.
bool notSequential(int a, int b) { return (a + 1) != b; }
void setToRanges(const std::set<int>& indices, std::vector<Range>& ranges)
{
std::set<int>::iterator iter = indices.begin();
std::set<int>::iterator end = indices.end();
int first;
while (iter != end)
{
first = *iter;
iter = std::adjacent_find(iter, end, notSequential);
if (iter != end)
{
ranges.push_back(std::make_pair(first, *iter));
++iter;
}
}
ranges.push_back(std::make_pair(first, *--iter));
}
Это более чем необходимо для тестирования end
. adjacent_find
никогда не сможет вернуть последний элемент списка, поэтому инкрементный итератор никогда не будет end
и, следовательно, его все равно можно разыменовать. Его можно было бы переписать как:
void setToRanges(const std::set<int>& indices, std::vector<Range>& ranges)
{
std::set<int>::iterator iter = indices.begin();
std::set<int>::iterator end = indices.end();
if (iter == end) return; // empty set has no ranges
int first;
while (true)
{
first = *iter;
iter = std::adjacent_find(iter, end, notSequential);
if (iter == end) break;
ranges.push_back(std::make_pair(first, *iter++));
}
ranges.push_back(std::make_pair(first, *--iter));
}