Pastebin

  1. """

  2. WizCoin is a class to represent a quantity of coins in a wizard currency.

  3. In this currency, there are knuts, sickles (worth 29 knuts), and galleons

  4. (worth 17 sickles or 493 knuts).

  5. """

  6. __version__ = '0.0.1'

  7. import copy

  8. import operator

  9. # Constants used in this module:

  10. KNUTS_PER_SICKLE = 29

  11. SICKLES_PER_GALLEON = 17

  12. KNUTS_PER_GALLEON = SICKLES_PER_GALLEON * KNUTS_PER_SICKLE

  13. class WizCoinException(Exception):

  14. """Exceptions of this class are raised by the wizcoin module for incorrect

  15.    use of the module. If wizcoin is the source of any other raised exceptions,

  16.    assume that it is caused by a bug in the module instead of misuse."""

  17. pass

  18. class CoinBag:

  19. """CoinBag objects represent an amount of coins, not money. They cannot

  20.    have half a coin, or a negative number of coins."""

  21. issuer = 'gb' # The ISO 2-letter country code of who issues this currency.

  22. def __init__(self, galleons=0, sickles=0, knuts=0):

  23. """Create a new CoinBag object with galleons, sickles, and knuts."""

  24. self.galleons = galleons

  25. self.sickles = sickles

  26. self.knuts = knuts

  27. @property

  28. def galleons(self):

  29. """The number of galleons in the CoinBag."""

  30. return self._galleons

  31. @galleons.setter

  32. def galleons(self, value):

  33. if not isinstance(value, int) or value < 0:

  34. raise WizCoinException('galleons attr must be a positive int')

  35. self._galleons = value

  36. @galleons.deleter

  37. def galleons(self):

  38. self._galleons = 0

  39. @property

  40. def sickles(self):

  41. """The number of sickles in the CoinBag."""

  42. return self._sickles

  43. @sickles.setter

  44. def sickles(self, value):

  45. if not isinstance(value, int) or value < 0:

  46. raise WizCoinException('sickles attr must be a positive int')

  47. self._sickles = value

  48. @sickles.deleter

  49. def sickles(self):

  50. self._sickles = 0

  51. @property

  52. def knuts(self):

  53. """The number of knuts in the CoinBag."""

  54. return self._knuts

  55. @knuts.setter

  56. def knuts(self, value):

  57. if not isinstance(value, int) or value < 0  :

  58. raise WizCoinException('knuts attr must be a positive int')

  59. self._knuts = value

  60. @knuts.deleter

  61. def knuts(self):

  62. self._knuts = 0

  63. @property

  64. def value(self):

  65. """The value (in knuts) of all the coins in this CoinBag."""

  66. return (self._galleons * KNUTS_PER_GALLEON) + (self._sickles * KNUTS_PER_SICKLE) + (self._knuts)

  67. def convertToGalleons(self):

  68. """Modifies the CoinBag in-place, converting knuts and sickles to

  69.        galleons. There may knuts and sickles leftover as change."""

  70. # Convert knuts to sickles, then sickles to galleons.

  71. self._sickles += self._knuts // KNUTS_PER_SICKLE

  72. self._knuts %= KNUTS_PER_SICKLE # Knuts may be remaining as change.

  73. self._galleons += self._sickles // SICKLES_PER_GALLEON

  74. self._sickles %= SICKLES_PER_GALLEON # Sickles might remain as change.

  75. def convertToSickles(self):

  76. """Modifies the CoinBag object in-place, converting knuts and

  77.        galleons to sickles. There may knuts leftover as change."""

  78. self._sickles += (self._galleons * SICKLES_PER_GALLEON) + (self._knuts // KNUTS_PER_SICKLE)

  79. self._knuts %= KNUTS_PER_SICKLE # Knuts might remain as change.

  80. self._galleons = 0

  81. def convertToKnuts(self):

  82. """Modifies the CoinBag object in-place, converting galleons and

  83.        sickles to knuts."""

  84. self._knuts += (self._galleons * KNUTS_PER_GALLEON) + (self._sickles * KNUTS_PER_SICKLE)

  85. self._galleons = 0

  86. self._sickles = 0

  87. def __repr__(self):

  88. """Returns a string representation of this CoinBag object that can be

  89.        fed into the interactive shell to make an identical CoinBag object."""

  90. className = type(self).__name__

  91. return '%s(galleons=%s, sickles=%s, knuts=%s)' % (className, self._galleons, self._sickles, self._knuts)

  92. def __len__(self):

  93. """Returns the number of coins in this CoinBag."""

  94. return self._galleons + self._sickles + self._knuts

  95. def __copy__(self):

  96. """Returns a new, duplicate CoinBag object of this CoinBag."""

  97. return CoinBag(self._galleons, self._sickles, self._knuts)

  98. def __deepcopy__(self, memo):

  99. """Returns a new, duplicate CoinBag object of this CoinBag. This

  100.        method reuses __copy__() since CoinBags don't need deep copies."""

  101. return self.__copy__()

  102. def __str__(self):

  103. """Returns a string representation of the CoinBag object, formatted

  104.        like '2g,5s,10k' for a CoinBag of 2 galleons, 5 sickles, 10 knuts."""

  105. return '%sg,%ss,%sk' % (self._galleons, self._sickles, self._knuts)

  106. def __int__(self):

  107. """Returns the value of the coins in this CoinBag as an int."""

  108. return self.value

  109. def __float__(self):

  110. """Returns the value of the coins in this CoinBag as a float."""

  111. return float(self.value)

  112. def __bool__(self):

  113. """Returns the Boolean value of the CoinBag."""

  114. return not (self._galleons == 0 and self._sickles == 0 and self._knuts == 0)

  115. @classmethod

  116. def fromStr(cls, coinStr):

  117. """An alternative constructor that gets the coin amounts from

  118.        `coinStr`, which is formatted like '2g,5s,10k'."""

  119. try:

  120. if coinStr == '':

  121. return cls(galleons=0, sickles=0, knuts=0)

  122. gTotal = 0

  123. sTotal = 0

  124. kTotal = 0

  125. for coinStrPart in coinStr.split(','):

  126. if coinStrPart.endswith('g'):

  127. gTotal += int(coinStrPart[:-1])

  128. elif coinStrPart.endswith('s'):

  129. sTotal += int(coinStrPart[:-1])

  130. elif coinStrPart.endswith('k'):

  131. kTotal += int(coinStrPart[:-1])

  132. else:

  133. raise Exception()

  134. except:

  135. raise WizCoinException('coinStr has an invalid format')

  136. return cls(galleons=gTotal, sickles=sTotal, knuts=kTotal)

  137. @classmethod

  138. def isEuropeanCurrency(cls):

  139. """A helper method that returns if this currency is used in Europe."""

  140. return cls.issuer in {'ad', 'al', 'am', 'at', 'ba', 'be', 'bg', 'by', 'ch', 'cy', 'cz', 'de', 'dk', 'ee', 'es', 'fi', 'fo', 'fr', 'gb', 'ge', 'gi', 'gr', 'hr', 'hu', 'ie', 'im', 'is', 'it', 'li', 'lt', 'lu', 'lv', 'mc', 'md', 'me', 'mk', 'mt', 'nl', 'no', 'pl', 'po', 'pt', 'ro', 'rs', 'ru', 'se', 'si', 'sk', 'sm', 'tr', 'ua', 'va'}

  141. @staticmethod

  142. def _isCoinBagType(obj): # This should be a module-level function.

  143. """A helper function that returns True if `obj` has `galleons`,

  144.        `sickles`, `knuts`, and `value` attributes, otherwise returns

  145.        False."""

  146. return hasattr(obj, 'galleons') and hasattr(obj, 'sickles') and hasattr(obj, 'galleons') and hasattr(obj, 'value')

  147. # Overloading comparison operators:

  148. def _comparisonOperatorHelper(self, operatorFunc, other):

  149. """A helper method that carries out a comparison operation."""

  150. if CoinBag._isCoinBagType(other):

  151. # Compare this CoinBag's value with another CoinBag's value.

  152. return operatorFunc(self.value, other.value)

  153. elif isinstance(other, (int, float)):

  154. # Compare this CoinBag's value with an int or float.

  155. return operatorFunc(self.value, other)

  156. elif operatorFunc == operator.eq:

  157. return False # Not equal to all non CoinBag/int/float values.

  158. elif operatorFunc == operator.ne:

  159. return True # Not equal to all non CoinBag/int/float values.

  160. else:

  161. # Can't compare with whatever data type `other` is.

  162. raise WizCoinException("'%s' not supported between instances of '%s' and '%s'" % (operatorFunc.__name__, self.__class__.__name__, other.__class__.__name__))

  163. def __eq__(self, other):

  164. """Overloads the == operator to compare CoinBag objects with ints,

  165.        floats, and other CoinBag objects."""

  166. return self._comparisonOperatorHelper(operator.eq, other)

  167. def __ne__(self, other):

  168. """Overloads the != operator to compare CoinBag objects with ints,

  169.        floats, and other CoinBag objects."""

  170. return self._comparisonOperatorHelper(operator.ne, other)

  171. def __lt__(self, other):

  172. """Overloads the < operator to compare CoinBag objects with ints,

  173.        floats, and other CoinBag objects."""

  174. return self._comparisonOperatorHelper(operator.lt, other)

  175. def __le__(self, other):

  176. """Overloads the <= operator to compare CoinBag objects with ints,

  177.        floats, and other CoinBag objects."""

  178. return self._comparisonOperatorHelper(operator.le, other)

  179. def __gt__(self, other):

  180. """Overloads the > operator to compare CoinBag objects with ints,

  181.        floats, and other CoinBag objects."""

  182. return self._comparisonOperatorHelper(operator.gt, other)

  183. def __ge__(self, other):

  184. """Overloads the >= operator to compare CoinBag objects with ints,

  185.        floats, and other CoinBag objects."""

  186. return self._comparisonOperatorHelper(operator.ge, other)

  187. # Overloading math operators:

  188. def __mul__(self, other):

  189. """Overloads the * operator to produce a new CoinBag object with the

  190.        product amount. `other` must be a positive int."""

  191. if isinstance(other, int) and other >= 0:

  192. return CoinBag(self._galleons * other,

  193. self._sickles * other,

  194. self._knuts * other)

  195. else:

  196. raise WizCoinException('%s objects can only multiply with positive ints' % (self.__class__.__name__))

  197. def __rmul__(self, other):

  198. """Overloads the * operator to produce a new CoinBag object with the

  199.        product amount. `other` must be a positive int."""

  200. return self.__mul__(other) # * is commutative, reuse __mul__().

  201. def __imul__(self, other):

  202. """Overloads the * operator to modify a CoinBag object in-place with

  203.        the product amount. `other` must be a positive int."""

  204. if isinstance(other, int) and other >= 0:

  205. self._galleons *= other # In-place modification.

  206. self._sickles *= other

  207. self._knuts *= other

  208. else:

  209. raise WizCoinException('%s objects can only multiply with positive ints' % (self.__class__.__name__))

  210. return self

  211. def __add__(self, other):

  212. """Overloads the + operator to produce a new CoinBag object with the

  213.        sum amount. `other` must be a CoinBag."""

  214. if CoinBag._isCoinBagType(other):

  215. return CoinBag(self._galleons + other.galleons,

  216. self._sickles + other.sickles,

  217. self._knuts + other.knuts)

  218. else:

  219. raise WizCoinException('%s objects can only add with other wizcoin.CoinBag objects' % (self.__class__.__name__))

  220. def __iadd__(self, other):

  221. """Overloads the += operator to modify this CoinBag in-place with the

  222.        sum amount. `other` must be a CoinBag."""

  223. if CoinBag._isCoinBagType(other):

  224. self._galleons += other.galleons # In-place modification.

  225. self._sickles += other.sickles

  226. self._knuts += other.knuts

  227. else:

  228. raise WizCoinException('%s objects can only add with other wizcoin.CoinBag objects' % (self.__class__.__name__))

  229. return self

  230. def __sub__(self, other):

  231. """Overloads the - operator to produce a new CoinBag object with the

  232.        difference amount. `other` must be a CoinBag object with less than or

  233.        equal number of coins of each type as this CoinBag object."""

  234. if CoinBag._isCoinBagType(other):

  235. if self._galleons < other.galleons or self._sickles < other.sickles or self._knuts < other.knuts:

  236. raise WizCoinException('subtracting %s from %s would result in negative quantity of coins' % (other, self))

  237. return CoinBag(self._galleons - other.galleons,

  238. self._sickles - other.sickles,

  239. self._knuts - other.knuts)

  240. else:

  241. raise WizCoinException('%s objects can only subtract with other wizcoin.CoinBag objects' % (self.__class__.__name__))

  242. def __isub__(self, other):

  243. """Overloads the -= operator to modify this CoinBag in-place with the

  244.        difference amount. `other` must be a CoinBag object with less than or

  245.        equal number of coins of each type as this CoinBag object."""

  246. if CoinBag._isCoinBagType(other):

  247. if self._galleons < other.galleons or self._sickles < other.sickles or self._knuts < other.knuts:

  248. raise WizCoinException('subtracting %s from %s would result in negative quantity of coins' % (other, self))

  249. self._galleons -= other.galleons

  250. self._sickles -= other.sickles

  251. self._knuts -= other.knuts

  252. else:

  253. raise WizCoinException('%s objects can only subtract with other wizcoin.CoinBag objects' % (self.__class__.__name__))

  254. return self

  255. def __lshift__(self, other):

  256. """Overloads the << operator to transfer all coins from the CoinBag on

  257.        the right side to the CoinBag on the left side."""

  258. if not CoinBag._isCoinBagType(other):

  259. raise WizCoinException('CoinBag can only use << on other CoinBag objects')

  260. self._galleons += other.galleons # Add to this CoinBag.

  261. self._sickles += other.sickles

  262. self._knuts += other.knuts

  263. other.galleons = 0 # Empty the other CoinBag.

  264. other.sickles = 0

  265. other.knuts = 0

  266. def __rshift__(self, other):

  267. """Overloads the >> operator to transfer all coins from the CoinBag on

  268.        the left side to the CoinBag on the right side."""

  269. if not CoinBag._isCoinBagType(other):

  270. raise WizCoinException('CoinBag can only use >> on other CoinBag objects')

  271. other.galleons += self._galleons # Add to the other CoinBag.

  272. other.sickles += self._sickles

  273. other.knuts += self._knuts

  274. self._galleons = 0 # Empty this CoinBag.

  275. self._sickles = 0

  276. self._knuts = 0

  277. def __getitem__(self, idx):

  278. """Overloads the [] operator to access what kind of coin is at index

  279.        `idx`. The order of coins is galleons, then sickles, then knutes."""

  280. if idx >= len(self) or idx < -len(self):

  281. raise WizCoinException('index out of range')

  282. if idx < 0:

  283. idx = len(self) + idx # Convert negative index to positive.

  284. if idx < self._galleons:

  285. return 'galleon'

  286. elif idx < self._galleons + self._sickles:

  287. return 'sickle'

  288. else:

  289. return 'knut'

  290. def __setitem__(self, idx, coinType):

  291. """Overloads the [] operator to access what kind of coin is at index

  292.        `idx`. The order of coins is galleons, then sickles, then knutes."""

  293. if coinType not in ('galleon', 'sickle', 'knut'):

  294. raise WizCoinException("coinType must be one of 'galleon', 'sickle', or 'knut'")

  295. try:

  296. coin = self[idx]

  297. except Exception as exc:

  298. raise WizCoinException(str(exc))

  299. if coin == 'galleon':

  300. self._galleons -= 1

  301. elif coin == 'sickle':

  302. self._sickles -= 1

  303. elif coin == 'knut':

  304. self._knuts -= 1

  305. # Add a coin of type `coinType`.

  306. if coinType == 'galleon':

  307. self._galleons += 1

  308. elif coinType == 'sickle':

  309. self._sickles += 1

  310. elif coinType == 'knut':

  311. self._knuts += 1

  312. def __delitem__(self, idx):

  313. """Overloads the [] operator to remove the kind of coin at index

  314.        `idx`."""

  315. try:

  316. coin = self[idx]

  317. except Exception as exc:

  318. raise WizCoinException(str(exc))

  319. if coin == 'galleon':

  320. self._galleons -= 1

  321. elif coin == 'sickle':

  322. self._sickles -= 1

  323. elif coin == 'knut':

  324. self._knuts -= 1

  325. def __iter__(self):

  326. """Returns an iterator that iterates over the coins in this CoinBag.

  327.        The order of coins is galleons, then sickles, then knuts."""

  328. return CoinBagIterator(self)

  329. class CoinBagIterator:

  330. def __init__(self, coinBagObj):

  331. """Creates an iterator for the given CoinBag object."""

  332. self.nextIndex = 0

  333. self.coinBagObj = coinBagObj

  334. def __next__(self):

  335. """Returns the next coin from the CoinBag. The order of coins is

  336.        galleons, then sickles, then knuts."""

  337. if self.nextIndex >= len(self.coinBagObj):

  338. raise StopIteration

  339. nextCoin = self.coinBagObj[self.nextIndex]

  340. self.nextIndex += 1

  341. return nextCoin

  342. class CoinBagCollection:

  343. def __init__(self, coinBags):

  344. self.coinBags = tuple(coinBags)

  345. self._origAmounts = tuple([copy.copy(bag) for bag in self.coinBags])

  346. for bag in self.coinBags:

  347. if not CoinBag._isCoinBagType(bag):

  348. raise WizCoinException('all arguments to CoinBagCollection must be CoinBag objects')

  349. def __enter__(self):

  350. self.expectedTotal = sum([bag.value for bag in self.coinBags])

  351. return tuple(self.coinBags)

  352. def __exit__(self, excType, excValue, excTraceback):

  353. total = sum([bag.value for bag in self.coinBags])

  354. if total == self.expectedTotal and excType is None:

  355. return # Everything is fine.

  356. # Reset bags to their original amounts.

  357. for i, bag in enumerate(self.coinBags):

  358. bag._galleons = self._origAmounts[i]._galleons

  359. bag._sickles = self._origAmounts[i]._sickles

  360. bag._knuts = self._origAmounts[i]._knuts

  361. if total != self.expectedTotal:

  362. raise WizCoinException('expected total value (%s) does not match current total value (%s)' % (self.expectedTotal, total))

Read the original on pastebin.com ↗