2016-06-24 18 views
5

私はこのようになりますPythonで簡単な列挙型を持っている:Python Enumで次の値と前の値をエレガントに見つける方法を教えてください。

私が欲しい
from enum import Enum 

class MyEnum(Enum): 
    #All members have increasing non-consecutive integer values. 
    A = 0 
    B = 2 
    C = 10 
    D = 18 
    ... 

機能pred()MyEnumリターン先行し、(同じようにそれぞれ指定された要素を、成功MyEnumのメンバーのメンバーを与えsucc()the functions of the same name in Haskell)。たとえば、succ(MyEnum.B)pred(MyEnum.D)は両方ともMyEnum.Cを返す必要があります。最初のメンバーでpredの最後のメンバーが呼び出されたときにsuccが呼び出された場合、例外が発生することがあります。

これを行う方法はありません。iter(MyEnum)を呼び出して、値を繰り返して呼び出すことができますが、最初から全体の列挙型を処理する必要があります。私はおそらく自分でこれを達成するために厄介なループを実装することができますが、私はこのサイトに実際のPythonの教祖がいることを知っていますので、私は尋ねます。あなたはEnumクラス内部succpred方法を提供することができます

+0

Pythonで 'enum'の目的はマジックナンバー」の代わりに使用することができるデータの種類を提供することです実際には列挙可能な集合に対応する数学的データ型を提供するわけではありません。したがって、Haskellの 'enum'の目的は若干異なります。いずれにしても、 'myEnum'のメソッドとして' pred'と 'succ'を実装することはできません。 – Bakuriu

+0

その質問の答えの簡単な翻訳を提供しました。 –

答えて

2

注:として使用

class Sequential(Enum): 
    A = 1 
    B = 2 
    C = 4 
    D = 8 
    E = 16 

    def succ(self): 
     v = self.value * 2 
     if v > 16: 
      raise ValueError('Enumeration ended') 
     return Sequential(v) 

    def pred(self): 
     v = self.value // 2 
     if v == 0: 
      raise ValueError('Enumeration ended') 
     return Sequential(v) 

>>> import myenums 
>>> myenums.Sequential.B.succ() 
<Sequential.C: 4> 
>>> myenums.Sequential.B.succ().succ() 
<Sequential.D: 8> 
>>> myenums.Sequential.B.succ().succ().pred() 
<Sequential.C: 4> 

明らかにこれはあなたが実際に計算するための簡単な方法を持っている場合にのみ効率的ですアイテムから次のアイテムまたは前のアイテムへの値。これは常にそうであるとは限りません。

スペースを追加するだけで効率的な一般的なソリューションが必要な場合は、後継関数と先行関数のマッピングを作成できます。あなたは(Enum台無しアップ属性以降)クラスの後に作成を属性としてこれらを追加する必要がありますので、あなたはそれを行うためにデコレータを使用することができます :

として使用
def add_succ_and_pred_maps(cls): 
    succ_map = {} 
    pred_map = {} 
    cur = None 
    nxt = None 
    for val in cls.__members__.values(): 
     if cur is None: 
      cur = val 
     elif nxt is None: 
      nxt = val 

     if cur is not None and nxt is not None: 
      succ_map[cur] = nxt 
      pred_map[nxt] = cur 
      cur = nxt 
      nxt = None 
    cls._succ_map = succ_map 
    cls._pred_map = pred_map 

    def succ(self): 
     return self._succ_map[self] 

    def pred(self): 
     return self._pred_map[self] 

    cls.succ = succ 
    cls.pred = pred 
    return cls 





@add_succ_and_pred_maps 
class MyEnum(Enum): 
    A = 0 
    B = 2 
    C = 8 
    D = 18 

>>> myenums.MyEnum.A.succ() 
<MyEnum.B: 2> 
>>> myenums.MyEnum.B.succ() 
<MyEnum.C: 8> 
>>> myenums.MyEnum.B.succ().pred() 
<MyEnum.B: 2> 
>>> myenums.MyEnum._succ_map 
{<MyEnum.A: 0>: <MyEnum.B: 2>, <MyEnum.C: 8>: <MyEnum.D: 18>, <MyEnum.B: 2>: <MyEnum.C: 8>} 

あなたはおそらくKeyErrorの代わりにカスタム例外が必要ですが、あなたはそのアイデアを得るでしょう。


はおそらく、メタクラスを使用して、最後のステップを統合する方法があるが、それはEnum sがメタクラスを使用して実装されており、それがメタクラスを構成するために些細ではないという単純な事実のためにnotstraightforwardです。あなたのnextprev方法(またはsuccpred)を追加する

+0

あなたの答えは2つの点で非常に役に立ちました。それは私の問題を解決しました。その解決策は非常に複雑で、私は現在、列挙型が私がPythonでやろうとしていることに対するデータ構造の貧弱な選択であると確信しています。あなたのお手伝いをありがとう! – ApproachingDarknessFish

1

十分に簡単です:

def next(self): 
    cls = self.__class__ 
    members = list(cls) 
    index = members.index(self) + 1 
    if index >= len(members): 
     # to cycle around 
     # index = 0 
     # 
     # to error out 
     raise StopIteration('end of enumeration reached') 
    return members[index] 

def prev(self): 
    cls = self.__class__ 
    members = list(cls) 
    index = members.index(self) - 1 
    if index < 0: 
     # to cycle around 
     # index = len(members) - 1 
     # 
     # to error out 
     raise StopIteration('beginning of enumeration reached') 
    return members[index] 
+0

'index'メソッドは、要素が見つからなかったときに' ValueError'を発生させますが、見つからなければ 'find'メソッドを使用して' -1'を取得したいとします。また、このソリューションでは、少なくとも1回、おそらく2回、すべてのメンバーを通過する必要があるため、効率的ではありません。 – Bakuriu

+0

@Bakuriu:この場合、メンバーが常に見つかるため、 '.index()'は大丈夫です。平均してメンバーの半分を通過するだけで、決して2回繰り返されることはありません。 –

+0

いいえ、 'list(cls)'は** all **要素を一度通過してから平均して 'members.index'がメンバーの半分を占めますが、すべてのメンバーが1回以上呼び出されます。簡単な 'for i、enumerate(cls) 'ループを実行し、各反復の前の要素を追跡する方が効率的です。 – Bakuriu

関連する問題