Get next enumerator constant/property
Asked Answered
I

5

9

Lets's say I have an enumerator, is it possible to get the property that follows? So if I had today=Days.Sunday would I be able to do something like tomorrow=today.next()?

example:

class Days(Enum):
     Sunday = 'S'
     Monday = 'M'
     ...
     Saturday = 'Sa'

I know I could use tuples (like below) to do something like tomorrow=today[1], but I was hoping there was something built in or more elegant.

class Days(Enum):
     Sunday = ('S','Monday')
     Monday = ('M','Tuesday')
     ...
     Saturday = ('Sa','Sunday')
Ideation answered 26/10, 2014 at 20:32 Comment(2)
create some kind of iterator?Benumb
Thanks @PadraicCunningham, would that be more efficient than the tuple solution I have? I like yours more, I am just curious if it would save memory or run time :)Ideation
L
11

Absolutely.

Just add the desired functionality to your Days class:

class Days(Enum):

    Sunday = 'S'
    Monday = 'M'
    Tuesday = 'T'
    Wednesday = 'W'
    Thursday = 'Th'
    Friday = 'F'
    Saturday = 'Sa'

    def next(self):
        cls = self.__class__
        members = list(cls)
        index = members.index(self) + 1
        if index >= len(members):
            index = 0
        return members[index]

and in use:

today = Days.Wednesday
print(today.next())
# Days.Thursday

While the above is probably easier to understand, it is possible to do the work once in __init__ by adding a next attribute to each member (and previous while we're at it).

class Days(Enum):
    #
    Sunday = 'S'
    Monday = 'M'
    Tuesday = 'T'
    Wednesday = 'W'
    Thursday = 'Th'
    Friday = 'F'
    Saturday = 'Sa'
    #
    def __init__(self, value):
        if len(self.__class__):
            # make links
            all = list(self.__class__)
            first, previous = all[0], all[-1]
            previous.next = self
            self.previous = previous
            self.next = first

and in use:

>>> Days.Tuesday.next
<Days.Wednesday: 'W'>

>>> Days.Tuesday.previous
<Days.Monday: 'M'>

>>> Days.Saturday.next
<Days.Sunday: 'S'>

>>> Days.Saturday.previous
<Days.Friday: 'F'>

NB Using the this method of attributes means we no longer need the ()s after next/previous.

Laity answered 10/3, 2016 at 0:58 Comment(1)
If you want to avoid creating list you can use itertools.cycle(). See my answer.Airiness
G
2

You can create a dictionary to lookup the next day like so:

In [10]: class Days(Enum):
    Sun = 'Su'
    Mon = 'M'
    Tue = 'Tu'
    Wed = 'W'
    Thu = 'Th'
    Fri = 'F'
    Sat = 'Sa'

In [11]: days = list(Days)

In [12]: nxt = dict((day, days[(i+1) % len(days)]) for i, day in enumerate(days))

Quick test:

In [13]: nxt[Days.Tue]
Out[13]: <Days.Wed: 'W'>

In [14]: nxt[Days.Sat]
Out[14]: <Days.Sun: 'Su'>
Gutta answered 26/10, 2014 at 20:53 Comment(0)
S
0
#ENUM CLASS

#colors
import enum

class Color(enum.Enum):
    turquoise = 1
    indigo = 2
    magenta = 3
    cyan = 4
    teal = 5
    azure = 6
    rose = 7
    amber = 8
    vermillon = 9
    plum = 10
    russet = 11
    slate = 12

    def __iter__(self):
        self.idx_current = self.value
        return self

    def __next__(self):
        if (self.idx_current > 12):
            return None

        self.idx_current = self.idx_current + 1
        return Color (self.idx_current - 1)

#CLIENT CODE
    #iterate colors starting from cyan
    it = iter (Color.cyan)
    while True:
        #print (v.get_id())
        c = next (it)
        if c is None:
            break
        print(c)

#OUTPUT
Color.cyan
Color.teal
Color.azure
Color.rose
Color.amber
Color.vermillon
Color.plum
Color.russet
Color.slate
Subshrub answered 31/7, 2019 at 10:15 Comment(1)
It would make more sense to raise StopIteration rather than return None from __next__().Sacroiliac
F
0

For me that seems like the most elegant solution without additional functions

day = Days.Sunday

day = Days((day.value + 1) % len(Days) + 1) # next day cycled
Favour answered 23/11, 2020 at 13:6 Comment(0)
A
-1

All the provided answers creating a list of all the Enum members, but you actually can avoid it by using the itertools.cycle:

from itertools import cycle

class Days(Enum):
    SUNDAY = 'Su'
    MONDAY = 'Mo'
    TUESDAY = 'Tu'
    WEDNSDAY = 'We'
    THURSDAY = 'Th'
    FRIDAY = 'Fr'
    SATURDAY = 'Sa'
    
    def next(self):
        iterator = cycle(self)
        for member in iterator:
             if member is self:
                 # We have found the current member, so we want to 
                 # return the next member
                 return next(iterator)

# Call it:
tomorrow = DAY.SUNDAY.next()

Note that in this approach, if current member is the last one in your enum, then it will return the first member, because cycle() is cycling your iterable forever.

With the functools.cached_property decorator we can also easily save the result for further reuse:

    @chached_property
    def next(self):
        # The rest of the code is the same.

# Call it:
tomorrow = DAY.SUNDAY.next  # Without parenthesis

To get previous member in same style you can use the reversed() generator:

    @cached_property
    def prev(self):
        # The only change is in the following line: we cycling in the revert order.        
        iterator = cycle(reversed(self))  
        for member in iterator:
             if member is self:
                 # We have found the current member, so we want to 
                 # return the next member
                 return next(iterator)

UPDATE:
As @EthanFurman states in the comment, the cycle() saves the objects that it returns (to be able to return them in future cycles). so if you want to avoid this overhead as well, you can just iter(self) on your enum

    def next(self):
        iterator = iter(self)
        for member in iterator:
            if member is self:
                try:
                    return next(iterator)
                except StopIteration:
                    # Create a new iterator of the enum and return the 
                    # first object in it.
                    return next(iter(self))
Airiness answered 21/7, 2024 at 13:23 Comment(2)
itertools.cycle creates its own list, so you're not really saving anything.Laity
It only creates list up to your object, which is yet better. Of course, you can just use iter(self), which will not save copy of the members for this short run. But then you will have to use try - except to check if you are on the last member, so the next one is the first.Airiness

© 2022 - 2025 — McMap. All rights reserved.