Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 e.g. 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 e.g. 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

like image 871
Flamefire Avatar asked Oct 31 '22 22:10

Flamefire


1 Answers

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 ints instead of Directions, which would be statically initialized even in C++98).

like image 200
antron Avatar answered Nov 09 '22 12:11

antron