import math
class Point(object):
def __init__(self, *args, **kargs):
self.num_dimensions = kargs.get("num_dimensions", len(args))
self.coords = [0 for i in range(self.num_dimensions)]
for i in range(len(args)):
self.coords[i] = args[i]
def magnitude(self):
return math.sqrt(sum(c*c for c in self.coords))
def angle(self):
assert self.num_dimensions == 2
assert self.x != 0 or self.y != 0
return math.atan2(self.y,self.x)
def tuple(self):
return tuple(self.coords)
def map(self, func):
new_coords = [func(a) for a in self.coords]
return Point(*new_coords)
def _applyVectorFunc(self, other, f):
assert self.num_dimensions == other.num_dimensions
new_coords = [f(a,b) for a,b in zip(self.coords, other.coords)]
return Point(*new_coords)
def _applyScalarFunc(self, val, f):
return self.map(lambda a: f(a,val))
"""
new_coords = [f(a, val) for a in self.coords]
return Point(*new_coords)
"""
def normalized(self):
return self.__div__(self.magnitude())
def __add__(self, other):
return self._applyVectorFunc(other, lambda a,b: a+b)
def __sub__(self, other):
return self._applyVectorFunc(other, lambda a,b: a-b)
def __mul__(self, a):
return self._applyScalarFunc(a, lambda b,c: b*c)
def __div__(self, a):
return self._applyScalarFunc(a, lambda b,c: b/c)
def __eq__(self, other):
try:
return self.num_dimensions == other.num_dimensions and self.coords == other.coords
except:
return False
def __ne__(self, other):
return not(self == other)
def __getattr__(self, name):
if name == "x": return self.coords[0]
if name == "y": return self.coords[1]
if name == "z": return self.coords[2]
raise AttributeError(name)
def __setattr__(self, name, value):
if name == "x": self.coords[0] = value
elif name == "y": self.coords[1] = value
elif name == "z": self.coords[2] = value
else: object.__setattr__(self, name, value)
def copy(self):
return Point(*self.coords[:])
def __hash__(self):
return hash(self.tuple())
def __repr__(self):
use_nice_floats = False
if use_nice_floats:
return "Point(" + ", ".join("%.1f" % c for c in self.coords) + ")"
else:
return "Point(" + ", ".join(str(c) for c in self.coords) + ")"
def to_radians(x):
return math.pi * 2 * x / 360.0
#converts from hex grid coordinates to screen coordinates.
#assumes that the hexagons have a minor radius of 1.
def hex_to_screen(p):
y = 2 * p.y * math.sin(to_radians(60))
x = 2 * ((p.y * math.cos(to_radians(60))) + p.x)
return Point(x,y)
#finds the coordinates of the center of each circle of `minor` radius that can fit in a circle of `major` radius.
def get_centers(major, minor):
assert major > minor
extent = int(2 * major / minor)
for i in range(-extent, extent):
for j in range(-extent, extent):
p = hex_to_screen(Point(i,j)) * minor
if p.magnitude() + minor <= major:
yield p
mm_per_inch = 25.4
values = [1, 2, 5, 10, 20, 50, 100, 200]
diameters = [20.3, 25.9, 18.0, 24.5, 21.4, 27.3, 22.5, 28.4]
for value, diameter in zip(values, diameters):
for major_diameter in [12, 14]:
major_radius = major_diameter * mm_per_inch / 2
count = len(list(get_centers(major_radius, diameter/2)))
cost = count * value / 100.0
print "{} pence coin on {}\" pizza: {} coins, {} pounds".format(value, major_diameter, count, cost)
aW1wb3J0IG1hdGgKCmNsYXNzIFBvaW50KG9iamVjdCk6CiAgICBkZWYgX19pbml0X18oc2VsZiwgKmFyZ3MsICoqa2FyZ3MpOgogICAgICAgIHNlbGYubnVtX2RpbWVuc2lvbnMgPSBrYXJncy5nZXQoIm51bV9kaW1lbnNpb25zIiwgbGVuKGFyZ3MpKQogICAgICAgIHNlbGYuY29vcmRzID0gWzAgZm9yIGkgaW4gcmFuZ2Uoc2VsZi5udW1fZGltZW5zaW9ucyldCiAgICAgICAgZm9yIGkgaW4gcmFuZ2UobGVuKGFyZ3MpKToKICAgICAgICAgICAgc2VsZi5jb29yZHNbaV0gPSBhcmdzW2ldCiAgICBkZWYgbWFnbml0dWRlKHNlbGYpOgogICAgICAgIHJldHVybiBtYXRoLnNxcnQoc3VtKGMqYyBmb3IgYyBpbiBzZWxmLmNvb3JkcykpCiAgICBkZWYgYW5nbGUoc2VsZik6CiAgICAgICAgYXNzZXJ0IHNlbGYubnVtX2RpbWVuc2lvbnMgPT0gMgogICAgICAgIGFzc2VydCBzZWxmLnggIT0gMCBvciBzZWxmLnkgIT0gMAogICAgICAgIHJldHVybiBtYXRoLmF0YW4yKHNlbGYueSxzZWxmLngpCiAgICBkZWYgdHVwbGUoc2VsZik6CiAgICAgICAgcmV0dXJuIHR1cGxlKHNlbGYuY29vcmRzKQogICAgZGVmIG1hcChzZWxmLCBmdW5jKToKICAgICAgICBuZXdfY29vcmRzID0gW2Z1bmMoYSkgZm9yIGEgaW4gc2VsZi5jb29yZHNdCiAgICAgICAgcmV0dXJuIFBvaW50KCpuZXdfY29vcmRzKQogICAgZGVmIF9hcHBseVZlY3RvckZ1bmMoc2VsZiwgb3RoZXIsIGYpOgogICAgICAgIGFzc2VydCBzZWxmLm51bV9kaW1lbnNpb25zID09IG90aGVyLm51bV9kaW1lbnNpb25zCiAgICAgICAgbmV3X2Nvb3JkcyA9IFtmKGEsYikgZm9yIGEsYiBpbiB6aXAoc2VsZi5jb29yZHMsIG90aGVyLmNvb3JkcyldCiAgICAgICAgcmV0dXJuIFBvaW50KCpuZXdfY29vcmRzKQogICAgZGVmIF9hcHBseVNjYWxhckZ1bmMoc2VsZiwgdmFsLCBmKToKICAgICAgICByZXR1cm4gc2VsZi5tYXAobGFtYmRhIGE6IGYoYSx2YWwpKQogICAgICAgICIiIgogICAgICAgIG5ld19jb29yZHMgPSBbZihhLCB2YWwpIGZvciBhIGluIHNlbGYuY29vcmRzXQogICAgICAgIHJldHVybiBQb2ludCgqbmV3X2Nvb3JkcykKICAgICAgICAiIiIKCiAgICBkZWYgbm9ybWFsaXplZChzZWxmKToKICAgICAgICByZXR1cm4gc2VsZi5fX2Rpdl9fKHNlbGYubWFnbml0dWRlKCkpCgogICAgZGVmIF9fYWRkX18oc2VsZiwgb3RoZXIpOgogICAgICAgIHJldHVybiBzZWxmLl9hcHBseVZlY3RvckZ1bmMob3RoZXIsIGxhbWJkYSBhLGI6IGErYikKICAgIGRlZiBfX3N1Yl9fKHNlbGYsIG90aGVyKToKICAgICAgICByZXR1cm4gc2VsZi5fYXBwbHlWZWN0b3JGdW5jKG90aGVyLCBsYW1iZGEgYSxiOiBhLWIpCiAgICBkZWYgX19tdWxfXyhzZWxmLCBhKToKICAgICAgICByZXR1cm4gc2VsZi5fYXBwbHlTY2FsYXJGdW5jKGEsIGxhbWJkYSBiLGM6IGIqYykKICAgIGRlZiBfX2Rpdl9fKHNlbGYsIGEpOgogICAgICAgIHJldHVybiBzZWxmLl9hcHBseVNjYWxhckZ1bmMoYSwgbGFtYmRhIGIsYzogYi9jKQogICAgCiAgICBkZWYgX19lcV9fKHNlbGYsIG90aGVyKToKICAgICAgICB0cnk6CiAgICAgICAgICAgIHJldHVybiBzZWxmLm51bV9kaW1lbnNpb25zID09IG90aGVyLm51bV9kaW1lbnNpb25zIGFuZCBzZWxmLmNvb3JkcyA9PSBvdGhlci5jb29yZHMKICAgICAgICBleGNlcHQ6CiAgICAgICAgICAgIHJldHVybiBGYWxzZQoKICAgIGRlZiBfX25lX18oc2VsZiwgb3RoZXIpOgogICAgICAgIHJldHVybiBub3Qoc2VsZiA9PSBvdGhlcikKICAgIAogICAgZGVmIF9fZ2V0YXR0cl9fKHNlbGYsIG5hbWUpOgogICAgICAgIGlmIG5hbWUgPT0gIngiOiByZXR1cm4gc2VsZi5jb29yZHNbMF0KICAgICAgICBpZiBuYW1lID09ICJ5IjogcmV0dXJuIHNlbGYuY29vcmRzWzFdCiAgICAgICAgaWYgbmFtZSA9PSAieiI6IHJldHVybiBzZWxmLmNvb3Jkc1syXQogICAgICAgIHJhaXNlIEF0dHJpYnV0ZUVycm9yKG5hbWUpCiAgICBkZWYgX19zZXRhdHRyX18oc2VsZiwgbmFtZSwgdmFsdWUpOgogICAgICAgIGlmIG5hbWUgPT0gIngiOiBzZWxmLmNvb3Jkc1swXSA9IHZhbHVlCiAgICAgICAgZWxpZiBuYW1lID09ICJ5Ijogc2VsZi5jb29yZHNbMV0gPSB2YWx1ZQogICAgICAgIGVsaWYgbmFtZSA9PSAieiI6IHNlbGYuY29vcmRzWzJdID0gdmFsdWUKICAgICAgICBlbHNlOiBvYmplY3QuX19zZXRhdHRyX18oc2VsZiwgbmFtZSwgdmFsdWUpCiAgICBkZWYgY29weShzZWxmKToKICAgICAgICByZXR1cm4gUG9pbnQoKnNlbGYuY29vcmRzWzpdKQogICAgZGVmIF9faGFzaF9fKHNlbGYpOgogICAgICAgIHJldHVybiBoYXNoKHNlbGYudHVwbGUoKSkKICAgIGRlZiBfX3JlcHJfXyhzZWxmKToKICAgICAgICB1c2VfbmljZV9mbG9hdHMgPSBGYWxzZQogICAgICAgIGlmIHVzZV9uaWNlX2Zsb2F0czoKICAgICAgICAgICAgcmV0dXJuICJQb2ludCgiICsgIiwgIi5qb2luKCIlLjFmIiAlIGMgZm9yIGMgaW4gc2VsZi5jb29yZHMpICsgIikiCiAgICAgICAgZWxzZToKICAgICAgICAgICAgcmV0dXJuICJQb2ludCgiICsgIiwgIi5qb2luKHN0cihjKSBmb3IgYyBpbiBzZWxmLmNvb3JkcykgKyAiKSIKCmRlZiB0b19yYWRpYW5zKHgpOgogICAgcmV0dXJuIG1hdGgucGkgKiAyICogeCAvIDM2MC4wCgojY29udmVydHMgZnJvbSBoZXggZ3JpZCBjb29yZGluYXRlcyB0byBzY3JlZW4gY29vcmRpbmF0ZXMuCiNhc3N1bWVzIHRoYXQgdGhlIGhleGFnb25zIGhhdmUgYSBtaW5vciByYWRpdXMgb2YgMS4KZGVmIGhleF90b19zY3JlZW4ocCk6CiAgICB5ID0gMiAqIHAueSAqIG1hdGguc2luKHRvX3JhZGlhbnMoNjApKQogICAgeCA9IDIgKiAoKHAueSAqIG1hdGguY29zKHRvX3JhZGlhbnMoNjApKSkgKyBwLngpCiAgICByZXR1cm4gUG9pbnQoeCx5KQoKI2ZpbmRzIHRoZSBjb29yZGluYXRlcyBvZiB0aGUgY2VudGVyIG9mIGVhY2ggY2lyY2xlIG9mIGBtaW5vcmAgcmFkaXVzIHRoYXQgY2FuIGZpdCBpbiBhIGNpcmNsZSBvZiBgbWFqb3JgIHJhZGl1cy4KZGVmIGdldF9jZW50ZXJzKG1ham9yLCBtaW5vcik6CiAgICBhc3NlcnQgbWFqb3IgPiBtaW5vcgogICAgZXh0ZW50ID0gaW50KDIgKiBtYWpvciAvIG1pbm9yKQogICAgZm9yIGkgaW4gcmFuZ2UoLWV4dGVudCwgZXh0ZW50KToKICAgICAgICBmb3IgaiBpbiByYW5nZSgtZXh0ZW50LCBleHRlbnQpOgogICAgICAgICAgICBwID0gaGV4X3RvX3NjcmVlbihQb2ludChpLGopKSAqIG1pbm9yCiAgICAgICAgICAgIGlmIHAubWFnbml0dWRlKCkgKyBtaW5vciA8PSBtYWpvcjoKICAgICAgICAgICAgICAgIHlpZWxkIHAKCm1tX3Blcl9pbmNoID0gMjUuNAp2YWx1ZXMgPSBbMSwgMiwgNSwgMTAsIDIwLCA1MCwgMTAwLCAyMDBdCmRpYW1ldGVycyA9IFsyMC4zLCAyNS45LCAxOC4wLCAyNC41LCAyMS40LCAyNy4zLCAyMi41LCAyOC40XQpmb3IgdmFsdWUsIGRpYW1ldGVyIGluIHppcCh2YWx1ZXMsIGRpYW1ldGVycyk6CiAgICBmb3IgbWFqb3JfZGlhbWV0ZXIgaW4gWzEyLCAxNF06CiAgICAgICAgbWFqb3JfcmFkaXVzID0gbWFqb3JfZGlhbWV0ZXIgKiBtbV9wZXJfaW5jaCAvIDIKICAgICAgICBjb3VudCA9IGxlbihsaXN0KGdldF9jZW50ZXJzKG1ham9yX3JhZGl1cywgZGlhbWV0ZXIvMikpKQogICAgICAgIGNvc3QgPSBjb3VudCAqIHZhbHVlIC8gMTAwLjAKICAgICAgICBwcmludCAie30gcGVuY2UgY29pbiBvbiB7fVwiIHBpenphOiB7fSBjb2lucywge30gcG91bmRzIi5mb3JtYXQodmFsdWUsIG1ham9yX2RpYW1ldGVyLCBjb3VudCwgY29zdCk=