簡體   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