-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathgroup.py
147 lines (103 loc) · 4.21 KB
/
group.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
from typing import Self
class Group:
"""
Group of Go stones
Args:
intersections: set of indices of stones in the group
borders: set of indices of intersections bordering the group
liberties: subset of borders containing empty intersections
group_type: numeric type of the stones in the group
"""
def __init__(self, intersections: set[int], borders: set[int],
liberties: set[int], group_type: int) -> None:
self.intersections = intersections
self.borders = borders
self.liberties = liberties
self.group_type = group_type
def __str__(self) -> str:
"""Return only intersections and liberties"""
return f"Ints: {self.intersections} Libs: {self.liberties}"
def __repr__(self) -> str:
"""Repr identical to str"""
return str(self)
def copy(self) -> Self:
"""Create copy of this group"""
return Group(
intersections = self.intersections.copy(),
borders = self.borders.copy(),
liberties = self.liberties.copy(),
group_type = self.group_type
)
def union_in_place_(self, other: Self) -> Self:
"""
Union this group with another group
Changes only this group
"""
if self.group_type != other.group_type:
raise ValueError("Groups may only union with same type Groups")
self.intersections |= other.intersections
self.borders |= other.borders
self.liberties |= other.liberties
self.borders -= self.intersections
self.liberties -= self.intersections
if not self.liberties <= self.borders:
raise NotImplementedError("Oops")
return self
@staticmethod
def add_union(groups: list[Self], new_stone_group: Self) -> list[Self]:
"""
Returns a minimized list of disjoint groups after adding a
new group containing exactly 1 stone
Args:
groups: list of Groups
new_stone_group: Group containing exactly 1 stone
"""
if len(new_stone_group.intersections) != 1:
raise ValueError("new_stone_group must have exactly one intersection")
out = []
need_union = [new_stone_group]
new_stone = list(new_stone_group.intersections)[0]
# Split groups
for group in groups:
if new_stone in group.borders and group.group_type == new_stone_group.group_type:
need_union.append(group.copy())
else:
out.append(group.copy())
# Union groups of same stone containing the new stone as a liberty
new_intersections = set().union(*[group.intersections for group in need_union])
new_borders = set().union(*[group.borders for group in need_union])
new_liberties = set().union(*[group.liberties for group in need_union])
new_borders -= new_intersections
new_liberties -= new_intersections
unioned = Group(
intersections=new_intersections,
borders=new_borders,
liberties=new_liberties,
group_type=new_stone_group.group_type
)
# Add unioned group back to group list
out.append(unioned)
# New stone cannot be liberty
for group in out:
group.liberties -= {new_stone}
return out
def replenish_liberties(self, opens: set[int]) -> set[int]:
"""
Adds back liberties after intersections were captured
Returns the resulting set of liberties
Args:
opens: set of newly open intersections
"""
self.liberties |= self.borders & opens
return self.liberties
def trim_liberties(self, groups: list[Self]) -> set[int]:
"""
Removes all invalid liberties in this Group based on
intersections provided in a list of Groups in place
Also returns the resulting set of liberties
Args:
groups: list of Groups containing stones
"""
for group in groups:
self.liberties -= group.intersections
return self.liberties