迭代方向枚举

时间:2015-08-04 21:32:28

标签: c++ loops enums iterator

我有一个带有6个方向条目(N,NE,SE,S,SW,NW)的方向枚举,它们基本上是图形中节点的边缘。我经常需要迭代所有邻居,这些邻居目前通过仅使用int从0-> 5进行迭代来完成。

有时人们还需要从例如当前通过从2-> 2 + 5迭代并且在使用时将其取为mod 6来完成2-> 1缠绕。

我能做些什么使得更安全/更容易使用而不会失去性能?具有固定整数范围的for循环可以展开,内联等。基于矢量的方法可以(在枚举中使用静态const向量)

我已经在以下方案中使用了枚举:

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

所以我想要的是允许迭代器允许在整个集合上有效迭代,并允许这个任意起始点,在这种情况下迭代器会回绕。

怎么能写这个?我无法解决任何问题。 start = end表示整个循环无效。或者我应该只有start = givenStartType,end = start + COUNT并在每个迭代器deref上做一个模数?

不幸的是,不允许使用C ++ 11

2 个答案:

答案 0 :(得分:2)

编辑以回应澄清

你想在每个取消引用上使用迭代器模COUNT是一个很好的想法。请参阅下面的反向迭代器/可迭代组合。在使用-O3进行编译后,我检查了程序集输出。编译器展开循环。输出为2 1 0 5 4 3。您可以实现前向迭代器,或将方向作为参数。您还可以将其转换为枚举类型的模板。

不幸的是,从使用语法的角度来看,我不认为这会在do - while循环中为你买得那么多,如另一个答案所示 - 至少在之前没有C ++ 11。它确实隐藏了各种索引变量,并帮助您避免错误,但它更加冗长。

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

在C ++ 11中,循环可以是:

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

你可能(ab?)使用宏来获得类似的东西在C ++ 98中。

我暂时保留下面的原始答案,因为如果枚举定义可以更改,并且因为它允许稀疏范围,它可以简化可维护性。可以在它上面实现一个非常相似的迭代器。

原创答案侧重于安全

这可能是完全矫枉过正的目的,而且可能不适合我将进一步描述的原因。但是,您可以使用此库(免责声明:我是作者):https://github.com/aantron/better-enums编写如下代码:

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

输出:

SE S SW NW N NE

(值为int - 大小的枚举,但转换为字符串仅输出到std::cout

这显示了整个集合的迭代,具有任意的起始点。你可以让它前进或后退,并在任何枚举上进行模板化。

我认为在你的问题中使用模数是一个好主意。这段代码只是给你一些关于枚举中常量数量的反射信息,并将它们放在一个数组中。

这可能不合适的原因是,由于您没有使用C ++ 11,因此阵列Direction::_values()将在程序启动时初始化。我认为循环展开仍然可能发生,但编译器将无法对数组的内容做任何事情。该数组仍将静态分配,但在编译期间元素将不可用。

如果您以后可以选择使用C ++ 11,那么该数组将与静态初始化的int[6]基本相同(实际上,Direction[6],其中Direction是文字struct类型)。

(实际上,我想我可以公开int s而不是Direction s的数组,即使在C ++ 98中也会静态初始化。

答案 1 :(得分:0)

如果您想避免使用自定义库,我通常使用的方法就是这样:

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

    DIRECTION_FIRST = SE,
    DIRECTION_LAST = NE,
}

然后,您可以使用DIRECTION_FIRSTDIRECTION_LAST来正确迭代范围。如果有人在不更新迭代端点的情况下向枚举添加值,则仍然存在错误的空间,但是将其集中在枚举中应该会降低这种可能性。

现在,如果您假设一个任意的起始方向start,您可以像这样迭代:

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

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

(如果您的枚举不是从0开始,那么逻辑会有点复杂,但仍有可能。这可能是您唯一需要使用{{1}的时间}。)