Get next enumerator constant/property - python

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')

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.

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'>

#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

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

Related

Why is my linked list being sorted incorrectly?

I am trying to insert new appointments sorted by date to a linked list but when I go to test it, there is always one instance where it is not sorted correctly. Currently this is my code:
from datetime import datetime
class VaccList:
class Appointment:
def __init__(self, name, age, city, date):
assert type(name) is str, 'name variable must be a string'
assert type(age) is int, 'age variable must be a integer'
assert type(city) is str, 'city variable must be a string'
assert type(date) is datetime, 'date variable must be a datetime object'
assert name != None, 'name variable cannot be empty'
assert age >= 18 and age <= 100, 'age must be between 18 and 100'
# ADD 6 asserts. 4 for the types and name cannot be empty,
# and age must be between 18 and 100
self.name = name
self.age = age
self.city = city
self.date = date
self.confirmed = False
self.next = None
def __str__(self):
s = "Appointment for " + self.name + " on " + str(self.date) + " age:" + str(self.age) + " city:" + self.city
if self.confirmed:
s += " (confirmed)"
else:
s += " (unconfirmed)"
return s
def __init__(self):
self.head = None
self.tail = None
def print(self): #YOU WRITE THIS (EASY)
'''
Print all the appointments, one per line. Print a blank line after the last one.
If the list is empty, print a line saying the Appointment List is empty.
'''
runner = self.head
while runner != None:
print(runner)
runner = runner.next
def insertByDate(self, newAppt): #### YOU WRITE (HARD)
''' Given a pointer to an Appointment object, put it into the list so that the list remains sorted by date.
Obviously the linked list may be empty, which is easy. But inserting the newAppt in sorted order
may mean putting it at the front if the newAppt's date is less than the first. Or at the end, or in
the middle.
'''
assert type(newAppt) is VaccList.Appointment, 'insertByDate requires a pointer to an appointment object'
newnode = newAppt
if self.head is None:
self.head = newAppt
elif newAppt.date < self.head.date:
newAppt.next = self.head
self.head = newAppt
else:
current = self.head
while current.next != None and current.date < newAppt.date:
current = current.next
newAppt.next = current.next
current.next = newAppt
This is the test code that I am attempting to run:
active =VaccList()
appt = VaccList.Appointment("Henry", 72, "Buffalo", datetime(2021,5,1,12,0,0))
active.insertByDate(appt)
appt = VaccList.Appointment("John", date=datetime(2021,3,10,12,0,0), age=75, city="Buffalo")
active.insertByDate(appt)
appt = VaccList.Appointment(date = datetime(2021, 3, 5, 8, 0, 0), name="Mary", city="Buffalo", age=65)
active.insertByDate(appt)
appt = VaccList.Appointment(date = datetime(2021, 4, 28, 13, 30, 0), name="Alvin", city="New York City", age=39)
active.insertByDate(appt)
appt = VaccList.Appointment(date = datetime(2021, 4, 21, 14, 0, 0), name="Sheila", city="New York City", age=50)
active.insertByDate(appt)
appt = VaccList.Appointment(date = datetime(2021, 3, 12, 18, 0, 0), name="Melvin", city="New York City", age=80)
active.insertByDate(appt)
active.print()
However everytime I run this the appointment under 'Henry' is sorted incorrectly. I am lost because it seems to sort the rest of the appointments just fine except for 'Henry' being out of place. any ideas/solutions are really appreciated as this is one of my first python projects.
Appointment for Mary on 2021-03-05 08:00:00 age:65 city:Buffalo (unconfirmed)
Appointment for John on 2021-03-10 12:00:00 age:75 city:Buffalo (unconfirmed)
Appointment for Henry on 2021-05-01 12:00:00 age:72 city:Buffalo (unconfirmed)
Appointment for Melvin on 2021-03-12 18:00:00 age:80 city:New York City (unconfirmed)
Appointment for Sheila on 2021-04-21 14:00:00 age:50 city:New York City (unconfirmed)
Appointment for Alvin on 2021-04-28 13:30:00 age:39 city:New York City (unconfirmed)
In the else block, your loop is finding a current node such that its date comes after the date of the node you want to insert. But realise how that brings you one node too far... You will insert the new node after current, so current must still be a node that has a date that comes before the one you are inserting.
So change this:
while current.next != None and current.date < newAppt.date:
to:
while current.next != None and current.next.date < newAppt.date:
# ^^^^^

Overload __init__() for a subclass of Enum

I'm trying to overload the __init__() method of a subclass of an enum. Strangely, the pattern that work with a normal class doesn't work anymore with Enum.
The following show the desired pattern working with a normal class:
class Integer:
def __init__(self, a):
"""Accepts only int"""
assert isinstance(a, int)
self.a = a
def __repr__(self):
return str(self.a)
class RobustInteger(Integer):
def __init__(self, a):
"""Accepts int or str"""
if isinstance(a, str):
super().__init__(int(a))
else:
super().__init__(a)
print(Integer(1))
# 1
print(RobustInteger(1))
# 1
print(RobustInteger('1'))
# 1
The same pattern then breaks if used with an Enum:
from enum import Enum
from datetime import date
class WeekDay(Enum):
MONDAY = 0
TUESDAY = 1
WEDNESDAY = 2
THURSDAY = 3
FRIDAY = 4
SATURDAY = 5
SUNDAY = 6
def __init__(self, value):
"""Accepts int or date"""
if isinstance(value, date):
super().__init__(date.weekday())
else:
super().__init__(value)
assert WeekDay(0) == WeekDay.MONDAY
assert WeekDay(date(2019, 4, 3)) == WeekDay.MONDAY
# ---------------------------------------------------------------------------
# TypeError Traceback (most recent call last)
# /path/to/my/test/file.py in <module>()
# 27
# 28
# ---> 29 class WeekDay(Enum):
# 30 MONDAY = 0
# 31 TUESDAY = 1
# /path/to/my/virtualenv/lib/python3.6/enum.py in __new__(metacls, cls, bases, classdict)
# 208 enum_member._name_ = member_name
# 209 enum_member.__objclass__ = enum_class
# --> 210 enum_member.__init__(*args)
# 211 # If another member with the same value was already defined, the
# 212 # new member becomes an alias to the existing one.
# /path/to/my/test/file.py in __init__(self, value)
# 40 super().__init__(date.weekday())
# 41 else:
# ---> 42 super().__init__(value)
# 43
# 44
# TypeError: object.__init__() takes no parameters
You have to overload the _missing_ hook. All instances of WeekDay are created when the class is first defined; WeekDay(date(...)) is an indexing operation rather than a creation operation, and __new__ is initially looking for pre-existing values bound to the integers 0 to 6. Failing that, it calls _missing_, in which you can convert the date object into such an integer.
class WeekDay(Enum):
MONDAY = 0
TUESDAY = 1
WEDNESDAY = 2
THURSDAY = 3
FRIDAY = 4
SATURDAY = 5
SUNDAY = 6
#classmethod
def _missing_(cls, value):
if isinstance(value, date):
return cls(value.weekday())
return super()._missing_(value)
A few examples:
>>> WeekDay(date(2019,3,7))
<WeekDay.THURSDAY: 3>
>>> assert WeekDay(date(2019, 4, 1)) == WeekDay.MONDAY
>>> assert WeekDay(date(2019, 4, 3)) == WeekDay.MONDAY
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
(Note: _missing_ is not available prior to Python 3.6.)
Prior to 3.6, it seems you can override EnumMeta.__call__ to make the same check, but I'm not sure if this will have unintended side effects. (Reasoning about __call__ always makes my head spin a little.)
# Silently convert an instance of datatime.date to a day-of-week
# integer for lookup.
class WeekDayMeta(EnumMeta):
def __call__(cls, value, *args, **kwargs):
if isinstance(value, date):
value = value.weekday())
return super().__call__(value, *args, **kwargs)
class WeekDay(Enum, metaclass=WeekDayMeta):
MONDAY = 0
TUESDAY = 1
WEDNESDAY = 2
THURSDAY = 3
FRIDAY = 4
SATURDAY = 5
SUNDAY = 6
There is a much better answer, but I post this anyway as it might be helpful for understanding the issue.
The docs gives this hint:
EnumMeta creates them all while it is creating the Enum class itself,
and then puts a custom new() in place to ensure that no new ones
are ever instantiated by returning only the existing member instances.
So we have to wait with redefining __new__ until the class is created. With some ugly patching this passes the test:
from enum import Enum
from datetime import date
class WeekDay(Enum):
MONDAY = 0
TUESDAY = 1
WEDNESDAY = 2
THURSDAY = 3
FRIDAY = 4
SATURDAY = 5
SUNDAY = 6
wnew = WeekDay.__new__
def _new(cls, value):
if isinstance(value, date):
return wnew(cls, value.weekday()) # not date.weekday()
else:
return wnew(cls, value)
WeekDay.__new__ = _new
assert WeekDay(0) == WeekDay.MONDAY
assert WeekDay(date(2019, 3, 4)) == WeekDay.MONDAY # not 2019,4,3

How to return enum's value by its corresponding number?

How should I implement func to correctly return the corresponding value of the Direction enum?
from enum import Enum
class Direction(Enum):
right = 0
down = 1
left = 2
up = 3
def func(self, n):
# When n = 0 return Direction.right
# When n = 1 return Direction.down
# When n = 2 return Direction.left
# When n = 3 return Direction.up
A function is not needed, it can simply be done like this:
>>> Direction(1)
<Direction.down: 1>
>>> Direction(3)
<Direction.up: 3>
Source: https://docs.python.org/3.4/library/enum.html

How to get specific value from this function?

I read this : https://stackoverflow.com/a/37605582/6426449
START_TIME = a constant that represents a unix timestamp
def make_id():
t = int(time.time()*1000) - START_TIME
u = random.SystemRandom().getrandbits(23)
id = (t << 23 ) | u
return id
def reverse_id(id):
t = id >> 23
return t + START_TIME
From above def, How to get t and u of id(It's generated from def make_id)?
Like
def get_t(id):
some methods
return t
def get_u(id):
some methods
return u
To get t, just undo the left shift with a right shift.
def get_t(id):
return id >> 23
To get u, use a bit mask with the rightmost 23 bits set
def get_u(id):
return id & 0x7fffff

replace semicolon by newline in python code

I would like to parse Python code that contains semicolons ; for separating commands and produce code that replaces those by newlines \n. E.g., from
def main():
a = "a;b"; return a
I'd like to produce
def main():
a = "a;b"
return a
Any hints?
Use the tokenize library to look for token.OP tokens, where the second element is a ; *. Replace these tokens with a token.NEWLINE token.
You'd need to adjust your token offsets and generate matching indent too however; so after a NEWLINE you'd need to adjust line numbers (increment by an offset you increase for every NEWLINE you insert) and the 'next' line (remainder of the current line) would have to have the indices adjusted to match the current indentation level:
import tokenize
TokenInfo = getattr(tokenize, 'TokenInfo', lambda *a: a) # Python 3 compat
def semicolon_to_newline(tokens):
line_offset = 0
last_indent = None
col_offset = None # None or an integer
for ttype, tstr, (slno, scol), (elno, ecol), line in tokens:
slno, elno = slno + line_offset, elno + line_offset
if ttype in (tokenize.INDENT, tokenize.DEDENT):
last_indent = ecol # block is indented to this column
elif ttype == tokenize.OP and tstr == ';':
# swap out semicolon with a newline
ttype = tokenize.NEWLINE
tstr = '\n'
line_offset += 1
if col_offset is not None:
scol, ecol = scol - col_offset, ecol - col_offset
col_offset = 0 # next tokens should start at the current indent
elif col_offset is not None:
if not col_offset:
# adjust column by starting column of next token
col_offset = scol - last_indent
scol, ecol = scol - col_offset, ecol - col_offset
if ttype == tokenize.NEWLINE:
col_offset = None
yield TokenInfo(
ttype, tstr, (slno, scol), (elno, ecol), line)
with open(sourcefile, 'r') as source, open(destination, 'w') as dest:
generator = tokenize.generate_tokens(source.readline)
dest.write(tokenize.untokenize(semicolon_to_newline(generator)))
Note that I don't bother to correct the line value; it is informative only, the data that was read from the file is not actually used when un-tokenizing.
Demo:
>>> from io import StringIO
>>> source = StringIO('''\
... def main():
... a = "a;b"; return a
... ''')
>>> generator = tokenize.generate_tokens(source.readline)
>>> result = tokenize.untokenize(semicolon_to_newline(generator))
>>> print(result)
def main():
a = "a;b"
return a
and slightly more complex:
>>> source = StringIO('''\
... class Foo(object):
... def bar(self):
... a = 10; b = 11; c = 12
... if self.spam:
... x = 12; return x
... x = 15; return y
...
... def baz(self):
... return self.bar;
... # note, nothing after the semicolon
... ''')
>>> generator = tokenize.generate_tokens(source.readline)
>>> result = tokenize.untokenize(semicolon_to_newline(generator))
>>> print(result)
class Foo(object):
def bar(self):
a = 10
b = 11
c = 12
if self.spam:
x = 12
return x
x = 15
return y
def baz(self):
return self.bar
# note, nothing after the semicolon
>>> print(result.replace(' ', '.'))
class.Foo(object):
....def.bar(self):
........a.=.10
........b.=.11
........c.=.12
........if.self.spam:
............x.=.12
............return.x
........x.=.15
........return.y
....def.baz(self):
........return.self.bar
........
........#.note,.nothing.after.the.semicolon
* The Python 3 version of tokenize outputs more informative TokenInfo named tuples, which have an extra exact_type attribute that can be used instead of doing a text match: tok.exact_type == tokenize.SEMI. I kept the above compatible with Python 2 and 3 however.
Here's a pyparsing solution - see comments in the code below:
from pyparsing import Literal, restOfLine, quotedString, pythonStyleComment, line
SEMI = Literal(';')
patt = SEMI + restOfLine
patt.ignore(quotedString)
patt.ignore(pythonStyleComment)
def split_at(s, locs):
"""
break up s into pieces, given list of break locations
"""
current = 0
ret = []
for loc in locs:
ret.append(s[current:loc].lstrip())
current = loc+1
ret.append(s[current:].lstrip())
return ret
def split_on_semicolon(s,l,tokens):
"""
parse time callback, when finding first unquoted ';' on a line
"""
current_line = line(l,s)
line_body = current_line.lstrip()
indent = current_line.index(line_body)
indent = current_line[:indent]
# may be more than one ';' on this line, find them all
# (the second token contains everything after the ';')
remainder = tokens[1]
if remainder.strip():
all_semis = [s for _,s,_ in SEMI.scanString(remainder)]
# break line into pieces
pieces = split_at(remainder, all_semis)
# rejoin pieces, with leading indents
return '\n'+'\n'.join(indent+piece for piece in pieces)
else:
return ''
patt.addParseAction(split_on_semicolon)
sample = """
def main():
this_semi_does_nothing();
neither_does_this_but_there_are_spaces_afterward();
a = "a;b"; return a # this is a comment; it has a semicolon!
def b():
if False:
z=1000;b("; in quotes"); c=200;return z
return ';'
class Foo(object):
def bar(self):
'''a docstring; with a semicolon'''
a = 10; b = 11; c = 12
# this comment; has several; semicolons
if self.spam:
x = 12; return x # so; does; this; one
x = 15;;; y += x; return y
def baz(self):
return self.bar
"""
print(patt.transformString(sample))
Gives:
def main():
this_semi_does_nothing()
neither_does_this_but_there_are_spaces_afterward()
a = "a;b"
return a # this is a comment; it has a semicolon!
def b():
if False:
z=1000
b("; in quotes")
c=200
return z
return ';'
class Foo(object):
def bar(self):
'''a docstring; with a semicolon'''
a = 10
b = 11
c = 12
# this comment; has several; semicolons
if self.spam:
x = 12
return x # so; does; this; one
x = 15
y += x
return y
def baz(self):
return self.bar

Categories