Source code for towers.core.rod

#!/usr/bin/env python
# -*- coding: latin-1 -*-
#
# @module towers.core.rod
# @version 0.1
# @copyright (c) 2017-present Francis Horsman.

import json
from collections import Iterable, Sized, namedtuple

import six

from .disk import Disk
from .errors import CorruptRod, DuplicateDisk
from .utils import Serializable
from .validation import Validatable

__all__ = ['Rod']


[docs]class Rod( namedtuple('Rod', ('name', 'disks', 'height')), Iterable, Sized, Validatable, Serializable ): """ A single tower containing disks. """
[docs] def __new__(cls, name, disks=None, height=0): """ :param str name: The name of the rod. :param List[Disk] disks: (optional) mutatable list of `Disks`. :param int height: The height of the rod. :rtype: Rod :raises: See `Rod.validate`. """ self = super(Rod, cls).__new__(cls, name, disks or [], height) self.validate() return self
[docs] def to_json(self): """ Return a json serializable representation of this instance. :rtype: object """ return { 'name': self.name, 'height': self.height, 'disks': [i.to_json() for i in self.disks], }
@classmethod
[docs] def from_json(cls, d): """ Return a class instance from a json serializable representation. :param Union[str,dict] d: The json or decoded-json from which to create a new instance. :rtype: Rod :raises: See `Rod.__new__`. """ if isinstance(d, six.string_types): d = json.loads(d) return cls( name=d.pop('name'), height=d.pop('height'), disks=[Disk.from_json(i) for i in d.pop('disks')], )
def __len__(self): return self.height
[docs] def __copy__(self): """ Return a shallow copy of this instance. :rtype: Rod """ return Rod( self.name, disks=self.disks, height=self.height, )
[docs] def __deepcopy__(self, *d): """ Return a deep copy of this instance. :param dict d: Memoisation dict. :rtype: Rod """ return Rod( self.name, disks=self.disks[:], height=self.height, )
def __str__(self): return '{name}({rod})'.format( name=self.name, rod=self.disks, )
[docs] def __eq__(self, other): """ Compare Rod instances for equivalence. :param Rod other: :rtype: bool """ if isinstance(other, Rod): if other.height == self.height: if other.disks == self.disks: return True
[docs] def __bool__(self): """ A Rod is considered True if it contains any disks. :rtype: bool """ return self.__nonzero__()
[docs] def __nonzero__(self): """ A Rod is considered non-zero if it contains any disks. :rtype: bool """ return bool(self.disks)
[docs] def pop(self): """ Pop the top most disk from this rod and return it :rtype: Disk """ return self.disks.pop()
[docs] def append(self, disk, validate=True): """ Append the disk to this rod and optionally validate. :param Disk disk: The disk to add to the top of our rod. :param bool validate: True=perform self validation. """ self.disks.append(disk) if validate: self.validate()
[docs] def __iter__(self): """ Iterate over all the disks in this rod. :rtype: Disk """ for disk in self.disks: yield disk
[docs] def validate(self): """ Perform self validation. :raises DuplicateDisk: This rod already contains this disk :raises CorruptRod: A disk is on top of a disk of smaller size. :raises InvalidTowerHeight: The height of the tower is invalid. :raises InvalidDiskPosition: The position of the disk is invalid. """ width = 0 seen = set() for disk in reversed(list(iter(self))): disk_width = disk.width if disk_width in seen: raise DuplicateDisk(self, disk_width) seen.update([disk_width]) if disk.width < width: raise CorruptRod(self, disk) width = disk_width disk.validate()