Jonathan Frechとmotavicaによって7バイト節約
B=frozenset
M=min
X={}
T=lambda s:((x,y)for y,x in s)
N=lambda s:B((x-M(s)[0],y-M(T(s))[0])for x,y in s)
G=lambda s:{(x,y):s&{(x-1,y),(x+1,y),(x,y-1),(x,y+1)}for(x,y)in s}
C=lambda g,s,i=0:i<=len(g)and(len(g)==len(s)or C(g,s.union(*map(g.get,s)),i+1))
F=lambda s:N([(-x,y)for x,y in s])
P=lambda s,i=4:M([N(s),F(s),P(F(T(s)),i-1)],key=list)if i else s
S=lambda s,t=B(),i=0:t|S(s,B().union(u|{p}for u in t for p in s-u if C(G(u|{p}),{p}))if i else B([B([next(iter(s))])]),i+1)if-~i<len(s)else t
def U(s):
k=P(s)
if k in X:return
j=[t for t in S(k)if C(G(k-t),{next(iter(k-t))})];X[k]={P(t):B()for t in j}
for t in j:X[k][P(t)]|=B([B(P(k-t))]);U(t);U(k-t)
V=lambda s,t:1+M(V(v,w)for u in B(X[s])&B(X[t])for v in X[s][u]for w in X[t][u])if s^t else 1
A=lambda s,t:U(s)or U(t)or V(P(s),P(t))
オンラインでお試しください!
使用法
A(s, t)
x, y
グリッド位置のリストによって各形状が与えられる2つの形状を取り込みます。
グラフィカル表現を位置のリストに変換するヘルパー関数は次のとおりです。
def H(g):
g=g.split("\n")
return[(x,y)for x in range(len(g[0]))for y in range(len(g))if"#"==g[y][x]]
例:
case1_1 = \
""".....
.###.
.#.#.
.###.
....."""
case1_2 = \
"""###..
..#..
..#..
..###
....."""
print(A(H(case1_1), H(case1_2))) # Prints 2
説明
ここで使用されているアルゴリズムは、サブシェイプをキャッシュするため、ブルートフォースよりも少し優れています。与えられた形状について、それはその形状を2つの隣接する形状に分割するすべての方法をキャッシュし、次にこれらの形状を正規化します(原点から始まるように座標をシフトし、次にキャッシュで使用されるその回転/反射を見つけます)。後ですばやく検索できるように、それらをキャッシュに保存します。すべてのサブシェイプは、単一のブロックシェイプになるまで、サブシェイプもキャッシュされます。
これらのサブシェイプは、グラフの隣接リストに変換し、BFSを使用してすべてのサブグラフを生成することによって生成されます。次に、これらのサブグラフをフィルタリングして、含まれていなかった頂点が接続されたコンポーネントになるようにします。グラフが接続されているかどうかの判別は、別のBFSで行われます。
キャッシュが完了した後、2つの形状を比較して、共通のサブ形状を見つけることにより、ソリューションが見つかります。サブシェイプのこれらのリストを取得すると、共通のシェイプを削除した後に残ったサブシェイプのペアを取り、同じアルゴリズムを再帰的に適用して、シェイプを再構築するために必要な最小数のブロックを見つけます。次に、これらすべての値の最小値を持つサブシェイプを返し、ソリューションが得られます。
各行が何をしているのかを説明するために、以下に注釈付きのバージョンを配置しました。
B=frozenset
M=min
# Shapes are stored as a frozenset of tuples where each tuple represents an (x, y) position
# Cache of shape partitions. This is a two-level dictionary where the outer key is a shape, and the inner key is a sub-shape where the value is a list of the shapes left when the sub-shape is removed.
# there may be multiple shapes in the inner-most list if the sub-shape appears multiple times
X={}
# Transpose list of coords (flip around diagonal axis)
T=lambda s:((x,y)for y,x in s)
# Translate shape so its upper-left corner is at the origin
N=lambda s:B((x-M(s)[0],y-M(T(s))[0])for x,y in s)
# Convert shape to graph in adjacency list form
G=lambda s:{(x,y):s&{(x-1,y),(x+1,y),(x,y-1),(x,y+1)}for(x,y)in s}
# Check if graph is connected given a set of nodes, s, known to be connected
C=lambda g,s,i=0:i<=len(g)and(len(g)==len(s)or C(g,s.union(*map(g.get,s)),i+1))
# Flip shape around vertical axis
F=lambda s:N([(-x,y)for x,y in s])
# Converts shape to the minimal reflection or rotation. rotation is equivalent to transpose then flip.
P=lambda s,i=4:M([N(s),F(s),P(F(T(s)),i-1)],key=list)if i else s
# returns all the contiguous sub-shapes of s that contain the first pos, given by next(iter(s))
S=lambda s,t=B(),i=0:t|S(s,B().union(u|{p}for u in t for p in s-u if C(G(u|{p}),{p}))if i else B([B([next(iter(s))])]),i+1)if-~i<len(s)else t
# updates the sub-shape cache, X, recursively for an input shape s
def U(s):
k=P(s)
if k in X:return
j=[t for t in S(k)if C(G(k-t),{next(iter(k-t))})];X[k]={P(t):B()for t in j}
for t in j:X[k][P(t)]|=B([B(P(k-t))]);U(t);U(k-t)
# Gets the minimum number of partitions for two shapes
V=lambda s,t:1+M(V(v,w)for u in B(X[s])&B(X[t])for v in X[s][u]for w in X[t][u])if s^t else 1
# The function to run, will update the cache for the two input shapes then return the minimum number of partitions
A=lambda s,t:U(s)or U(t)or V(P(s),P(t))