2015-08-04 20 views
5

Ho un enum di direzione con 6 voci di direzione (N, NE, SE, S, SW, NW) che sono fondamentalmente i bordi di un nodo in un grafico. Ho spesso bisogno di iterare su tutti i vicini che sono attualmente eseguiti ripetendo da 0 a 5 usando solo un int.Iterate over direction enum

A volte è necessario anche iterare da ad es. 2-> 1 avvolgimento attorno al quale si fa attualmente iterando da 2 a 2 + 5 e assumendolo mod 6 in uso.

C'è qualcosa che posso fare per renderlo più sicuro/più facile da usare senza perdere le prestazioni? Il loop con intervallo intero fissato può essere srotolato, inline ecc vettore basato approcci non possono (utilizzando un vettore const statico all'interno del enum)

già hanno l'enumerazione in uno schema del tipo:

struct Direction{ 
    enum Type{ 
     N, NE, ... 
    } 
    unsigned COUNT = ...; 
    Type t_; 
    operator Type(){return t_;} 
    Direction(Type t):t_(t){assert(N<=t<COUNT);} 
    explicit Direction(unsigned t):t_(t%COUNT){} 
    static Direction fromUInt(unsigned t){return Direction(Type(t));} 
} 

Quindi quello che mi piacerebbe è avere iteratori che permettano di iterare in modo efficiente sull'intero insieme e consentire anche questo per punti di partenza arbitrari nel qual caso l'iteratore si avvolge.

Come si può scrivere questo? Non riesco a capire niente come avrei ad esempio start = end per un intero ciclo che non sarebbe valido. O dovrei avere start = givenStartType, end = start + COUNT e fare un modulo su ogni iteratore deref?

N. C++ 11 ammessi purtroppo

+0

Nessun codice funzionante nella domanda. Il codice postato è semplicemente la struttura di un enum a cui si riferisce la domanda. Ciò che è pubblicato sotto il codice sta iniziando idee non più – Flamefire

+0

Mi dispiace, mio ​​errore! –

risposta

2

EDIT in risposta alla chiarificazione

La tua idea di prendere il modulo iteratore COUNT su ogni dereference è un buon compromesso. Vedere l'iteratore al rovescio/combinazione iterabile sotto. Ho controllato l'output del gruppo dopo aver compilato clang con -O3. Il compilatore ha srotolato il ciclo. L'output è 2 1 0 5 4 3. È possibile implementare un iteratore in avanti o impostare la direzione come parametro. Puoi anche trasformarlo in un modello sopra il tipo enum.

Purtroppo, dal punto di vista della sintassi utilizzo, non credo che questo si acquista più di tanto nel corso di un do - while ciclo come dimostra l'altra risposta - almeno non prima di C++ 11. Nasconde le varie variabili dell'indice e aiuta a evitare errori con loro, ma è molto più dettagliato.

#include <iostream> 

struct Direction { 
    enum Type {N, NE, SE, S, SW, NW}; 

    static const unsigned COUNT = 6; 

    Type t_; 

    operator Type() { return t_; } 
    Direction(Type t) : t_(t) { } 
    explicit Direction(unsigned t) : t_(Type(t % COUNT)) { } 
}; 

struct ReverseIterable { 
    const unsigned  start_; 

    struct iterator { 
     unsigned  offset_; 

     explicit iterator(unsigned offset) : offset_(offset) { } 

     Direction operator *() { return Direction(offset_); } 
     iterator& operator++() { --offset_; return *this; } 

     bool operator ==(const iterator &other) 
      { return offset_ == other.offset_; } 
     bool operator !=(const iterator &other) { return !(*this == other); } 
    }; 

    explicit ReverseIterable(Direction start) : start_(start) { } 

    iterator begin() { return iterator(start_ + Direction::COUNT); } 
    iterator end() { return iterator(start_); } 
}; 

int main() 
{ 
    ReverseIterable  range = ReverseIterable(Direction::SE); 

    for (ReverseIterable::iterator iterator = range.begin(); 
     iterator != range.end(); ++iterator) { 

     std::cout << (int)*iterator << " "; 
    } 
    std::cout << std::endl; 

    return 0; 
} 

In C++ 11, il ciclo potrebbe essere:

for (Direction direction : ReverseIterable(Direction::SE)) 
     std::cout << (int)direction << " "; 
    std::cout << std::endl; 

Probabilmente si può (? Ab) usare una macro per ottenere qualcosa di simile in C++ 98.

Ho mantenuto la risposta originale qui sotto per il momento, perché semplifica la manutenibilità se la definizione enum può cambiare e perché consente intervalli sparsi. Un iteratore molto simile può essere implementato sopra di esso.


risposta originale incentrata sulla sicurezza

Questo potrebbe essere eccessivo completo per il vostro scopo, e potrebbe essere poco adatto per un motivo mi limiterò a descrivere più in basso.Tuttavia, è possibile utilizzare questa libreria (disclaimer: io sono l'autore): https://github.com/aantron/better-enums di scrivere codice come questo:

#include <iostream> 
#include <enum.h> 

ENUM(Direction, int, N, NE, SE, S, SW, NW) 

int main() 
{ 
    size_t iterations = Direction::_size(); 
    size_t index = 2; 

    for (size_t count = 0; count < iterations; ++count) { 
     std::cout << Direction::_values()[index] << " "; 
     index = (index + 1) % Direction::_size(); 
    } 
    std::cout << std::endl; 

    return 0; 
} 

uscita:

SE S SW NW N NE 

(I valori erano int enumerazioni -sized, ma erano convertito in stringhe per l'output solo su std::cout).

Questo mostra un'iterazione sull'intero insieme, con un punto di partenza arbitrario. Puoi farlo andare avanti o indietro e templatizzarlo su qualsiasi enumerazione.

Penso che usare modulo come nella tua domanda sia una buona idea. Questo codice fornisce solo alcune informazioni riflettenti sul numero di costanti nell'enumerazione e le inserisce in un array.

Il motivo per cui questo potrebbe non essere adatto è che, poiché non si utilizza C++ 11, l'array Direction::_values() verrà inizializzato all'avvio del programma. Penso che lo svolgimento del ciclo possa ancora accadere, ma il compilatore non sarà in grado di fare nulla con il contenuto dell'array. La matrice verrà comunque allocata staticamente, ma gli elementi non saranno disponibili durante la compilazione.

caso più tardi avere la possibilità di utilizzare C++ 11, l'array sarà sostanzialmente lo stesso di un staticamente-inizializzato int[6] (in realtà, Direction[6], dove Direction è un letterale struct tipo).

(In realtà, suppongo di poter esporre un array di int s invece di Direction s, che sarebbe stato inizializzato staticamente anche in C++ 98).

+0

Ho già 2 costruttori che prendono gli argomenti interi convertendoli in Direzione. Quindi non vedo alcun beneficio qui. Soprattutto perché ho bisogno di un paio di altre variabili (almeno il conteggio e l'indice) Vedi la domanda modificata. – Flamefire

+0

Capisco. Penso che io (e l'altro rispondente) ci siamo concentrati sulla parte di sicurezza della tua domanda, nel senso (presumibilmente) di resistenza al cambiamento della definizione dell'enum. Modificherò la domanda per tracciare un iteratore. – antron

+0

In realtà sembra che la tua domanda abbia già l'idea giusta per gli iteratori: fai un modulo su ogni dereferenziazione. Come ho detto, penso che sia la cosa giusta da fare. Il punto era spostare il codice precedente in una funzione o tagliarlo in iteratori.Daremo comunque un esempio, ma è sufficiente "fare modulo su ogni dereferenziazione" come risposta? Dal momento che hai già 'COUNT' e sembra che stia assumendo un intervallo denso, qual è la preoccupazione rimanente per la sicurezza? – antron

0

Se hai intenzione di evitare una libreria personalizzata, l'approccio che ho visto generalmente usato è qualcosa di simile:

enum Direction 
{ 
    SE, 
    S, 
    SW, 
    NW, 
    N, 
    NE, 

    DIRECTION_FIRST = SE, 
    DIRECTION_LAST = NE, 
} 

quindi è possibile utilizzare DIRECTION_FIRST e DIRECTION_LAST per scorrere correttamente su tutta la gamma. C'è ancora spazio per errori se qualcuno aggiunge valori all'enumerazione senza aggiornare gli endpoint di iterazione, ma la sua centralizzazione nell'enumerazione dovrebbe renderlo meno probabile.

Ora, se si assumono una direzione di partenza arbitrario start si potrebbe iterare in questo modo:

Direction current = start; 
do 
{ 
    // Do stuff with current... 

    current = (current + 1) % DIRECTION_LAST; 
} while(current != start); 

(La logica sarebbe un po 'più complessa, anche se ancora possibile, se il vostro enum non inizia a 0. Probabilmente è l'unica volta in cui avresti bisogno di usare DIRECTION_FIRST.)