#!/usr/bin/env python3
from collections import namedtuple
from math import ceil, floor
Circle = namedtuple('Circle', 'x, y, r')
def count_lattice_points(r):
"""Count the number of lattice points inside a circle of radius *r*.
https://e...content-available-to-author-only...a.org/wiki/Gauss_circle_problem
"""
assert isinstance(r, int)
return 1 + 4 * r + 4 * sum(int((r**2 - i**2)**.5) for i in range(1, r + 1))
def count_lattice_points_intersection(a, b):
"""The number of lattice points within intersection of circle *a* and *b*.
x, y, r are all integers
"""
# d = (a.x - b.x)**2 + (a.y - b.y)**2 # distance squared
# if d > (a.r + b.r)**2: # no intersection
# return 0
# elif d == (a.r + b.r)**2: # touch in one point
# # (y - a.y)/ a.r == (b.y - a.y) / (a.r + b.r)
# # (x - a.x)/ a.r == (b.x - a.x) / (a.r + b.r)
# return (a.r * (b.y - a.y) % (a.r + b.r) == 0 and
# a.r * (b.x - a.x) % (a.r + b.r) == 0)
# elif d <= (a.r - b.r)**2: # one circle within another
# return count_lattice_points(min(a.r, b.r))
# # circumferences intersect in two points
# assert (a.r - b.r)**2 < d < (a.r + b.r)**2
if a.r > b.r:
a, b = b, a # make *a* be the smaller circle
assert a.r <= b.r
# scan from bottom to top
count = 0
for y in range(a.y - a.r, a.y + a.r + 1):
if b.y - b.r <= y <= b.y + b.r: # intersection possible
# find intersection boundaries for given y
ax1, ax2 = x_coordinates_intesect(a, y)
bx1, bx2 = x_coordinates_intesect(b, y)
if min(ax2, bx2) >= max(ax1, bx1): # intersect
count += floor(min(ax2, bx2)) - ceil(max(ax1, bx1)) + 1
return count
def count_lattice_points_intersection_bruteforce(a, b):
return sum(((a.x - x)**2 + (a.y - y)**2 <= a.r**2 and
(b.x - x)**2 + (b.y - y)**2 <= b.r**2)
for y in range(a.y - a.r, a.y + a.r + 1)
for x in range(a.x - a.r, a.x + a.r + 1))
def x_coordinates_intesect(c, y):
"""Get x-coordinates of intersection of circle *c* and horizontal line *y*."""
# solve quadratic equation
# (x - c.x)**2 + (y - c.y)**2 == c.r**2
assert c.r**2 >= (y - c.y)**2
D = (c.r**2 - (y - c.y)**2)**.5
return c.x - D, c.x + D
print(count_lattice_points_intersection(Circle(0, 0, 5), Circle(2, 2, 3)))
for a, b in [
(Circle(0, 0, 5), Circle(2, 2, 3)),
(Circle(0, 0, 4), Circle(2, 2, 3)),
(Circle(0, 0, 6), Circle(2, 2, 3)),
]:
got = count_lattice_points_intersection(a, b)
expected = count_lattice_points_intersection_bruteforce(a, b)
assert got == expected, (a, b, got, expected)
IyEvdXNyL2Jpbi9lbnYgcHl0aG9uMwpmcm9tIGNvbGxlY3Rpb25zIGltcG9ydCBuYW1lZHR1cGxlCmZyb20gbWF0aCBpbXBvcnQgY2VpbCwgZmxvb3IKCkNpcmNsZSA9IG5hbWVkdHVwbGUoJ0NpcmNsZScsICd4LCB5LCByJykKCgpkZWYgY291bnRfbGF0dGljZV9wb2ludHMocik6CiAgICAiIiJDb3VudCB0aGUgbnVtYmVyIG9mIGxhdHRpY2UgcG9pbnRzIGluc2lkZSBhIGNpcmNsZSBvZiByYWRpdXMgKnIqLgoKICAgIGh0dHBzOi8vZS4uLmNvbnRlbnQtYXZhaWxhYmxlLXRvLWF1dGhvci1vbmx5Li4uYS5vcmcvd2lraS9HYXVzc19jaXJjbGVfcHJvYmxlbQogICAgIiIiCiAgICBhc3NlcnQgaXNpbnN0YW5jZShyLCBpbnQpCiAgICByZXR1cm4gMSArIDQgKiByICsgNCAqIHN1bShpbnQoKHIqKjIgLSBpKioyKSoqLjUpIGZvciBpIGluIHJhbmdlKDEsIHIgKyAxKSkKCgpkZWYgY291bnRfbGF0dGljZV9wb2ludHNfaW50ZXJzZWN0aW9uKGEsIGIpOgogICAgIiIiVGhlIG51bWJlciBvZiBsYXR0aWNlIHBvaW50cyB3aXRoaW4gaW50ZXJzZWN0aW9uIG9mIGNpcmNsZSAqYSogYW5kICpiKi4KCiAgICB4LCB5LCByIGFyZSBhbGwgaW50ZWdlcnMKICAgICIiIgogICAgIyBkID0gKGEueCAtIGIueCkqKjIgKyAoYS55IC0gYi55KSoqMiAgIyBkaXN0YW5jZSBzcXVhcmVkCiAgICAjIGlmIGQgPiAoYS5yICsgYi5yKSoqMjogICAgICMgbm8gaW50ZXJzZWN0aW9uCiAgICAjICAgICByZXR1cm4gMAogICAgIyBlbGlmIGQgPT0gKGEuciArIGIucikqKjI6ICAjIHRvdWNoIGluIG9uZSBwb2ludAogICAgIyAgICAgIyAoeSAtIGEueSkvIGEuciA9PSAoYi55IC0gYS55KSAvIChhLnIgKyBiLnIpCiAgICAjICAgICAjICh4IC0gYS54KS8gYS5yID09IChiLnggLSBhLngpIC8gKGEuciArIGIucikKICAgICMgICAgIHJldHVybiAoYS5yICogKGIueSAtIGEueSkgJSAoYS5yICsgYi5yKSA9PSAwIGFuZAogICAgIyAgICAgICAgICAgICBhLnIgKiAoYi54IC0gYS54KSAlIChhLnIgKyBiLnIpID09IDApCiAgICAjIGVsaWYgZCA8PSAoYS5yIC0gYi5yKSoqMjogICMgb25lIGNpcmNsZSB3aXRoaW4gYW5vdGhlcgogICAgIyAgICAgcmV0dXJuIGNvdW50X2xhdHRpY2VfcG9pbnRzKG1pbihhLnIsIGIucikpCgogICAgIyAjIGNpcmN1bWZlcmVuY2VzIGludGVyc2VjdCBpbiB0d28gcG9pbnRzCiAgICAjIGFzc2VydCAoYS5yIC0gYi5yKSoqMiA8IGQgPCAoYS5yICsgYi5yKSoqMgogICAgaWYgYS5yID4gYi5yOgogICAgICAgIGEsIGIgPSBiLCBhICAjIG1ha2UgKmEqIGJlIHRoZSBzbWFsbGVyIGNpcmNsZQogICAgYXNzZXJ0IGEuciA8PSBiLnIKCiAgICAjIHNjYW4gZnJvbSBib3R0b20gdG8gdG9wCiAgICBjb3VudCA9IDAKICAgIGZvciB5IGluIHJhbmdlKGEueSAtIGEuciwgYS55ICsgYS5yICsgMSk6CiAgICAgICAgaWYgYi55IC0gYi5yIDw9IHkgPD0gYi55ICsgYi5yOiAgIyBpbnRlcnNlY3Rpb24gcG9zc2libGUKICAgICAgICAgICAgIyBmaW5kIGludGVyc2VjdGlvbiBib3VuZGFyaWVzIGZvciBnaXZlbiB5CiAgICAgICAgICAgIGF4MSwgYXgyID0geF9jb29yZGluYXRlc19pbnRlc2VjdChhLCB5KQogICAgICAgICAgICBieDEsIGJ4MiA9IHhfY29vcmRpbmF0ZXNfaW50ZXNlY3QoYiwgeSkKICAgICAgICAgICAgaWYgbWluKGF4MiwgYngyKSA+PSBtYXgoYXgxLCBieDEpOiAgIyBpbnRlcnNlY3QKICAgICAgICAgICAgICAgIGNvdW50ICs9IGZsb29yKG1pbihheDIsIGJ4MikpIC0gY2VpbChtYXgoYXgxLCBieDEpKSArIDEKICAgIHJldHVybiBjb3VudAoKCmRlZiBjb3VudF9sYXR0aWNlX3BvaW50c19pbnRlcnNlY3Rpb25fYnJ1dGVmb3JjZShhLCBiKToKICAgIHJldHVybiBzdW0oKChhLnggLSB4KSoqMiArIChhLnkgLSB5KSoqMiA8PSBhLnIqKjIgYW5kCiAgICAgICAgICAgICAgICAoYi54IC0geCkqKjIgKyAoYi55IC0geSkqKjIgPD0gYi5yKioyKQogICAgICAgICAgICAgICBmb3IgeSBpbiByYW5nZShhLnkgLSBhLnIsIGEueSArIGEuciArIDEpCiAgICAgICAgICAgICAgIGZvciB4IGluIHJhbmdlKGEueCAtIGEuciwgYS54ICsgYS5yICsgMSkpCgoKZGVmIHhfY29vcmRpbmF0ZXNfaW50ZXNlY3QoYywgeSk6CiAgICAiIiJHZXQgeC1jb29yZGluYXRlcyBvZiBpbnRlcnNlY3Rpb24gb2YgY2lyY2xlICpjKiBhbmQgaG9yaXpvbnRhbCBsaW5lICp5Ki4iIiIKICAgICMgc29sdmUgcXVhZHJhdGljIGVxdWF0aW9uCiAgICAjICh4IC0gYy54KSoqMiArICh5IC0gYy55KSoqMiA9PSBjLnIqKjIKICAgIGFzc2VydCBjLnIqKjIgPj0gKHkgLSBjLnkpKioyCiAgICBEID0gKGMucioqMiAtICh5IC0gYy55KSoqMikqKi41CiAgICByZXR1cm4gYy54IC0gRCwgYy54ICsgRAoKcHJpbnQoY291bnRfbGF0dGljZV9wb2ludHNfaW50ZXJzZWN0aW9uKENpcmNsZSgwLCAwLCA1KSwgQ2lyY2xlKDIsIDIsIDMpKSkKCmZvciBhLCBiIGluIFsKICAgICAgICAoQ2lyY2xlKDAsIDAsIDUpLCBDaXJjbGUoMiwgMiwgMykpLAogICAgICAgIChDaXJjbGUoMCwgMCwgNCksIENpcmNsZSgyLCAyLCAzKSksCiAgICAgICAgKENpcmNsZSgwLCAwLCA2KSwgQ2lyY2xlKDIsIDIsIDMpKSwKXToKICAgIGdvdCA9IGNvdW50X2xhdHRpY2VfcG9pbnRzX2ludGVyc2VjdGlvbihhLCBiKQogICAgZXhwZWN0ZWQgPSBjb3VudF9sYXR0aWNlX3BvaW50c19pbnRlcnNlY3Rpb25fYnJ1dGVmb3JjZShhLCBiKQogICAgYXNzZXJ0IGdvdCA9PSBleHBlY3RlZCwgKGEsIGIsIGdvdCwgZXhwZWN0ZWQpCg==