If this is your first time here it might be a good idea to start at the start of this series; there be spoilers ahead!


It is now time to try and stitch everything together! Not quite the finish line but it should be in sight.

In this post I’ll be adding graph/__init__.py which’ll contain the Graph base class.

As a refresher here’s a sketch of Hybrid_Iterator’s super relationships with dict and Iterator classes

By now the project’s file path structure may look something like…

graph/agents/__init__.py
graph/points/__init__.py
graph/points/construction.py

Here’s a sketch of what the following code hopes to build

Don’t get too caught-up with that bit going on with __init__ and the add_agent, and add_point methods, get to the example usage and it should become much clearer; really the star of this show is the set_travel_plans method ;-)

graph/__init__.py

#!/usr/bin/env python
from __future__ import absolute_import

from hybrid_iterator import Hybrid_Iterator
from graph.points import Point
from graph.agents import Agent


class Graph(Hybrid_Iterator):
    """
    Let `agents` be a `dict` with `{'agent_name': 'address'}` key pares
    Let `points` be a `dict` with `{'address': {'neighbors': {'addr': 'cost'}}}`
    """

    def __init__(self, agents, points, **kwargs):
        super(Graph, self).__init__(**kwargs)
        self.update(
            agents = {},
            points = {},
            off_duty = {},
            travel_plans = {})

        for point, neighbors in points.items():
            self.add_point(point, neighbors)

        for name, address in agents.items():
            self.add_agent(name, address)

    def add_agent(self, name, address):
        """
        ## Arguments
        - `address` maybe a `str`ing or instance of `Point`
        - `name` should be a `str`ing

        ## Assigns
        - instance of `Agent` under `self['agents'][name]`
        - reference to `self['points'][address]` under `self['agents'][name]['points']`

        ## Appends `name` to
        - `self['points'][address]['population']`
        """
        if isinstance(address, str):
            address = self['points'][address]

        self['agents'].update({name: Agent(name, address)})
        address['population'].append(name)

    def add_point(self, address, neighbors):
        """
        Adds instance of `Point` under `self['points'][name]`

        ## Arguments
        - `address` should be a `str`ing
        - `neighbors` should be a `{addr: cost}` `dict`ionary
        """
        self['points'].update({address: Point(address, neighbors)})

    def set_travel_plans(self, agents = None):
        """
        Assigns `self['travel_plans']` by calling `next()` on each of `agents`

        Example: `{'Bill': Agent(name='Bill', ...), ...}`, __or__ `{}`

        ## Arguments
        - `agents`; should be a `{key: Agent()}` `dict`ionary

        > `agents` defaults to `self['agents']` if `None` where passed
        """
        self['travel_plans'] = {}
        if not agents:
            agents = self['agents']

        for key, agent in agents.items():
            try:
                self['travel_plans'].update({key: agent.next()})
            except (StopIteration, GeneratorExit):
                # Pop agents that will not move anymore
                print("Moved {name} to off_duty".format(name = agent['name']))
                self['off_duty'].update({key: agents.pop(key)})
                if self['travel_plans'].get(key):
                    self['travel_plans'].pop(key)

        return self['travel_plans']

    def next(self):
        """
        Calls `self.set_travel_plans`, `raise`s `GeneratorExit`
        if out of `agents` or `travel_plans`, otherwise this
        method will _simulate_ moving agents around the graph
        """
        self.set_travel_plans(agents = self['agents'])
        if not self['agents'] or not self['travel_plans']:
            self.throw(GeneratorExit)

        for key, agent in self['travel_plans'].items():
            here = agent['point']['address']
            heading = agent['heading']
            there = heading.keys()[0]
            # ... `heading` ~ `{addr: cost}`

            print("{name} traveling from {here} to {there} paying {cost}".format(
                name = agent['name'], here = here,
                there = there, cost = heading[there]))

            agent['point']['population'].remove(agent['name'])
            agent['visited'].append(heading)

            agent['point'] = self['points'][there]
            agent['point']['population'].append(agent['name'])

        return self


if __name__ == '__main__':
    raise Exception("This file must be used as a module!")

Provided that the above was saved somewhere like graph/__init__.py it could be imported via…

from graph import Graph

Time to initialize an instance of Graph!

X, O = (0.2, 0.7)


graph = Graph(
    agents = {
        'Bill': 'u',
        'Alice': 'u',
        'Ted': 'v',
        'Jain': 'w'
    },
    points = {
        'u': {'v': X, 'w': X},
        'v': {'u': O, 'w': X},
        'w': {'u': O, 'v': X},
    })

Note how having custom setters eliminates most of the redundant repetition, maybe not under the hood, but this at least looks cleaner and much more approachable for adding more default Points and/or Agents.

And now time to use it with a safety loop….

count = 0
for i, _ in enumerate(graph):
    if i > 5:
        raise Exception("Hunt for bugs!")

    print("## {0} {1}".format(count, f_line))

Which should result if output very similar to…

Bill traveling from u to w paying 0.2
Alice traveling from u to v paying 0.2
Jain traveling from w to u paying 0.7
Ted traveling from v to u paying 0.7
## 0 _________
# ... Trimmed for brevity...
Bill traveling from u to v paying 0.2
Alice traveling from w to u paying 0.7
Jain traveling from v to w paying 0.2
Ted traveling from v to w paying 0.2
## 2 _________
Moved Bill to off_duty
Moved Alice to off_duty
Moved Jain to off_duty
Moved Ted to off_duty
## 3 _________

Excellent it didn’t hit the Hunt for bugs Exception and successfully moved the Agents about! While not quite fully fleshed out, with a bit of lurching it’s getting closer to something that will model graph related problems in an organized fashion.

Hopefully, in regards to having the tools and know-how to break a problem down, you’re now seeing the finish line fast approaching. If not open an Issue detailing what steps you’ve already tried, maybe someone’ll be kind in their reply.