Jane Street current puzzle: https://www.janestreet.com/puzzles/current-puzzle/
I wrote a program to simulate the process of filling in elements.
If anyone is interested, copy the code into Notebook and after that,
you can update each cell by m[i, j].update(new_val)
. Setting a cell to -1 means to shade it.
import numpy as np
import seaborn as sns
from typing import List, Optional
import matplotlib.colors as colors
from matplotlib import pyplot as plt
# b is the board, each number represent a connected region, must be filled with the same number
b = [
[ 1, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4],
[ 1, 5, 5, 5, 2, 2, 3, 4, 4, 4, 6],
[ 1, 5, 5, 2, 2, 2, 3, 4, 4, 4, 6],
[ 1, 5, 5, 2, 2, 7, 7, 4, 6, 6, 6],
[ 1, 5, 2, 2, 4, 4, 7, 4, 6, 9, 6],
[ 1, 4, 4, 4, 4, 4, 4, 4, 9, 9,10],
[11, 4, 4, 4, 4,12,12, 4, 9, 9, 9],
[11,11,13, 4,13,12,12, 4, 4, 9, 4],
[11,11,13,13,13,12,12, 4, 4, 4, 4],
[11,13,13,11,11,11,12, 4, 4, 4,14],
[11,11,11,11,11,12,12,12, 4, 4,14],
]
def sieve_of_eratosthenes(limit):
# Create a boolean array "prime[0..limit]" and initialize
# all entries it as true. A value in prime[i] will
# finally be false if i is Not a prime, else true.
prime = [True for _ in range(limit + 1)]
p = 2
while (p * p <= limit):
# If prime[p] is not changed, then it is a prime
if prime[p]:
# Updating all multiples of p to not prime
for i in range(p * p, limit + 1, p):
prime[i] = False
p += 1
prime[0], prime[1] = False, False # 0 and 1 are not primes
# Collecting all prime numbers
primes = [p for p, is_prime in enumerate(prime) if is_prime]
return primes
square = lambda x: int(x**0.5)**2 == x
one_more_than_a_palindrome = lambda x: str(x-1) == str(x-1)[::-1]
sum_of_digit_is_7 = lambda x: sum(int(i) for i in str(x)) == 7
multiple_of_37 = lambda x: x % 37 == 0
palindrome_multiple_of_23 = lambda x: x % 23 == 0 and str(x) == str(x)[::-1]
product_of_digits_ends_in_1 = lambda x: np.prod([int(i) for i in str(x)]) % 10 == 1
multiple_of_88 = lambda x: x % 88 == 0
one_less_than_a_palindrome = lambda x: str(x+1) == str(x+1)[::-1]
def fibonacci(x):
if x in {1, 2, 3, 5, 8, 13, 21, 34, 55, 89}:
return True
x1 = x * 5**0.5
q = np.log(x1) / np.log((1 + 5**0.5) / 2)
q = np.round(q)
x2 = (((1+5**0.5)/2)**q - ((1-5**0.5)/2)**q) / 5**0.5
return np.allclose(x, x2)
primes = sieve_of_eratosthenes(10**6)
pop = set()
for i in primes:
for j in primes:
if i**j >= 10**12:
break
pop.add(i**j)
def prime_raised_to_a_prime_power(x):
return x in pop
rules = [
square,
one_more_than_a_palindrome,
prime_raised_to_a_prime_power,
sum_of_digit_is_7,
fibonacci,
square,
multiple_of_37,
palindrome_multiple_of_23,
product_of_digits_ends_in_1,
multiple_of_88,
one_less_than_a_palindrome
]
def valid_rules(m):
valid = [[True] * 11 for _ in range(11)]
words = []
for i in range(11):
s = ""
st = 0
for j in range(11):
cell = m[i, j]
if cell.m == 1:
words.append([i, st, j, s])
s = ""
st = j+1
else:
s += str(cell.v)
words.append([i, st, 11, s])
words = [i for i in words if i[-1] != ""]
for (i, st, ed, s) in words:
if len(s) == 1 or s[0] == '0' or not rules[i](int(s)):
for j in range(st, ed):
valid[i][j] = False
return valid
def plot_map(m):
# plot heatmap, color based on m.c, annotate with m.val
annot = [[str(m[i, j].v) if m[i, j].v != -1 else "" for j in range(11)] for i in range(11)]
colors = [[m[i, j].c if m[i, j].m != 1 else 15 for j in range(11)] for i in range(11)]
valid = [[m[i, j].valid() for j in range(11)] for i in range(11)]
valid2 = valid_rules(m)
plt.figure(figsize=(12, 12))
plt.rcParams.update({'font.size': 15})
ax = sns.heatmap(colors, annot=annot, fmt="s", cmap='tab20', vmin=0.5, vmax=20.5, cbar_kws={"ticks": list(range(21))}, square=True)
ax.xaxis.tick_top()
def x(ax, i, j):
ax.plot([i, i+1], [j, j+1], color='red', linewidth=2)
ax.plot([i, i+1], [j+1, j], color='red', linewidth=2)
def xx(ax, i, j):
ax.vlines(i+0.5, j, j+1, color='red', linewidth=2, alpha=0.2)
ax.hlines(j+0.5, i, i+1, color='red', linewidth=2, alpha=0.2)
for i in range(11):
for j in range(11):
if not valid[i][j]:
x(ax, j, i)
if not valid2[i][j]:
xx(ax, j, i)
class Cell:
def __init__(self, i, j) -> None:
self.m = 0 # mask
self.c = -100 # color
self.v = 0 # value
# neighbor cells
self.u = None # type: Optional[Cell]
self.l = None # type: Optional[Cell]
self.d = None # type: Optional[Cell]
self.r = None # type: Optional[Cell]
self.i = i
self.j = j
def valid(self) -> bool:
if self.m == 1:
for nbr in [self.u, self.l, self.d, self.r]:
if nbr and nbr.m == 1:
return False
return True
for nbr in [self.u, self.l, self.d, self.r]:
if nbr is None or nbr.m == 1:
continue
elif nbr.c == self.c and nbr.v != self.v:
return False
elif nbr.c != self.c and nbr.v == self.v:
return False
return True
def update_and_plot(self, new_val):
if new_val != -1:
self.m = 0
self.update_val(new_val)
else:
self.m = 1
self.v = -1
plot_map(m)
def update(self, new_val):
return self.update_and_plot(new_val)
def update_val(self, new_val, visited=None):
if visited is None:
visited = set()
if self.m == 1:
return
self.v = new_val
visited.add(self)
for neighbor in [self.u, self.l, self.d, self.r]:
if neighbor and neighbor not in visited and neighbor.m == 0 and neighbor.c == self.c:
neighbor.update_val(new_val, visited)
def __str__(self): return "-1" if self.m else str(self.v)
def __repr__(self): return "-1" if self.m else str(self.v)
m = np.array([[Cell(i, j) for j in range(11)] for i in range(11)])
init_val = {i: np.random.randint(0, 10) for i in range(1, 15)}
# fill color
for i in range(11):
for j in range(11):
m[i, j].c = b[i][j]
m[i, j].v = init_val[b[i][j]]
m[i, j].m = int(np.random.rand() > 0.999)
if m[i, j].m:
m[i, j].v = -1
# set neighbors
if i > 0: m[i, j].u = m[i-1, j]
if j > 0: m[i, j].l = m[i, j-1]
if i < 10: m[i, j].d = m[i+1, j]
if j < 10: m[i, j].r = m[i, j+1]
plot_map(m)