mirror of
https://github.com/InternLM/InternBootcamp.git
synced 2026-04-19 12:58:04 +00:00
1599 lines
36 KiB
Python
Executable file
1599 lines
36 KiB
Python
Executable file
# types
|
|
|
|
|
|
from typing import (
|
|
List,
|
|
Union,
|
|
Tuple,
|
|
Any,
|
|
Container,
|
|
Callable,
|
|
FrozenSet,
|
|
Iterable
|
|
)
|
|
|
|
Boolean = bool
|
|
Integer = int
|
|
IntegerTuple = Tuple[Integer, Integer]
|
|
Numerical = Union[Integer, IntegerTuple]
|
|
IntegerSet = FrozenSet[Integer]
|
|
Grid = Tuple[Tuple[Integer]]
|
|
Cell = Tuple[Integer, IntegerTuple]
|
|
Object = FrozenSet[Cell]
|
|
Objects = FrozenSet[Object]
|
|
Indices = FrozenSet[IntegerTuple]
|
|
IndicesSet = FrozenSet[Indices]
|
|
Patch = Union[Object, Indices]
|
|
Element = Union[Object, Grid]
|
|
Piece = Union[Grid, Patch]
|
|
TupleTuple = Tuple[Tuple]
|
|
ContainerContainer = Container[Container]
|
|
|
|
|
|
|
|
# constants
|
|
|
|
|
|
ZERO = 0
|
|
ONE = 1
|
|
TWO = 2
|
|
THREE = 3
|
|
FOUR = 4
|
|
FIVE = 5
|
|
SIX = 6
|
|
SEVEN = 7
|
|
EIGHT = 8
|
|
NINE = 9
|
|
TEN = 10
|
|
F = False
|
|
T = True
|
|
|
|
NEG_ONE = -1
|
|
|
|
ORIGIN = (0, 0)
|
|
UNITY = (1, 1)
|
|
DOWN = (1, 0)
|
|
RIGHT = (0, 1)
|
|
UP = (-1, 0)
|
|
LEFT = (0, -1)
|
|
|
|
NEG_TWO = -2
|
|
NEG_UNITY = (-1, -1)
|
|
UP_RIGHT = (-1, 1)
|
|
DOWN_LEFT = (1, -1)
|
|
|
|
ZERO_BY_TWO = (0, 2)
|
|
TWO_BY_ZERO = (2, 0)
|
|
TWO_BY_TWO = (2, 2)
|
|
THREE_BY_THREE = (3, 3)
|
|
|
|
|
|
|
|
# primitives
|
|
|
|
|
|
def identity(
|
|
x: Any
|
|
) -> Any:
|
|
""" identity function """
|
|
return x
|
|
|
|
|
|
def add(
|
|
a: Numerical,
|
|
b: Numerical
|
|
) -> Numerical:
|
|
""" addition """
|
|
if isinstance(a, int) and isinstance(b, int):
|
|
return a + b
|
|
elif isinstance(a, tuple) and isinstance(b, tuple):
|
|
return (a[0] + b[0], a[1] + b[1])
|
|
elif isinstance(a, int) and isinstance(b, tuple):
|
|
return (a + b[0], a + b[1])
|
|
return (a[0] + b, a[1] + b)
|
|
|
|
|
|
def subtract(
|
|
a: Numerical,
|
|
b: Numerical
|
|
) -> Numerical:
|
|
""" subtraction """
|
|
if isinstance(a, int) and isinstance(b, int):
|
|
return a - b
|
|
elif isinstance(a, tuple) and isinstance(b, tuple):
|
|
return (a[0] - b[0], a[1] - b[1])
|
|
elif isinstance(a, int) and isinstance(b, tuple):
|
|
return (a - b[0], a - b[1])
|
|
return (a[0] - b, a[1] - b)
|
|
|
|
|
|
def multiply(
|
|
a: Numerical,
|
|
b: Numerical
|
|
) -> Numerical:
|
|
""" multiplication """
|
|
if isinstance(a, int) and isinstance(b, int):
|
|
return a * b
|
|
elif isinstance(a, tuple) and isinstance(b, tuple):
|
|
return (a[0] * b[0], a[1] * b[1])
|
|
elif isinstance(a, int) and isinstance(b, tuple):
|
|
return (a * b[0], a * b[1])
|
|
return (a[0] * b, a[1] * b)
|
|
|
|
|
|
def divide(
|
|
a: Numerical,
|
|
b: Numerical
|
|
) -> Numerical:
|
|
""" floor division """
|
|
if isinstance(a, int) and isinstance(b, int):
|
|
return a // b
|
|
elif isinstance(a, tuple) and isinstance(b, tuple):
|
|
return (a[0] // b[0], a[1] // b[1])
|
|
elif isinstance(a, int) and isinstance(b, tuple):
|
|
return (a // b[0], a // b[1])
|
|
return (a[0] // b, a[1] // b)
|
|
|
|
|
|
def invert(
|
|
n: Numerical
|
|
) -> Numerical:
|
|
""" inversion with respect to addition """
|
|
return -n if isinstance(n, int) else (-n[0], -n[1])
|
|
|
|
|
|
def even(
|
|
n: Integer
|
|
) -> Boolean:
|
|
""" evenness """
|
|
return n % 2 == 0
|
|
|
|
|
|
def double(
|
|
n: Numerical
|
|
) -> Numerical:
|
|
""" scaling by two """
|
|
return n * 2 if isinstance(n, int) else (n[0] * 2, n[1] * 2)
|
|
|
|
|
|
def halve(
|
|
n: Numerical
|
|
) -> Numerical:
|
|
""" scaling by one half """
|
|
return n // 2 if isinstance(n, int) else (n[0] // 2, n[1] // 2)
|
|
|
|
|
|
def flip(
|
|
b: Boolean
|
|
) -> Boolean:
|
|
""" logical not """
|
|
return not b
|
|
|
|
|
|
def equality(
|
|
a: Any,
|
|
b: Any
|
|
) -> Boolean:
|
|
""" equality """
|
|
return a == b
|
|
|
|
|
|
def contained(
|
|
value: Any,
|
|
container: Container
|
|
) -> Boolean:
|
|
""" element of """
|
|
return value in container
|
|
|
|
|
|
def combine(
|
|
a: Container,
|
|
b: Container
|
|
) -> Container:
|
|
""" union """
|
|
return type(a)((*a, *b))
|
|
|
|
|
|
def intersection(
|
|
a: FrozenSet,
|
|
b: FrozenSet
|
|
) -> FrozenSet:
|
|
""" returns the intersection of two containers """
|
|
return a & b
|
|
|
|
|
|
def difference(
|
|
a: Container,
|
|
b: Container
|
|
) -> Container:
|
|
""" difference """
|
|
return type(a)(e for e in a if e not in b)
|
|
|
|
|
|
def dedupe(
|
|
iterable: Tuple
|
|
) -> Tuple:
|
|
""" remove duplicates """
|
|
return tuple(e for i, e in enumerate(iterable) if iterable.index(e) == i)
|
|
|
|
|
|
def order(
|
|
container: Container,
|
|
compfunc: Callable
|
|
) -> Tuple:
|
|
""" order container by custom key """
|
|
return tuple(sorted(container, key=compfunc))
|
|
|
|
|
|
def repeat(
|
|
item: Any,
|
|
num: Integer
|
|
) -> Tuple:
|
|
""" repetition of item within vector """
|
|
return tuple(item for i in range(num))
|
|
|
|
|
|
def greater(
|
|
a: Integer,
|
|
b: Integer
|
|
) -> Boolean:
|
|
""" greater """
|
|
return a > b
|
|
|
|
|
|
def size(
|
|
container: Container
|
|
) -> Integer:
|
|
""" cardinality """
|
|
return len(container)
|
|
|
|
|
|
def merge(
|
|
containers: ContainerContainer
|
|
) -> Container:
|
|
""" merging """
|
|
return type(containers)(e for c in containers for e in c)
|
|
|
|
|
|
def maximum(
|
|
container: IntegerSet
|
|
) -> Integer:
|
|
""" maximum """
|
|
return max(container, default=0)
|
|
|
|
|
|
def minimum(
|
|
container: IntegerSet
|
|
) -> Integer:
|
|
""" minimum """
|
|
return min(container, default=0)
|
|
|
|
|
|
def valmax(
|
|
container: Container,
|
|
compfunc: Callable
|
|
) -> Integer:
|
|
""" maximum by custom function """
|
|
return compfunc(max(container, key=compfunc, default=0))
|
|
|
|
|
|
def valmin(
|
|
container: Container,
|
|
compfunc: Callable
|
|
) -> Integer:
|
|
""" minimum by custom function """
|
|
return compfunc(min(container, key=compfunc, default=0))
|
|
|
|
|
|
def argmax(
|
|
container: Container,
|
|
compfunc: Callable
|
|
) -> Any:
|
|
""" largest item by custom order """
|
|
return max(container, key=compfunc, default=None)
|
|
|
|
|
|
def argmin(
|
|
container: Container,
|
|
compfunc: Callable
|
|
) -> Any:
|
|
""" smallest item by custom order """
|
|
return min(container, key=compfunc, default=None)
|
|
|
|
|
|
def mostcommon(
|
|
container: Container
|
|
) -> Any:
|
|
""" most common item """
|
|
return max(set(container), key=container.count)
|
|
|
|
|
|
def leastcommon(
|
|
container: Container
|
|
) -> Any:
|
|
""" least common item """
|
|
return min(set(container), key=container.count)
|
|
|
|
|
|
def initset(
|
|
value: Any
|
|
) -> FrozenSet:
|
|
""" initialize container """
|
|
return frozenset({value})
|
|
|
|
|
|
def both(
|
|
a: Boolean,
|
|
b: Boolean
|
|
) -> Boolean:
|
|
""" logical and """
|
|
return a and b
|
|
|
|
|
|
def either(
|
|
a: Boolean,
|
|
b: Boolean
|
|
) -> Boolean:
|
|
""" logical or """
|
|
return a or b
|
|
|
|
|
|
def increment(
|
|
x: Numerical
|
|
) -> Numerical:
|
|
""" incrementing """
|
|
return x + 1 if isinstance(x, int) else (x[0] + 1, x[1] + 1)
|
|
|
|
|
|
def decrement(
|
|
x: Numerical
|
|
) -> Numerical:
|
|
""" decrementing """
|
|
return x - 1 if isinstance(x, int) else (x[0] - 1, x[1] - 1)
|
|
|
|
|
|
def crement(
|
|
x: Numerical
|
|
) -> Numerical:
|
|
""" incrementing positive and decrementing negative """
|
|
if isinstance(x, int):
|
|
return 0 if x == 0 else (x + 1 if x > 0 else x - 1)
|
|
return (
|
|
0 if x[0] == 0 else (x[0] + 1 if x[0] > 0 else x[0] - 1),
|
|
0 if x[1] == 0 else (x[1] + 1 if x[1] > 0 else x[1] - 1)
|
|
)
|
|
|
|
|
|
def sign(
|
|
x: Numerical
|
|
) -> Numerical:
|
|
""" sign """
|
|
if isinstance(x, int):
|
|
return 0 if x == 0 else (1 if x > 0 else -1)
|
|
return (
|
|
0 if x[0] == 0 else (1 if x[0] > 0 else -1),
|
|
0 if x[1] == 0 else (1 if x[1] > 0 else -1)
|
|
)
|
|
|
|
|
|
def positive(
|
|
x: Integer
|
|
) -> Boolean:
|
|
""" positive """
|
|
return x > 0
|
|
|
|
|
|
def toivec(
|
|
i: Integer
|
|
) -> IntegerTuple:
|
|
""" vector pointing vertically """
|
|
return (i, 0)
|
|
|
|
|
|
def tojvec(
|
|
j: Integer
|
|
) -> IntegerTuple:
|
|
""" vector pointing horizontally """
|
|
return (0, j)
|
|
|
|
|
|
def sfilter(
|
|
container: Container,
|
|
condition: Callable
|
|
) -> Container:
|
|
""" keep elements in container that satisfy condition """
|
|
return type(container)(e for e in container if condition(e))
|
|
|
|
|
|
def mfilter(
|
|
container: Container,
|
|
function: Callable
|
|
) -> FrozenSet:
|
|
""" filter and merge """
|
|
return merge(sfilter(container, function))
|
|
|
|
|
|
def extract(
|
|
container: Container,
|
|
condition: Callable
|
|
) -> Any:
|
|
""" first element of container that satisfies condition """
|
|
return next(e for e in container if condition(e))
|
|
|
|
|
|
def totuple(
|
|
container: FrozenSet
|
|
) -> Tuple:
|
|
""" conversion to tuple """
|
|
return tuple(container)
|
|
|
|
|
|
def first(
|
|
container: Container
|
|
) -> Any:
|
|
""" first item of container """
|
|
return next(iter(container))
|
|
|
|
|
|
def last(
|
|
container: Container
|
|
) -> Any:
|
|
""" last item of container """
|
|
return max(enumerate(container))[1]
|
|
|
|
|
|
def insert(
|
|
value: Any,
|
|
container: FrozenSet
|
|
) -> FrozenSet:
|
|
""" insert item into container """
|
|
return container.union(frozenset({value}))
|
|
|
|
|
|
def remove(
|
|
value: Any,
|
|
container: Container
|
|
) -> Container:
|
|
""" remove item from container """
|
|
return type(container)(e for e in container if e != value)
|
|
|
|
|
|
def other(
|
|
container: Container,
|
|
value: Any
|
|
) -> Any:
|
|
""" other value in the container """
|
|
return first(remove(value, container))
|
|
|
|
|
|
def interval(
|
|
start: Integer,
|
|
stop: Integer,
|
|
step: Integer
|
|
) -> Tuple:
|
|
""" range """
|
|
return tuple(range(start, stop, step))
|
|
|
|
|
|
def astuple(
|
|
a: Integer,
|
|
b: Integer
|
|
) -> IntegerTuple:
|
|
""" constructs a tuple """
|
|
return (a, b)
|
|
|
|
|
|
def product(
|
|
a: Container,
|
|
b: Container
|
|
) -> FrozenSet:
|
|
""" cartesian product """
|
|
return frozenset((i, j) for j in b for i in a)
|
|
|
|
|
|
def pair(
|
|
a: Tuple,
|
|
b: Tuple
|
|
) -> TupleTuple:
|
|
""" zipping of two tuples """
|
|
return tuple(zip(a, b))
|
|
|
|
|
|
def branch(
|
|
condition: Boolean,
|
|
if_value: Any,
|
|
else_value: Any
|
|
) -> Any:
|
|
""" if else branching """
|
|
return if_value if condition else else_value
|
|
|
|
|
|
def compose(
|
|
outer: Callable,
|
|
inner: Callable
|
|
) -> Callable:
|
|
""" function composition """
|
|
return lambda x: outer(inner(x))
|
|
|
|
|
|
def chain(
|
|
h: Callable,
|
|
g: Callable,
|
|
f: Callable
|
|
) -> Callable:
|
|
""" function composition with three functions """
|
|
return lambda x: h(g(f(x)))
|
|
|
|
|
|
def matcher(
|
|
function: Callable,
|
|
target: Any
|
|
) -> Callable:
|
|
""" construction of equality function """
|
|
return lambda x: function(x) == target
|
|
|
|
|
|
def rbind(
|
|
function: Callable,
|
|
fixed: Any
|
|
) -> Callable:
|
|
""" fix the rightmost argument """
|
|
n = function.__code__.co_argcount
|
|
if n == 2:
|
|
return lambda x: function(x, fixed)
|
|
elif n == 3:
|
|
return lambda x, y: function(x, y, fixed)
|
|
else:
|
|
return lambda x, y, z: function(x, y, z, fixed)
|
|
|
|
|
|
def lbind(
|
|
function: Callable,
|
|
fixed: Any
|
|
) -> Callable:
|
|
""" fix the leftmost argument """
|
|
n = function.__code__.co_argcount
|
|
if n == 2:
|
|
return lambda y: function(fixed, y)
|
|
elif n == 3:
|
|
return lambda y, z: function(fixed, y, z)
|
|
else:
|
|
return lambda y, z, a: function(fixed, y, z, a)
|
|
|
|
|
|
def power(
|
|
function: Callable,
|
|
n: Integer
|
|
) -> Callable:
|
|
""" power of function """
|
|
if n == 1:
|
|
return function
|
|
return compose(function, power(function, n - 1))
|
|
|
|
|
|
def fork(
|
|
outer: Callable,
|
|
a: Callable,
|
|
b: Callable
|
|
) -> Callable:
|
|
""" creates a wrapper function """
|
|
return lambda x: outer(a(x), b(x))
|
|
|
|
|
|
def apply(
|
|
function: Callable,
|
|
container: Container
|
|
) -> Container:
|
|
""" apply function to each item in container """
|
|
return type(container)(function(e) for e in container)
|
|
|
|
|
|
def rapply(
|
|
functions: Container,
|
|
value: Any
|
|
) -> Container:
|
|
""" apply each function in container to value """
|
|
return type(functions)(function(value) for function in functions)
|
|
|
|
|
|
def mapply(
|
|
function: Callable,
|
|
container: ContainerContainer
|
|
) -> FrozenSet:
|
|
""" apply and merge """
|
|
return merge(apply(function, container))
|
|
|
|
|
|
def papply(
|
|
function: Callable,
|
|
a: Tuple,
|
|
b: Tuple
|
|
) -> Tuple:
|
|
""" apply function on two vectors """
|
|
return tuple(function(i, j) for i, j in zip(a, b))
|
|
|
|
|
|
def mpapply(
|
|
function: Callable,
|
|
a: Tuple,
|
|
b: Tuple
|
|
) -> Tuple:
|
|
""" apply function on two vectors and merge """
|
|
return merge(papply(function, a, b))
|
|
|
|
|
|
def prapply(
|
|
function: Callable,
|
|
a: Container,
|
|
b: Container
|
|
) -> FrozenSet:
|
|
""" apply function on cartesian product """
|
|
return frozenset(function(i, j) for j in b for i in a)
|
|
|
|
|
|
def mostcolor(
|
|
element: Element
|
|
) -> Integer:
|
|
""" most common color """
|
|
values = [v for r in element for v in r] if isinstance(element, tuple) else [v for v, _ in element]
|
|
return max(set(values), key=values.count)
|
|
|
|
|
|
def leastcolor(
|
|
element: Element
|
|
) -> Integer:
|
|
""" least common color """
|
|
values = [v for r in element for v in r] if isinstance(element, tuple) else [v for v, _ in element]
|
|
return min(set(values), key=values.count)
|
|
|
|
|
|
def height(
|
|
piece: Piece
|
|
) -> Integer:
|
|
""" height of grid or patch """
|
|
if len(piece) == 0:
|
|
return 0
|
|
if isinstance(piece, tuple):
|
|
return len(piece)
|
|
return lowermost(piece) - uppermost(piece) + 1
|
|
|
|
|
|
def width(
|
|
piece: Piece
|
|
) -> Integer:
|
|
""" width of grid or patch """
|
|
if len(piece) == 0:
|
|
return 0
|
|
if isinstance(piece, tuple):
|
|
return len(piece[0])
|
|
return rightmost(piece) - leftmost(piece) + 1
|
|
|
|
|
|
def shape(
|
|
piece: Piece
|
|
) -> IntegerTuple:
|
|
""" height and width of grid or patch """
|
|
return (height(piece), width(piece))
|
|
|
|
|
|
def portrait(
|
|
piece: Piece
|
|
) -> Boolean:
|
|
""" whether height is greater than width """
|
|
return height(piece) > width(piece)
|
|
|
|
|
|
def colorcount(
|
|
element: Element,
|
|
value: Integer
|
|
) -> Integer:
|
|
""" number of cells with color """
|
|
if isinstance(element, tuple):
|
|
return sum(row.count(value) for row in element)
|
|
return sum(v == value for v, _ in element)
|
|
|
|
|
|
def colorfilter(
|
|
objs: Objects,
|
|
value: Integer
|
|
) -> Objects:
|
|
""" filter objects by color """
|
|
return frozenset(obj for obj in objs if next(iter(obj))[0] == value)
|
|
|
|
|
|
def sizefilter(
|
|
container: Container,
|
|
n: Integer
|
|
) -> FrozenSet:
|
|
""" filter items by size """
|
|
return frozenset(item for item in container if len(item) == n)
|
|
|
|
|
|
def asindices(
|
|
grid: Grid
|
|
) -> Indices:
|
|
""" indices of all grid cells """
|
|
return frozenset((i, j) for i in range(len(grid)) for j in range(len(grid[0])))
|
|
|
|
|
|
def ofcolor(
|
|
grid: Grid,
|
|
value: Integer
|
|
) -> Indices:
|
|
""" indices of all grid cells with value """
|
|
return frozenset((i, j) for i, r in enumerate(grid) for j, v in enumerate(r) if v == value)
|
|
|
|
|
|
def ulcorner(
|
|
patch: Patch
|
|
) -> IntegerTuple:
|
|
""" index of upper left corner """
|
|
return tuple(map(min, zip(*toindices(patch))))
|
|
|
|
|
|
def urcorner(
|
|
patch: Patch
|
|
) -> IntegerTuple:
|
|
""" index of upper right corner """
|
|
return tuple(map(lambda ix: {0: min, 1: max}[ix[0]](ix[1]), enumerate(zip(*toindices(patch)))))
|
|
|
|
|
|
def llcorner(
|
|
patch: Patch
|
|
) -> IntegerTuple:
|
|
""" index of lower left corner """
|
|
return tuple(map(lambda ix: {0: max, 1: min}[ix[0]](ix[1]), enumerate(zip(*toindices(patch)))))
|
|
|
|
|
|
def lrcorner(
|
|
patch: Patch
|
|
) -> IntegerTuple:
|
|
""" index of lower right corner """
|
|
return tuple(map(max, zip(*toindices(patch))))
|
|
|
|
|
|
def crop(
|
|
grid: Grid,
|
|
start: IntegerTuple,
|
|
dims: IntegerTuple
|
|
) -> Grid:
|
|
""" subgrid specified by start and dimension """
|
|
return tuple(r[start[1]:start[1]+dims[1]] for r in grid[start[0]:start[0]+dims[0]])
|
|
|
|
|
|
def toindices(
|
|
patch: Patch
|
|
) -> Indices:
|
|
""" indices of object cells """
|
|
if len(patch) == 0:
|
|
return frozenset()
|
|
if isinstance(next(iter(patch))[1], tuple):
|
|
return frozenset(index for value, index in patch)
|
|
return patch
|
|
|
|
|
|
def recolor(
|
|
value: Integer,
|
|
patch: Patch
|
|
) -> Object:
|
|
""" recolor patch """
|
|
return frozenset((value, index) for index in toindices(patch))
|
|
|
|
|
|
def shift(
|
|
patch: Patch,
|
|
directions: IntegerTuple
|
|
) -> Patch:
|
|
""" shift patch """
|
|
if len(patch) == 0:
|
|
return patch
|
|
di, dj = directions
|
|
if isinstance(next(iter(patch))[1], tuple):
|
|
return frozenset((value, (i + di, j + dj)) for value, (i, j) in patch)
|
|
return frozenset((i + di, j + dj) for i, j in patch)
|
|
|
|
|
|
def normalize(
|
|
patch: Patch
|
|
) -> Patch:
|
|
""" moves upper left corner to origin """
|
|
if len(patch) == 0:
|
|
return patch
|
|
return shift(patch, (-uppermost(patch), -leftmost(patch)))
|
|
|
|
|
|
def dneighbors(
|
|
loc: IntegerTuple
|
|
) -> Indices:
|
|
""" directly adjacent indices """
|
|
return frozenset({(loc[0] - 1, loc[1]), (loc[0] + 1, loc[1]), (loc[0], loc[1] - 1), (loc[0], loc[1] + 1)})
|
|
|
|
|
|
def ineighbors(
|
|
loc: IntegerTuple
|
|
) -> Indices:
|
|
""" diagonally adjacent indices """
|
|
return frozenset({(loc[0] - 1, loc[1] - 1), (loc[0] - 1, loc[1] + 1), (loc[0] + 1, loc[1] - 1), (loc[0] + 1, loc[1] + 1)})
|
|
|
|
|
|
def neighbors(
|
|
loc: IntegerTuple
|
|
) -> Indices:
|
|
""" adjacent indices """
|
|
return dneighbors(loc) | ineighbors(loc)
|
|
|
|
|
|
def objects(
|
|
grid: Grid,
|
|
univalued: Boolean,
|
|
diagonal: Boolean,
|
|
without_bg: Boolean
|
|
) -> Objects:
|
|
""" objects occurring on the grid """
|
|
bg = mostcolor(grid) if without_bg else None
|
|
objs = set()
|
|
occupied = set()
|
|
h, w = len(grid), len(grid[0])
|
|
unvisited = asindices(grid)
|
|
diagfun = neighbors if diagonal else dneighbors
|
|
for loc in unvisited:
|
|
if loc in occupied:
|
|
continue
|
|
val = grid[loc[0]][loc[1]]
|
|
if val == bg:
|
|
continue
|
|
obj = {(val, loc)}
|
|
cands = {loc}
|
|
while len(cands) > 0:
|
|
neighborhood = set()
|
|
for cand in cands:
|
|
v = grid[cand[0]][cand[1]]
|
|
if (val == v) if univalued else (v != bg):
|
|
obj.add((v, cand))
|
|
occupied.add(cand)
|
|
neighborhood |= {
|
|
(i, j) for i, j in diagfun(cand) if 0 <= i < h and 0 <= j < w
|
|
}
|
|
cands = neighborhood - occupied
|
|
objs.add(frozenset(obj))
|
|
return frozenset(objs)
|
|
|
|
|
|
def partition(
|
|
grid: Grid
|
|
) -> Objects:
|
|
""" each cell with the same value part of the same object """
|
|
return frozenset(
|
|
frozenset(
|
|
(v, (i, j)) for i, r in enumerate(grid) for j, v in enumerate(r) if v == value
|
|
) for value in palette(grid)
|
|
)
|
|
|
|
|
|
def fgpartition(
|
|
grid: Grid
|
|
) -> Objects:
|
|
""" each cell with the same value part of the same object without background """
|
|
return frozenset(
|
|
frozenset(
|
|
(v, (i, j)) for i, r in enumerate(grid) for j, v in enumerate(r) if v == value
|
|
) for value in palette(grid) - {mostcolor(grid)}
|
|
)
|
|
|
|
|
|
def uppermost(
|
|
patch: Patch
|
|
) -> Integer:
|
|
""" row index of uppermost occupied cell """
|
|
return min(i for i, j in toindices(patch))
|
|
|
|
|
|
def lowermost(
|
|
patch: Patch
|
|
) -> Integer:
|
|
""" row index of lowermost occupied cell """
|
|
return max(i for i, j in toindices(patch))
|
|
|
|
|
|
def leftmost(
|
|
patch: Patch
|
|
) -> Integer:
|
|
""" column index of leftmost occupied cell """
|
|
return min(j for i, j in toindices(patch))
|
|
|
|
|
|
def rightmost(
|
|
patch: Patch
|
|
) -> Integer:
|
|
""" column index of rightmost occupied cell """
|
|
return max(j for i, j in toindices(patch))
|
|
|
|
|
|
def square(
|
|
piece: Piece
|
|
) -> Boolean:
|
|
""" whether the piece forms a square """
|
|
return len(piece) == len(piece[0]) if isinstance(piece, tuple) else height(piece) * width(piece) == len(piece) and height(piece) == width(piece)
|
|
|
|
|
|
def vline(
|
|
patch: Patch
|
|
) -> Boolean:
|
|
""" whether the piece forms a vertical line """
|
|
return height(patch) == len(patch) and width(patch) == 1
|
|
|
|
|
|
def hline(
|
|
patch: Patch
|
|
) -> Boolean:
|
|
""" whether the piece forms a horizontal line """
|
|
return width(patch) == len(patch) and height(patch) == 1
|
|
|
|
|
|
def hmatching(
|
|
a: Patch,
|
|
b: Patch
|
|
) -> Boolean:
|
|
""" whether there exists a row for which both patches have cells """
|
|
return len(set(i for i, j in toindices(a)) & set(i for i, j in toindices(b))) > 0
|
|
|
|
|
|
def vmatching(
|
|
a: Patch,
|
|
b: Patch
|
|
) -> Boolean:
|
|
""" whether there exists a column for which both patches have cells """
|
|
return len(set(j for i, j in toindices(a)) & set(j for i, j in toindices(b))) > 0
|
|
|
|
|
|
def manhattan(
|
|
a: Patch,
|
|
b: Patch
|
|
) -> Integer:
|
|
""" closest manhattan distance between two patches """
|
|
return min(abs(ai - bi) + abs(aj - bj) for ai, aj in toindices(a) for bi, bj in toindices(b))
|
|
|
|
|
|
def adjacent(
|
|
a: Patch,
|
|
b: Patch
|
|
) -> Boolean:
|
|
""" whether two patches are adjacent """
|
|
return manhattan(a, b) == 1
|
|
|
|
|
|
def bordering(
|
|
patch: Patch,
|
|
grid: Grid
|
|
) -> Boolean:
|
|
""" whether a patch is adjacent to a grid border """
|
|
return uppermost(patch) == 0 or leftmost(patch) == 0 or lowermost(patch) == len(grid) - 1 or rightmost(patch) == len(grid[0]) - 1
|
|
|
|
|
|
def centerofmass(
|
|
patch: Patch
|
|
) -> IntegerTuple:
|
|
""" center of mass """
|
|
return tuple(map(lambda x: sum(x) // len(patch), zip(*toindices(patch))))
|
|
|
|
|
|
def palette(
|
|
element: Element
|
|
) -> IntegerSet:
|
|
""" colors occurring in object or grid """
|
|
if isinstance(element, tuple):
|
|
return frozenset({v for r in element for v in r})
|
|
return frozenset({v for v, _ in element})
|
|
|
|
|
|
def numcolors(
|
|
element: Element
|
|
) -> IntegerSet:
|
|
""" number of colors occurring in object or grid """
|
|
return len(palette(element))
|
|
|
|
|
|
def color(
|
|
obj: Object
|
|
) -> Integer:
|
|
""" color of object """
|
|
return next(iter(obj))[0]
|
|
|
|
|
|
def toobject(
|
|
patch: Patch,
|
|
grid: Grid
|
|
) -> Object:
|
|
""" object from patch and grid """
|
|
h, w = len(grid), len(grid[0])
|
|
return frozenset((grid[i][j], (i, j)) for i, j in toindices(patch) if 0 <= i < h and 0 <= j < w)
|
|
|
|
|
|
def asobject(
|
|
grid: Grid
|
|
) -> Object:
|
|
""" conversion of grid to object """
|
|
return frozenset((v, (i, j)) for i, r in enumerate(grid) for j, v in enumerate(r))
|
|
|
|
|
|
def rot90(
|
|
grid: Grid
|
|
) -> Grid:
|
|
""" quarter clockwise rotation """
|
|
return tuple(row for row in zip(*grid[::-1]))
|
|
|
|
|
|
def rot180(
|
|
grid: Grid
|
|
) -> Grid:
|
|
""" half rotation """
|
|
return tuple(tuple(row[::-1]) for row in grid[::-1])
|
|
|
|
|
|
def rot270(
|
|
grid: Grid
|
|
) -> Grid:
|
|
""" quarter anticlockwise rotation """
|
|
return tuple(tuple(row[::-1]) for row in zip(*grid[::-1]))[::-1]
|
|
|
|
|
|
def hmirror(
|
|
piece: Piece
|
|
) -> Piece:
|
|
""" mirroring along horizontal """
|
|
if isinstance(piece, tuple):
|
|
return piece[::-1]
|
|
d = ulcorner(piece)[0] + lrcorner(piece)[0]
|
|
if isinstance(next(iter(piece))[1], tuple):
|
|
return frozenset((v, (d - i, j)) for v, (i, j) in piece)
|
|
return frozenset((d - i, j) for i, j in piece)
|
|
|
|
|
|
def vmirror(
|
|
piece: Piece
|
|
) -> Piece:
|
|
""" mirroring along vertical """
|
|
if isinstance(piece, tuple):
|
|
return tuple(row[::-1] for row in piece)
|
|
d = ulcorner(piece)[1] + lrcorner(piece)[1]
|
|
if isinstance(next(iter(piece))[1], tuple):
|
|
return frozenset((v, (i, d - j)) for v, (i, j) in piece)
|
|
return frozenset((i, d - j) for i, j in piece)
|
|
|
|
|
|
def dmirror(
|
|
piece: Piece
|
|
) -> Piece:
|
|
""" mirroring along diagonal """
|
|
if isinstance(piece, tuple):
|
|
return tuple(zip(*piece))
|
|
a, b = ulcorner(piece)
|
|
if isinstance(next(iter(piece))[1], tuple):
|
|
return frozenset((v, (j - b + a, i - a + b)) for v, (i, j) in piece)
|
|
return frozenset((j - b + a, i - a + b) for i, j in piece)
|
|
|
|
|
|
def cmirror(
|
|
piece: Piece
|
|
) -> Piece:
|
|
""" mirroring along counterdiagonal """
|
|
if isinstance(piece, tuple):
|
|
return tuple(zip(*(r[::-1] for r in piece[::-1])))
|
|
return vmirror(dmirror(vmirror(piece)))
|
|
|
|
|
|
def fill(
|
|
grid: Grid,
|
|
value: Integer,
|
|
patch: Patch
|
|
) -> Grid:
|
|
""" fill value at indices """
|
|
h, w = len(grid), len(grid[0])
|
|
grid_filled = list(list(row) for row in grid)
|
|
for i, j in toindices(patch):
|
|
if 0 <= i < h and 0 <= j < w:
|
|
grid_filled[i][j] = value
|
|
return tuple(tuple(row) for row in grid_filled)
|
|
|
|
|
|
def paint(
|
|
grid: Grid,
|
|
obj: Object
|
|
) -> Grid:
|
|
""" paint object to grid """
|
|
h, w = len(grid), len(grid[0])
|
|
grid_painted = list(list(row) for row in grid)
|
|
for value, (i, j) in obj:
|
|
if 0 <= i < h and 0 <= j < w:
|
|
grid_painted[i][j] = value
|
|
return tuple(tuple(row) for row in grid_painted)
|
|
|
|
|
|
def underfill(
|
|
grid: Grid,
|
|
value: Integer,
|
|
patch: Patch
|
|
) -> Grid:
|
|
""" fill value at indices that are background """
|
|
h, w = len(grid), len(grid[0])
|
|
bg = mostcolor(grid)
|
|
grid_filled = list(list(row) for row in grid)
|
|
for i, j in toindices(patch):
|
|
if 0 <= i < h and 0 <= j < w:
|
|
if grid_filled[i][j] == bg:
|
|
grid_filled[i][j] = value
|
|
return tuple(tuple(row) for row in grid_filled)
|
|
|
|
|
|
def underpaint(
|
|
grid: Grid,
|
|
obj: Object
|
|
) -> Grid:
|
|
""" paint object to grid where there is background """
|
|
h, w = len(grid), len(grid[0])
|
|
bg = mostcolor(grid)
|
|
grid_painted = list(list(row) for row in grid)
|
|
for value, (i, j) in obj:
|
|
if 0 <= i < h and 0 <= j < w:
|
|
if grid_painted[i][j] == bg:
|
|
grid_painted[i][j] = value
|
|
return tuple(tuple(row) for row in grid_painted)
|
|
|
|
|
|
def hupscale(
|
|
grid: Grid,
|
|
factor: Integer
|
|
) -> Grid:
|
|
""" upscale grid horizontally """
|
|
upscaled_grid = tuple()
|
|
for row in grid:
|
|
upscaled_row = tuple()
|
|
for value in row:
|
|
upscaled_row = upscaled_row + tuple(value for num in range(factor))
|
|
upscaled_grid = upscaled_grid + (upscaled_row,)
|
|
return upscaled_grid
|
|
|
|
|
|
def vupscale(
|
|
grid: Grid,
|
|
factor: Integer
|
|
) -> Grid:
|
|
""" upscale grid vertically """
|
|
upscaled_grid = tuple()
|
|
for row in grid:
|
|
upscaled_grid = upscaled_grid + tuple(row for num in range(factor))
|
|
return upscaled_grid
|
|
|
|
|
|
def upscale(
|
|
element: Element,
|
|
factor: Integer
|
|
) -> Element:
|
|
""" upscale object or grid """
|
|
if isinstance(element, tuple):
|
|
upscaled_grid = tuple()
|
|
for row in element:
|
|
upscaled_row = tuple()
|
|
for value in row:
|
|
upscaled_row = upscaled_row + tuple(value for num in range(factor))
|
|
upscaled_grid = upscaled_grid + tuple(upscaled_row for num in range(factor))
|
|
return upscaled_grid
|
|
else:
|
|
if len(element) == 0:
|
|
return frozenset()
|
|
di_inv, dj_inv = ulcorner(element)
|
|
di, dj = (-di_inv, -dj_inv)
|
|
normed_obj = shift(element, (di, dj))
|
|
upscaled_obj = set()
|
|
for value, (i, j) in normed_obj:
|
|
for io in range(factor):
|
|
for jo in range(factor):
|
|
upscaled_obj.add((value, (i * factor + io, j * factor + jo)))
|
|
return shift(frozenset(upscaled_obj), (di_inv, dj_inv))
|
|
|
|
|
|
def downscale(
|
|
grid: Grid,
|
|
factor: Integer
|
|
) -> Grid:
|
|
""" downscale grid """
|
|
h, w = len(grid), len(grid[0])
|
|
downscaled_grid = tuple()
|
|
for i in range(h):
|
|
downscaled_row = tuple()
|
|
for j in range(w):
|
|
if j % factor == 0:
|
|
downscaled_row = downscaled_row + (grid[i][j],)
|
|
downscaled_grid = downscaled_grid + (downscaled_row, )
|
|
h = len(downscaled_grid)
|
|
downscaled_grid2 = tuple()
|
|
for i in range(h):
|
|
if i % factor == 0:
|
|
downscaled_grid2 = downscaled_grid2 + (downscaled_grid[i],)
|
|
return downscaled_grid2
|
|
|
|
|
|
def hconcat(
|
|
a: Grid,
|
|
b: Grid
|
|
) -> Grid:
|
|
""" concatenate two grids horizontally """
|
|
return tuple(i + j for i, j in zip(a, b))
|
|
|
|
|
|
def vconcat(
|
|
a: Grid,
|
|
b: Grid
|
|
) -> Grid:
|
|
""" concatenate two grids vertically """
|
|
return a + b
|
|
|
|
|
|
def subgrid(
|
|
patch: Patch,
|
|
grid: Grid
|
|
) -> Grid:
|
|
""" smallest subgrid containing object """
|
|
return crop(grid, ulcorner(patch), shape(patch))
|
|
|
|
|
|
def hsplit(
|
|
grid: Grid,
|
|
n: Integer
|
|
) -> Tuple:
|
|
""" split grid horizontally """
|
|
h, w = len(grid), len(grid[0]) // n
|
|
offset = len(grid[0]) % n != 0
|
|
return tuple(crop(grid, (0, w * i + i * offset), (h, w)) for i in range(n))
|
|
|
|
|
|
def vsplit(
|
|
grid: Grid,
|
|
n: Integer
|
|
) -> Tuple:
|
|
""" split grid vertically """
|
|
h, w = len(grid) // n, len(grid[0])
|
|
offset = len(grid) % n != 0
|
|
return tuple(crop(grid, (h * i + i * offset, 0), (h, w)) for i in range(n))
|
|
|
|
|
|
def cellwise(
|
|
a: Grid,
|
|
b: Grid,
|
|
fallback: Integer
|
|
) -> Grid:
|
|
""" cellwise match of two grids """
|
|
h, w = len(a), len(a[0])
|
|
resulting_grid = tuple()
|
|
for i in range(h):
|
|
row = tuple()
|
|
for j in range(w):
|
|
a_value = a[i][j]
|
|
value = a_value if a_value == b[i][j] else fallback
|
|
row = row + (value,)
|
|
resulting_grid = resulting_grid + (row, )
|
|
return resulting_grid
|
|
|
|
|
|
def replace(
|
|
grid: Grid,
|
|
replacee: Integer,
|
|
replacer: Integer
|
|
) -> Grid:
|
|
""" color substitution """
|
|
return tuple(tuple(replacer if v == replacee else v for v in r) for r in grid)
|
|
|
|
|
|
def switch(
|
|
grid: Grid,
|
|
a: Integer,
|
|
b: Integer
|
|
) -> Grid:
|
|
""" color switching """
|
|
return tuple(tuple(v if (v != a and v != b) else {a: b, b: a}[v] for v in r) for r in grid)
|
|
|
|
|
|
def center(
|
|
patch: Patch
|
|
) -> IntegerTuple:
|
|
""" center of the patch """
|
|
return (uppermost(patch) + height(patch) // 2, leftmost(patch) + width(patch) // 2)
|
|
|
|
|
|
def position(
|
|
a: Patch,
|
|
b: Patch
|
|
) -> IntegerTuple:
|
|
""" relative position between two patches """
|
|
ia, ja = center(toindices(a))
|
|
ib, jb = center(toindices(b))
|
|
if ia == ib:
|
|
return (0, 1 if ja < jb else -1)
|
|
elif ja == jb:
|
|
return (1 if ia < ib else -1, 0)
|
|
elif ia < ib:
|
|
return (1, 1 if ja < jb else -1)
|
|
elif ia > ib:
|
|
return (-1, 1 if ja < jb else -1)
|
|
|
|
|
|
def index(
|
|
grid: Grid,
|
|
loc: IntegerTuple
|
|
) -> Integer:
|
|
""" color at location """
|
|
i, j = loc
|
|
h, w = len(grid), len(grid[0])
|
|
if not (0 <= i < h and 0 <= j < w):
|
|
return None
|
|
return grid[loc[0]][loc[1]]
|
|
|
|
|
|
def canvas(
|
|
value: Integer,
|
|
dimensions: IntegerTuple
|
|
) -> Grid:
|
|
""" grid construction """
|
|
return tuple(tuple(value for j in range(dimensions[1])) for i in range(dimensions[0]))
|
|
|
|
|
|
def corners(
|
|
patch: Patch
|
|
) -> Indices:
|
|
""" indices of corners """
|
|
return frozenset({ulcorner(patch), urcorner(patch), llcorner(patch), lrcorner(patch)})
|
|
|
|
|
|
def connect(
|
|
a: IntegerTuple,
|
|
b: IntegerTuple
|
|
) -> Indices:
|
|
""" line between two points """
|
|
ai, aj = a
|
|
bi, bj = b
|
|
si = min(ai, bi)
|
|
ei = max(ai, bi) + 1
|
|
sj = min(aj, bj)
|
|
ej = max(aj, bj) + 1
|
|
if ai == bi:
|
|
return frozenset((ai, j) for j in range(sj, ej))
|
|
elif aj == bj:
|
|
return frozenset((i, aj) for i in range(si, ei))
|
|
elif bi - ai == bj - aj:
|
|
return frozenset((i, j) for i, j in zip(range(si, ei), range(sj, ej)))
|
|
elif bi - ai == aj - bj:
|
|
return frozenset((i, j) for i, j in zip(range(si, ei), range(ej - 1, sj - 1, -1)))
|
|
return frozenset()
|
|
|
|
|
|
def cover(
|
|
grid: Grid,
|
|
patch: Patch
|
|
) -> Grid:
|
|
""" remove object from grid """
|
|
return fill(grid, mostcolor(grid), toindices(patch))
|
|
|
|
|
|
def trim(
|
|
grid: Grid
|
|
) -> Grid:
|
|
""" trim border of grid """
|
|
return tuple(r[1:-1] for r in grid[1:-1])
|
|
|
|
|
|
def move(
|
|
grid: Grid,
|
|
obj: Object,
|
|
offset: IntegerTuple
|
|
) -> Grid:
|
|
""" move object on grid """
|
|
return paint(cover(grid, obj), shift(obj, offset))
|
|
|
|
|
|
def tophalf(
|
|
grid: Grid
|
|
) -> Grid:
|
|
""" upper half of grid """
|
|
return grid[:len(grid) // 2]
|
|
|
|
|
|
def bottomhalf(
|
|
grid: Grid
|
|
) -> Grid:
|
|
""" lower half of grid """
|
|
return grid[len(grid) // 2 + len(grid) % 2:]
|
|
|
|
|
|
def lefthalf(
|
|
grid: Grid
|
|
) -> Grid:
|
|
""" left half of grid """
|
|
return rot270(tophalf(rot90(grid)))
|
|
|
|
|
|
def righthalf(
|
|
grid: Grid
|
|
) -> Grid:
|
|
""" right half of grid """
|
|
return rot270(bottomhalf(rot90(grid)))
|
|
|
|
|
|
def vfrontier(
|
|
location: IntegerTuple
|
|
) -> Indices:
|
|
""" vertical frontier """
|
|
return frozenset((i, location[1]) for i in range(30))
|
|
|
|
|
|
def hfrontier(
|
|
location: IntegerTuple
|
|
) -> Indices:
|
|
""" horizontal frontier """
|
|
return frozenset((location[0], j) for j in range(30))
|
|
|
|
|
|
def backdrop(
|
|
patch: Patch
|
|
) -> Indices:
|
|
""" indices in bounding box of patch """
|
|
if len(patch) == 0:
|
|
return frozenset({})
|
|
indices = toindices(patch)
|
|
si, sj = ulcorner(indices)
|
|
ei, ej = lrcorner(patch)
|
|
return frozenset((i, j) for i in range(si, ei + 1) for j in range(sj, ej + 1))
|
|
|
|
|
|
def delta(
|
|
patch: Patch
|
|
) -> Indices:
|
|
""" indices in bounding box but not part of patch """
|
|
if len(patch) == 0:
|
|
return frozenset({})
|
|
return backdrop(patch) - toindices(patch)
|
|
|
|
|
|
def gravitate(
|
|
source: Patch,
|
|
destination: Patch
|
|
) -> IntegerTuple:
|
|
""" direction to move source until adjacent to destination """
|
|
source_i, source_j = center(source)
|
|
destination_i, destination_j = center(destination)
|
|
i, j = 0, 0
|
|
if vmatching(source, destination):
|
|
i = 1 if source_i < destination_i else -1
|
|
else:
|
|
j = 1 if source_j < destination_j else -1
|
|
direction = (i, j)
|
|
gravitation_i, gravitation_j = i, j
|
|
maxcount = 42
|
|
c = 0
|
|
while not adjacent(source, destination) and c < maxcount:
|
|
c += 1
|
|
gravitation_i += i
|
|
gravitation_j += j
|
|
source = shift(source, direction)
|
|
return (gravitation_i - i, gravitation_j - j)
|
|
|
|
|
|
def inbox(
|
|
patch: Patch
|
|
) -> Indices:
|
|
""" inbox for patch """
|
|
ai, aj = uppermost(patch) + 1, leftmost(patch) + 1
|
|
bi, bj = lowermost(patch) - 1, rightmost(patch) - 1
|
|
si, sj = min(ai, bi), min(aj, bj)
|
|
ei, ej = max(ai, bi), max(aj, bj)
|
|
vlines = {(i, sj) for i in range(si, ei + 1)} | {(i, ej) for i in range(si, ei + 1)}
|
|
hlines = {(si, j) for j in range(sj, ej + 1)} | {(ei, j) for j in range(sj, ej + 1)}
|
|
return frozenset(vlines | hlines)
|
|
|
|
|
|
def outbox(
|
|
patch: Patch
|
|
) -> Indices:
|
|
""" outbox for patch """
|
|
ai, aj = uppermost(patch) - 1, leftmost(patch) - 1
|
|
bi, bj = lowermost(patch) + 1, rightmost(patch) + 1
|
|
si, sj = min(ai, bi), min(aj, bj)
|
|
ei, ej = max(ai, bi), max(aj, bj)
|
|
vlines = {(i, sj) for i in range(si, ei + 1)} | {(i, ej) for i in range(si, ei + 1)}
|
|
hlines = {(si, j) for j in range(sj, ej + 1)} | {(ei, j) for j in range(sj, ej + 1)}
|
|
return frozenset(vlines | hlines)
|
|
|
|
|
|
def box(
|
|
patch: Patch
|
|
) -> Indices:
|
|
""" outline of patch """
|
|
if len(patch) == 0:
|
|
return patch
|
|
ai, aj = ulcorner(patch)
|
|
bi, bj = lrcorner(patch)
|
|
si, sj = min(ai, bi), min(aj, bj)
|
|
ei, ej = max(ai, bi), max(aj, bj)
|
|
vlines = {(i, sj) for i in range(si, ei + 1)} | {(i, ej) for i in range(si, ei + 1)}
|
|
hlines = {(si, j) for j in range(sj, ej + 1)} | {(ei, j) for j in range(sj, ej + 1)}
|
|
return frozenset(vlines | hlines)
|
|
|
|
|
|
def shoot(
|
|
start: IntegerTuple,
|
|
direction: IntegerTuple
|
|
) -> Indices:
|
|
""" line from starting point and direction """
|
|
return connect(start, (start[0] + 42 * direction[0], start[1] + 42 * direction[1]))
|
|
|
|
|
|
def occurrences(
|
|
grid: Grid,
|
|
obj: Object
|
|
) -> Indices:
|
|
""" locations of occurrences of object in grid """
|
|
occurrences = set()
|
|
normed = normalize(obj)
|
|
h, w = len(grid), len(grid[0])
|
|
for i in range(h):
|
|
for j in range(w):
|
|
occurs = True
|
|
for v, (a, b) in shift(normed, (i, j)):
|
|
if 0 <= a < h and 0 <= b < w:
|
|
if grid[a][b] != v:
|
|
occurs = False
|
|
break
|
|
else:
|
|
occurs = False
|
|
break
|
|
if occurs:
|
|
occurrences.add((i, j))
|
|
return frozenset(occurrences)
|
|
|
|
|
|
def frontiers(
|
|
grid: Grid
|
|
) -> Objects:
|
|
""" set of frontiers """
|
|
h, w = len(grid), len(grid[0])
|
|
row_indices = tuple(i for i, r in enumerate(grid) if len(set(r)) == 1)
|
|
column_indices = tuple(j for j, c in enumerate(dmirror(grid)) if len(set(c)) == 1)
|
|
hfrontiers = frozenset({frozenset({(grid[i][j], (i, j)) for j in range(w)}) for i in row_indices})
|
|
vfrontiers = frozenset({frozenset({(grid[i][j], (i, j)) for i in range(h)}) for j in column_indices})
|
|
return hfrontiers | vfrontiers
|
|
|
|
|
|
def compress(
|
|
grid: Grid
|
|
) -> Grid:
|
|
""" removes frontiers from grid """
|
|
ri = tuple(i for i, r in enumerate(grid) if len(set(r)) == 1)
|
|
ci = tuple(j for j, c in enumerate(dmirror(grid)) if len(set(c)) == 1)
|
|
return tuple(tuple(v for j, v in enumerate(r) if j not in ci) for i, r in enumerate(grid) if i not in ri)
|
|
|
|
|
|
def hperiod(
|
|
obj: Object
|
|
) -> Integer:
|
|
""" horizontal periodicity """
|
|
normalized = normalize(obj)
|
|
w = width(normalized)
|
|
for p in range(1, w):
|
|
offsetted = shift(normalized, (0, -p))
|
|
pruned = frozenset({(c, (i, j)) for c, (i, j) in offsetted if j >= 0})
|
|
if pruned.issubset(normalized):
|
|
return p
|
|
return w
|
|
|
|
|
|
def vperiod(
|
|
obj: Object
|
|
) -> Integer:
|
|
""" vertical periodicity """
|
|
normalized = normalize(obj)
|
|
h = height(normalized)
|
|
for p in range(1, h):
|
|
offsetted = shift(normalized, (-p, 0))
|
|
pruned = frozenset({(c, (i, j)) for c, (i, j) in offsetted if i >= 0})
|
|
if pruned.issubset(normalized):
|
|
return p
|
|
return h
|