2010-02-21 19 views
6

¿Cuál es la forma más idiomática de convertir un conjunto de enteros en un conjunto de rangos?Conversión de conjuntos de enteros en rangos

E.g. dado el conjunto {0, 1, 2, 3, 4, 7, 8, 9, 11}, quiero obtener {{0,4}, {7,9}, {11,11}}.

Digamos que estamos convirtiendo de std::set<int> en std::vector<std::pair<int, int>>. Trato los rangos como inclusivos en ambos lados, ya que es más conveniente en mi caso, pero también puedo trabajar con rangos abiertos si es necesario.

He escrito la siguiente función, pero tengo ganas de reinventar la rueda. Por favor, díganos que hay algo en STL o impulso para esto.

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); 
} 
+0

no puedo ver una técnica que es mayor que O (n) para este tipo de cosas. El único aumento en la velocidad sería integrar esto en su género (si está haciendo uno) – dangerstat

+0

@dangerstat: bueno, esto hace surgir otra pregunta: si existe una estructura de datos que pueda almacenar eficientemente la lista de rangos (algún tipo de árbol, supongo?). Actualmente utilizo plain std :: set para almacenar información sobre qué elementos de la lista numerada están seleccionados. –

+0

un conjunto es internamente un árbol – Manuel

Respuesta

3

No creo que haya nada en el STL o Boost que lo haga.

Una cosa que puede hacer es hacer que su algoritmo de un poco más general:

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; 
} 

Uso:

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)); 

También debe considerar el uso del término interval en lugar de range, debido a que la el último en el lenguaje STL significa "cualquier secuencia de objetos a los que se puede acceder a través de iteradores o punteros" (source).

Finalmente, probablemente debería echarle un vistazo al Boost Interval Arithmetic Library, que se encuentra actualmente en revisión para la inclusión de Boost.

+0

Me temo que el algoritmo actual no se puede usar para la versión general: por ejemplo, no funcionará con los tipos sin firmar. La versión inicial al menos explícitamente exigió ints. Segundo, +1 para "intervalo". Esto fue realmente lo que he usado en mi código, pero al publicar aquí decidí cambiarlo a "rango" considerando que el término era más familiar. Tercero, Boost Interval es seguro, pero no me sirve de mucho, ya que todo lo que hago con los rangos es ponerlos en la cláusula WHERE SQL como "DELETE FROM X WHERE id> = interval_start AND id <= interval_end" –

1

No hay solución retráctil, me temo, pero un algoritmo alternativo.

Almacene sus artículos en un bitvector - O (n) si conoce el artículo máximo al inicio y preasigne el vector.

Traducir ese vector en un vector de indicadores de punto de transición - exclusivo - o el bitvector con una versión de desplazamiento de bits de sí mismo. Ligeramente complicado en los límites de las palabras, pero aún así O (n). Lógicamente, obtienes una nueva clave en el antiguo máximo + 1 (la transición vuelve a ceros después de que todas tus claves se hayan agotado), así que es una buena idea permitir eso en la asignación previa del vector.

Luego, itere a través del bitvector encontrando los bits establecidos. El primer bit configurado indica el inicio de un rango, el segundo el final, el tercero el inicio del siguiente rango y así sucesivamente. La siguiente función de bit-tocar el violín (suponiendo 32 int bit) puede ser útil ...

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; 
} 
+0

Interesante idea, el problema es 1) No sé la cantidad de elementos por adelantado y puede ser muy grande; 2) esta subrutina no es un cuello de botella de rendimiento, diciendo "idiomático". Quería más claridad y poder reutilizar el código de la biblioteca; y 3) este algoritmo es el mismo O (n) que la solución inicial pero mucho menos mantenible. –

+0

Pensé que no era realmente serio, solo que estaba aburrido y perdía un poco de tiempo, aunque el truco es código viejo, así que no tanto tiempo. Alguien que busca palabras clave similares puede encontrarlo útil: esa es mi excusa, y me estoy apegando a ella. – Steve314

1

que haría uso de adjacent_find con un predicado que define la "adyacencia" como dos elementos que no son secuenciales. Esta solución no depende de INT_MAX. Todavía se siente un poco torpe.

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

Eso prueba contra end más de lo necesario. adjacent_find nunca puede devolver el último elemento de una lista, por lo que el iterador incrementado nunca será end y, por lo tanto, todavía se puede desreferenciar. Podría ser reescrita como:

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)); 
} 
+0

Muy buena idea, es interesante aprender sobre el algoritmo de búsqueda_cercana, ¡no lo he usado antes! Es bueno que este código no dependa de INT_MAX y funcione en todos los enteros y no solo en los positivos. Por otro lado, el código es más largo y más oscuro, utiliza dos bucles (uno al mismo tiempo y otro dentro de la búsqueda adyacente) en lugar de uno solo, y es necesario definir una función gratuita adicional. –

4

Ahora uno puede usar interval_set de 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; 
} 

de salida:

intervals joined: {[0,4][7,9][11,11]} 
Cuestiones relacionadas