简体   繁体   中英

Iterate over direction enum

I have a direction enum with 6 direction entries (N, NE, SE, S, SW, NW) which are basicly the edges of a node in a graph. I often need to iterate over all neighbours which is currently done by iterating from 0->5 using an int only.

Sometimes one also needs to iterate from eg 2->1 wrapping around which is currently done by iterating from 2->2+5 and taking it mod 6 on use.

Is there anything I can to do make that safer/easier to use without losing performance? The for loops with fixed integer range can be unrolled, inlined etc. Vector based approaches can't (using a static const vector inside the enum)

I already have the enum in a scheme like:

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

So what I would like is having iterators that allow iterating efficiently over the whole set and also allow this for arbitrary start points in which case the iterator wraps around.

How could one write this? I can't figure anything out as I would have eg start=end for a whole loop which would be invalid. Or shall I just have start=givenStartType, end=start+COUNT and do a modulo on each iterator deref?

No C++11 allowed unfortunately

EDIT in response to clarification

Your idea of taking the iterator modulo COUNT on each dereference is a good one. See the reverse iterator/iterable combination below. I inspected the assembly output after compiling with clang with -O3 . The compiler unrolled the loop. The output is 2 1 0 5 4 3 . You can implement a forward iterator, or make the direction a parameter. You can also make this into a template over the enum type.

Unfortunately, from the point of view of usage syntax, I don't think this buys you all that much over a do - while loop as shown by the other answer – at least not before C++11. It does hide the various index variables, and helps you to avoid mistakes with them, but it is much more verbose.

#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, the loop could be:

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

You can probably (ab?)use a macro to get something similar in C++98.

I've kept the original answer below for the time being, because it eases maintainability if the enum definition can change, and because it allows sparse ranges. A very similar iterator can be implemented on top of it.


Original answer focused on safety

This might be complete overkill for your purpose, and it might be ill-suited for a reason I will describe further down. However, you can use this library (disclaimer: I am the author): https://github.com/aantron/better-enums to write code like this:

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

Output:

SE S SW NW N NE

(The values were int -sized enums, but were converted to strings for output to std::cout only).

This shows an iteration over the whole set, with an arbitrary starting point. You could make this go forwards or backwards, and templatize it over any enum.

I think using modulo as in your question is a good idea. This code just gives you some reflective information about the number of constants in the enum, and also puts them in an array.

The reason this may be ill-suited is that, since you are not using C++11, the array Direction::_values() will be initialized at program start-up. I think loop unrolling can still happen, but the compiler won't be able to do anything with the contents of the array. The array will still be allocated statically, but the elements won't be available during compilation.

Should you later have the option to use C++11, the array will be basically the same as a statically-initialized int[6] (actually, Direction[6] , where Direction is a literal struct type).

(Actually, I suppose I could expose an array of int s instead of Direction s, which would be statically initialized even in C++98).

If you're wanting to avoid a custom library, the approach I've generally seen used is something like this:

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

    DIRECTION_FIRST = SE,
    DIRECTION_LAST = NE,
}

Then you can use DIRECTION_FIRST and DIRECTION_LAST to properly iterate over the range. There is still room for error if someone adds values to the enum without updating the iteration endpoints, but centralizing it in the enum should make this less likely.

Now if you assume an arbitrary starting direction start you could iterate like this:

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

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

(The logic would be a bit more complex, though still possible, if your enum doesn't start at 0. That's probably the only time you'd need to use DIRECTION_FIRST .)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM