繁体   English   中英

迭代方向枚举

[英]Iterate over direction enum

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

有时候还需要从例如2-> 1的环绕中进行迭代,其中当前通过从2-> 2 + 5迭代并在使用时将其调整为6来完成。

有什么我可以做的更安全/更容易使用而不会失去性能? 具有固定整数范围的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允许

编辑以回应澄清

你想在每个取消引用上采用迭代器模数COUNT是一个很好的想法。 请参阅下面的反向迭代器/可迭代组合。 我用clang -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 -sized枚举,但转换为字符串,仅输出到std::cout )。

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

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

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

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

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

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

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开始,那么逻辑会有点复杂,但仍然可能。这可能是你唯一需要使用DIRECTION_FIRST 。)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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