[英]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_FIRST
和DIRECTION_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.