Skip to content

Commit

Permalink
Merge pull request #77 from PyconUK/issue-76
Browse files Browse the repository at this point in the history
Use properties for event.tags and event.unavailability
  • Loading branch information
meatballs authored May 12, 2017
2 parents 753aeb0 + 33ecc7b commit 9a5decd
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 23 deletions.
25 changes: 13 additions & 12 deletions docs/tutorial/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,12 @@ Further to this we have a couple of other constraints:

- The speaker for :code:`Talk 1` is also the person delivering :code:`Workshop 1`::

>>> events[0].unavailability.append(events[6])
>>> events[0].add_unavailability(events[6])

- Also, the person running :code:`Workshop 2` is the person hosting the
:code:`Boardgames`::

>>> events[13].unavailability.append(events[-1])
>>> events[13].add_unavailability(events[-1])

Note that we haven't indicated the workshops cannot happen in the talk slots but
this will automatically be taken care of because of the duration of the
Expand Down Expand Up @@ -124,7 +124,6 @@ event::
Workshop 1 at 16-Sep-2016 13:00 in Small
City tour at 16-Sep-2016 13:00 in Outside


We see that all the events are scheduled in appropriate rooms (as indicated by
the unavailability attribute for the events). Also we have that :code:`Talk 1`
doesn't clash with :code:`Workshop 1`.
Expand Down Expand Up @@ -178,11 +177,11 @@ Coping with new information
This is fantastic! Our schedule has now been published and everyone is excited
about the conference. However, as can often happen, one of the speakers now
informs us of a particular new constraints. For example, the speaker for
:code:`Talk 11` is unable to speak on the second day.
:code:`Talk 11` is unable to speak on the first day.

We can enter this new constraint::

>>> events[10].unavailability.extend(slots[9:])
>>> events[10].add_unavailability(*slots[9:])

We can now solve the problem one more time from scratch just as before::

Expand Down Expand Up @@ -238,6 +237,7 @@ old schedule::
City tour at 16-Sep-2016 13:00 in Outside



Spotting the Changes
--------------------
It can be a little difficult to spot what has changed when we compute a new schedule and so
Expand Down Expand Up @@ -345,17 +345,17 @@ As you can see, we have set all unavailabilities to be empty however
has informed us that they are not present on the first day. We can include these
constraints::

>>> events[0].unavailability.append(chair_slots[4])
>>> events[1].unavailability.append(chair_slots[4])
>>> events[2].unavailability.extend(chair_slots[4:])
>>> events[3].unavailability.extend(chair_slots[4:])
>>> events[0].add_unavailability(chair_slots[4])
>>> events[1].add_unavailability(chair_slots[4])
>>> events[2].add_unavailability(*chair_slots[4:])
>>> events[3].add_unavailability(*chair_slots[4:])

Finally, each chair cannot chair more than one session at a time::


>>> events[0].unavailability.append(events[1])
>>> events[2].unavailability.append(events[3])
>>> events[4].unavailability.append(events[5])
>>> events[0].add_unavailability(events[1])
>>> events[2].add_unavailability(events[3])
>>> events[4].add_unavailability(events[5])

Now let us get the chair schedule::

Expand All @@ -371,6 +371,7 @@ Now let us get the chair schedule::
Chair A-1 chairing 16-Sep-2016 12:30 in Small
Chair D-2 chairing 16-Sep-2016 12:30 in Big


Validating a schedule
---------------------

Expand Down
2 changes: 1 addition & 1 deletion src/conference_scheduler/lp_problem/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def _events_in_session_share_a_tag(events, slots, X, summation_type=None):
for session in session_indices:
slots = lpu._slots_in_session(session, session_array)
for slot, event in it.product(slots, event_indices):
if events[event].tags != []:
if len(events[event].tags) > 0:
other_events = lpu._events_with_diff_tag(event, tag_array)
for other_slot, other_event in it.product(slots, other_events):
if other_slot != slot and other_event != event:
Expand Down
32 changes: 30 additions & 2 deletions src/conference_scheduler/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ def __init__(self, name, duration, demand, tags=None, unavailability=None):
self.demand = demand
if tags is None:
tags = []
self.tags = tags
self._tags = tags
if unavailability is None:
unavailability = []
self.unavailability = unavailability
self._unavailability = unavailability

def __repr__(self):
return (
Expand All @@ -48,6 +48,34 @@ def __ne__(self, other):
def __hash__(self):
return hash(repr(self))

@property
def unavailability(self):
return tuple(self._unavailability)

def add_unavailability(self, *args):
for arg in args:
self._unavailability.append(arg)

def remove_unavailability(self, object):
self._unavailability.remove(object)

def clear_unavailability(self):
del self._unavailability[:]

@property
def tags(self):
return tuple(self._tags)

def add_tags(self, *args):
for arg in args:
self._tags.append(arg)

def remove_tag(self, tag):
self._tags.remove(tag)

def clear_tags(self):
del self._tags[:]


class ScheduledItem(NamedTuple):
event: Event
Expand Down
69 changes: 61 additions & 8 deletions tests/test_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,83 @@ def test_can_construct_event():
)
assert isinstance(e, Event)
assert e.name == 'example'
assert e.tags == ['beginner', 'python']
assert e.unavailability == []
assert e.tags == ('beginner', 'python')
assert e.unavailability == ()


def test_optional_args_to_event_are_defaulted():
e = Event(name='example', duration=60, demand=100)
assert e.tags == []
assert e.unavailability == []
assert e.tags == ()
assert e.unavailability == ()


def test_optional_args_are_safely_mutable():
# Construct an instance of `Event` with the optional arguments,
# omitted, then assign it a tag
e = Event(name='example', duration=60, demand=100)
assert e.tags == []
e.tags.append('intermediate')
assert e.tags == ['intermediate']
assert e.tags == ()
e.add_tags('intermediate')
assert e.tags == ('intermediate', )

# Now create a second instance of `Event`, and check we haven't
# polluted the default arguments.
f = Event(name='another example', duration=30, demand=50)
assert f.tags == []
assert f.tags == ()


def test_event_is_hashable():
e = Event(name='example', duration=60, demand=100)
events = set([e])
assert len(events) == 1


def test_add_single_unavailability():
e = Event(name='example', duration=60, demand=100)
e.add_unavailability(2)
assert e.unavailability == (2, )


def test_add_multiple_unavailability():
e = Event(name='example', duration=60, demand=100)
e.add_unavailability(2, 3, 4)
assert e.unavailability == (2, 3, 4)


def test_remove_unavailability():
e = Event(name='example', duration=60, demand=100)
e.add_unavailability(2, 3, 4)
e.remove_unavailability(3)
assert e.unavailability == (2, 4)


def test_clear_unavailability():
e = Event(name='example', duration=60, demand=100)
e.add_unavailability(2, 3, 4)
e.clear_unavailability()
assert e.unavailability == ()


def test_add_single_tag():
e = Event(name='example', duration=60, demand=100)
e.add_tags('test')
assert e.tags == ('test', )


def test_add_multiple_tags():
e = Event(name='example', duration=60, demand=100)
e.add_tags('test1', 'test2', 'test3')
assert e.tags == ('test1', 'test2', 'test3')


def test_remove_tag():
e = Event(name='example', duration=60, demand=100)
e.add_tags('test1', 'test2', 'test3')
e.remove_tag('test2')
assert e.tags == ('test1', 'test3')


def test_clear_tags():
e = Event(name='example', duration=60, demand=100)
e.add_tags('test1', 'test2', 'test3')
e.clear_tags()
assert e.tags == ()

0 comments on commit 9a5decd

Please sign in to comment.