考虑以下字母排列:
B
O A
N R I
D E N T
Run Code Online (Sandbox Code Playgroud)
从顶部字母开始,选择以下两个字母之一,Plinko风格,直到你到达底部.无论你选择什么样的路径,你都会创建一个四个字母的单词:BOND,BONE,BORE,BORN,BARE,BARN,BAIN或BAIT.DENT读到底部的事实只是一个很好的巧合.
我想帮助找出一个可以设计这种布局的算法,其中从顶部到底部的每条可能路径都会从(提供的)字典中生成一个不同的单词.程序的输入将是起始字母(在该示例中为B)和字长n(在该示例中为4).它将返回组成这种布局的字母,或者返回表示不可能的消息.它不一定是确定性的,因此它可能会生成具有相同输入的不同布局.
到目前为止,我还没有想到比蛮力方法更好的东西.也就是说,对于26^[(n+2)(n-1)/2]为布局底部选择字母的所有方法,检查所有可能的2^(n-1)路径是否给出字典中的单词.我考虑过某种前缀树,但路径可以交叉并分享与我混淆的字母.我对Python最熟悉,但至少我只是想要一个可以解决这个问题的算法或方法.谢谢.
假装V W X Y Z在底部这里实际上是完整的单词.
B
A O
I R N
T N E D
V W X Y Z
Run Code Online (Sandbox Code Playgroud)
我们可以用启发式扫描来实现一个非常严格的回溯搜索,似乎不太可能出现任何错误的路径.
n在如下的简单树中插入以相同字母开头的所有大小的单词.现在执行深度优先搜索,断言以下内容:每个连续级别需要一个额外的"共享"字母,意味着p(letter)它在该级别上的实例,另外要求他们的两个孩子是相同的字母(例如R括号中的两个s)在2级可能是一个"共享"字母,因为他们的孩子是相同的).
什么是p(letter)?帕斯卡的三角形当然!n choose r根据Plinko董事会的说法,这正是这棵简单树相关层面所需字母的实例数.在第3级,如果我们选择R和R,我们需要3 N秒和3 E秒至表达上该级别的"共享"的信件.并且3 Ns中的每一个必须具有相同的子字母(在这种情况下为W,X),并且3 Es中的每一个也必须(X,Y).
B
/ \
A O
/ \ / \
I (R) (R) N
/ \ / \ / \ / \
T (N) (N) E (N) E E D
V W W X W X X Y W X X Y X Y Y Z
4 W's, 6 X's, 4 Y's
Run Code Online (Sandbox Code Playgroud)
出于好奇,这里有一些Python代码 :)
from itertools import combinations
from copy import deepcopy
# assumes words all start
# with the same letter and
# are of the same length
def insert(word, i, tree):
if i == len(word):
return
if word[i] in tree:
insert(word, i + 1, tree[word[i]])
else:
tree[word[i]] = {}
insert(word, i + 1, tree[word[i]])
# Pascal's triangle
def get_next_needed(needed):
next_needed = [[1, None, 0]] + [None] * (len(needed) - 1) + [[1, None, 0]]
for i, _ in enumerate(needed):
if i == len(needed) - 1:
next_needed[i + 1] = [1, None, 0]
else:
next_needed[i + 1] = [needed[i][0] + needed[i+1][0], None, 0]
return next_needed
def get_candidates(next_needed, chosen, parents):
global log
if log:
print "get_candidates: parents: %s" % parents
# For each chosen node we need two children.
# The corners have only one shared node, while
# the others in each group are identical AND
# must have all have a pair of children identical
# to the others' in the group. Additionally, the
# share sequence matches at the ends of each group.
# I (R) (R) N
# / \ / \ / \ / \
# T (N) (N) E (N) E E D
# Iterate over the parents, choosing
# two nodes for each one
def g(cs, s, seq, i, h):
if log:
print "cs, seq, s, i, h: %s, %s, %s, %s, %s" % (cs, s, seq, i, h)
# Base case, we've achieved a candidate sequence
if i == len(parents):
return [(cs, s, seq)]
# The left character in the corner is
# arbitrary; the next one, shared.
# Left corner:
if i == 0:
candidates = []
for (l, r) in combinations(chosen[0].keys(), 2):
_cs = deepcopy(cs)
_cs[0] = [1, l, 1]
_cs[1][1] = r
_cs[1][2] = 1
_s = s[:]
_s.extend([chosen[0][l], chosen[0][r]])
_h = deepcopy(h)
# save the indexes in cs of the
# nodes chosen for the parent
_h[parents[1]] = [1, 2]
candidates.extend(g(_cs, _s, l+r, 1, _h))
_cs = deepcopy(cs)
_cs[0] = [1, r, 1]
_cs[1][1] = l
_cs[1][2] = 1
_s = s[:]
_s.extend([chosen[0][r], chosen[0][l]])
_h = deepcopy(h)
# save the indexes in cs of the
# nodes chosen for the parent
_h[parents[1]] = [1, 2]
candidates.extend(g(_cs, _s, r+l, 1, _h))
if log:
print "returning candidates: %s" % candidates
return candidates
# The right character is arbitrary but the
# character before it must match the previous one.
if i == len(parents)-1:
l = cs[len(cs)-2][1]
if log:
print "rightmost_char: %s" % l
if len(chosen[i]) < 2 or (not l in chosen[i]):
if log:
print "match not found: len(chosen[i]) < 2 or (not l in chosen[i])"
return []
else:
result = []
for r in [x for x in chosen[i].keys() if x != l]:
_cs = deepcopy(cs)
_cs[len(cs)-2][2] = _cs[len(cs)-2][2] + 1
_cs[len(cs)-1] = [1, r, 1]
_s = s[:] + [chosen[i][l], chosen[i][r]]
result.append((_cs, _s, seq + l + r))
return result
parent = parents[i]
if log:
print "get_candidates: g: parent, i: %s, %s" % (parent, i)
_h = deepcopy(h)
if not parent in _h:
prev = _h[parents[i-1]]
_h[parent] = [prev[0] + 1, prev[1] + 1]
# parent left and right children
pl, pr = _h[parent]
if log:
print "pl, pr: %s, %s" % (pl, pr)
l = cs[pl][1]
if log:
print "rightmost_char: %s" % l
if len(chosen[i]) < 2 or (not l in chosen[i]):
if log:
print "match not found: len(chosen[i]) < 2 or (not l in chosen[i])"
return []
else:
# "Base case," parent nodes have been filled
# so this is a duplicate character on the same
# row, which needs a new assignment
if cs[pl][0] == cs[pl][2] and cs[pr][0] == cs[pr][2]:
if log:
print "TODO"
return []
# Case 2, right child is not assigned
if not cs[pr][1]:
candidates = []
for r in [x for x in chosen[i].keys() if x != l]:
_cs = deepcopy(cs)
_cs[pl][2] += 1
_cs[pr][1] = r
_cs[pr][2] = 1
_s = s[:]
_s.extend([chosen[i][l], chosen[i][r]])
# save the indexes in cs of the
# nodes chosen for the parent
candidates.extend(g(_cs, _s, seq+l+r, i+1, _h))
return candidates
# Case 3, right child is already assigned
elif cs[pr][1]:
r = cs[pr][1]
if not r in chosen[i]:
if log:
print "match not found: r ('%s') not in chosen[i]" % r
return []
else:
_cs = deepcopy(cs)
_cs[pl][2] += 1
_cs[pr][2] += 1
_s = s[:]
_s.extend([chosen[i][l], chosen[i][r]])
# save the indexes in cs of the
# nodes chosen for the parent
return g(_cs, _s, seq+l+r, i+1, _h)
# Otherwise, fail
return []
return g(next_needed, [], "", 0, {})
def f(words, n):
global log
tree = {}
for w in words:
insert(w, 0, tree)
stack = []
root = tree[words[0][0]]
head = words[0][0]
for (l, r) in combinations(root.keys(), 2):
# (shared-chars-needed, chosen-nodes, board)
stack.append(([[1, None, 0],[1, None, 0]], [root[l], root[r]], [head, l + r], [head, l + r]))
while stack:
needed, chosen, seqs, board = stack.pop()
if log:
print "chosen: %s" % chosen
print "board: %s" % board
# Return early for demonstration
if len(board) == n:
# [y for x in chosen for y in x[1]]
return board
next_needed = get_next_needed(needed)
candidates = get_candidates(next_needed, chosen, seqs[-1])
for cs, s, seq in candidates:
if log:
print " cs: %s" % cs
print " s: %s" % s
print " seq: %s" % seq
_board = board[:]
_board.append("".join([x[1] for x in cs]))
_seqs = seqs[:]
_seqs.append(seq)
stack.append((cs, s, _seqs, _board))
"""
B
A O
I R N
T N E D
Z Y X W V
"""
words = [
"BONDV",
"BONDW",
"BONEW",
"BONEX",
"BOREW",
"BOREX",
"BAREW",
"BAREX",
"BORNX",
"BORNY",
"BARNX",
"BARNY",
"BAINX",
"BAINY",
"BAITY",
"BAITZ"]
N = 5
log = True
import time
start_time = time.time()
solution = f(list(words), N)
print ""
print ""
print("--- %s seconds ---" % (time.time() - start_time))
print "solution: %s" % solution
print ""
if solution:
for i, row in enumerate(solution):
print " " * (N - 1 - i) + " ".join(row)
print ""
print "words: %s" % words
Run Code Online (Sandbox Code Playgroud)
我发现这是一个非常有趣的问题。
第一次尝试是随机求解器;换句话说,它只是用字母填充三角形,然后计算存在多少“错误”(字典中没有的单词)。然后通过随机改变一个或多个字母来进行爬山,看看误差是否有所改善;如果误差保持不变,则仍然接受更改(因此在高原区域进行随机游走)。
令人惊讶的是,这可以在合理的时间内解决一些不明显的问题,例如以“b”开头的 5 个字母单词:
b
a u
l n r
l d g s
o y s a e
Run Code Online (Sandbox Code Playgroud)
然后,我尝试了一种完整搜索方法,以便能够回答“无解决方案”部分,其想法是编写递归搜索:
只需在左侧写下所有可接受的单词即可;例如
b
a ?
l ? ?
l ? ? ?
o ? ? ? ?
Run Code Online (Sandbox Code Playgroud)
并递归调用,直到找到可接受的解决方案或失败
如果第二个字母大于第一个单词的第二个字母,则在右侧写下所有可接受的单词,例如
b
a u
l ? r
l ? ? k
o ? ? ? e
Run Code Online (Sandbox Code Playgroud)
这样做是为了避免搜索对称解(对于任何给定的解,可以通过简单地在 X 轴上镜像来获得另一个解)
在一般情况下,如果对于使用所选问号的所有单词,第一个问号将替换为字母表中的所有字母
如果没有找到所选特定问号的解决方案,则继续搜索没有意义,因此False会返回。可能使用一些启发式方法来选择首先填充哪个问号会加快搜索速度,我没有调查这种可能性。
对于情况 2(搜索是否存在兼容的单词),我正在创建26*(N-1)在某个位置具有规定字符的单词集(不考虑位置 1),并且我在所有非问号字符上使用集合交集。
这种方法能够在大约 30 秒内(PyPy)判断出以 开头的 5 个字母单词没有解决方案w(字典中有 468 个以该开头字母的单词)。
该实现的代码可以参见
https://gist.github.com/6502/26552858e93ce4d4ec3a8ef46100df79
(程序需要一个名为words_alpha.txt包含所有有效单词的文件,然后必须调用指定首字母和大小;作为字典,我使用了https://github.com/dwyl/english-words中的文件)