将整数集转换为范围

时间:2010-02-21 11:54:23

标签: c++ algorithm set range

将一组整数转换为一组范围的最惯用的方法是什么?

E.g。给定{0,1,2,3,4,7,8,9,11}我想得到{{0,4},{7,9},{11,11}}。

假设我们正在从std::set<int>转换为std::vector<std::pair<int, int>>。 我认为Ranges在双方都是包容性的,因为在我的情况下它更方便,但如果有必要,我也可以使用开放式范围。

我写过以下功能,但我觉得要重新发明轮子。 请告诉我们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);
}

4 个答案:

答案 0 :(得分:4)

现在可以使用Boost.ICL中的interval_set(Boost&gt; 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]}

答案 1 :(得分:3)

我认为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用语中的后者意味着“可以通过迭代器或指针访问的任何对象序列”(source )。

最后,您应该看看Boost Interval Arithmetic Library,目前正在审核Boost包含。

答案 2 :(得分:1)

我担心没有收缩包裹的解决方案,而是另一种算法。

将项目存储在位向量中 - 如果您知道开头的最大项目并预先分配向量,则为O(n)。

将该向量转换为转换点标志的向量 - 异或 - 具有自身的位移版本的位向量。稍微偏僻于字边界,但仍然是O(n)。从逻辑上讲,你在旧的max + 1处获得一个新密钥(在所有密钥耗尽后转换回零),因此在向量的预分配中允许这样做是个好主意。

然后,迭代通过位向量查找设置位。第一个设置位表示范围的开始,第二个设置位表示下一个范围的开始,依此类推。以下bit-fiddling函数(假设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;
}

答案 3 :(得分:1)

我将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));
}