Eu vim com uma solução que provavelmente não é a mais eficiente, mas funciona bem o suficiente. Basicamente:
- Classifique todas as palavras por comprimento, descendente.
- Pegue a primeira palavra e coloque-a no quadro.
- Pegue a próxima palavra.
- Pesquise todas as palavras que já estão no quadro e veja se existem interseções possíveis (letras comuns) com essa palavra.
- Se houver um local possível para essa palavra, percorra todas as palavras que estão no quadro e verifique se a nova palavra interfere.
- Se essa palavra não quebrar o quadro, coloque-o ali e vá para a etapa 3; caso contrário, continue procurando por uma área (etapa 4).
- Continue esse loop até que todas as palavras sejam colocadas ou impossibilitadas de serem colocadas.
Isso faz com que as palavras cruzadas funcionem, mas muitas vezes sejam muito pobres. Houve várias alterações que fiz na receita básica acima para obter um melhor resultado.
- Ao final da geração de palavras cruzadas, faça uma pontuação com base em quantas palavras foram colocadas (quanto mais melhor), qual o tamanho do quadro (quanto menor, melhor) e a proporção entre altura e largura (quanto mais próximo 1 para melhor). Gere várias palavras cruzadas, compare suas pontuações e escolha a melhor.
- Em vez de executar um número arbitrário de iterações, decidi criar o maior número possível de palavras cruzadas em um período arbitrário. Se você tiver apenas uma pequena lista de palavras, obterá dezenas de palavras cruzadas possíveis em 5 segundos. Palavras-chave maiores só podem ser escolhidas de 5 a 6 possibilidades.
- Ao colocar uma nova palavra, em vez de colocá-la imediatamente após encontrar um local aceitável, atribua uma pontuação a esse local com base em quanto ela aumenta o tamanho da grade e quantas interseções existem (o ideal é que você queira que cada palavra seja cruzado por 2-3 outras palavras). Acompanhe todas as posições e suas pontuações e escolha a melhor.
Eu escrevi recentemente recentemente em Python. Você pode encontrá-lo aqui: http://bryanhelmig.com/python-crossword-puzzle-generator/ . Não cria as palavras cruzadas densas no estilo NYT, mas o estilo das palavras cruzadas que você pode encontrar no livro de quebra-cabeças de uma criança.
Ao contrário de alguns algoritmos que descobri que implementavam um método aleatório de força bruta para colocar palavras como algumas sugeriram, tentei implementar uma abordagem de força bruta um pouco mais inteligente no posicionamento das palavras. Aqui está o meu processo:
No final, você tem um jogo decente de palavras cruzadas ou de busca de palavras, já que eles são iguais. Ele tende a funcionar bastante bem, mas deixe-me saber se você tem alguma sugestão de melhoria. Grades maiores são exponencialmente mais lentas; listas de palavras maiores linearmente. Listas de palavras maiores também têm uma chance muito maior de obter números melhores de colocação de palavras.
fonte
array.sort(key=f)
é estável, o que significa (por exemplo) que simplesmente classificar uma lista de palavras alfabéticas por comprimento manteria todas as palavras de 8 letras classificadas em ordem alfabética.Na verdade, escrevi um programa de geração de palavras cruzadas há cerca de dez anos (era enigmático, mas as mesmas regras se aplicavam a palavras cruzadas normais).
Ele tinha uma lista de palavras (e dicas associadas) armazenadas em um arquivo classificado por uso decrescente até a data (para que as palavras menos usadas estivessem na parte superior do arquivo). Um modelo, basicamente uma máscara de bits representando os quadrados pretos e livres, foi escolhido aleatoriamente em um pool que foi fornecido pelo cliente.
Então, para cada palavra não completa no quebra-cabeça (encontre o primeiro quadrado em branco e veja se o da direita (em toda a palavra) ou o abaixo (em baixo da palavra) também está em branco), foi feita uma pesquisa de o arquivo procurando a primeira palavra que couber, levando em conta as letras que já estão nessa palavra. Se não houvesse uma palavra adequada, basta marcar a palavra inteira como incompleta e seguir em frente.
No final, haveria algumas palavras incompletas que o compilador teria que preencher (e adicionar a palavra e uma pista ao arquivo, se desejado). Se eles não pudessem ter nenhuma idéia, eles poderiam editar as palavras cruzadas manualmente para alterar as restrições ou apenas solicitar uma re-geração total.
Depois que o arquivo de palavras / pistas chegava a um determinado tamanho (e adicionava de 50 a 100 pistas por dia para esse cliente), raramente havia um caso de mais de duas ou três correções manuais que precisavam ser feitas para cada palavra cruzada. .
fonte
Esse algoritmo cria 50 palavras cruzadas de seta 6x9 densas em 60 segundos. Ele usa um banco de dados de palavras (com palavras + dicas) e um banco de dados de placas (com placas pré-configuradas).
Um banco de dados de palavras maior diminui consideravelmente o tempo de geração e é mais difícil preencher algum tipo de placa! Placas maiores requerem mais tempo para serem preenchidas corretamente!
Exemplo:
Placa 6x9 pré-configurada:
(# significa uma dica em uma célula,% significa duas dicas em uma célula, setas não mostradas)
Placa 6x9 gerada:
Dicas [linha, coluna]:
fonte
Embora essa seja uma pergunta antiga, tentarei uma resposta com base em trabalho semelhante que fiz.
Existem muitas abordagens para resolver problemas de restrição (que geralmente estão na classe de complexidade do NPC).
Isso está relacionado à otimização combinatória e à programação de restrições. Nesse caso, as restrições são a geometria da grade e a exigência de que as palavras sejam únicas etc.
As abordagens de randomização / recozimento também podem funcionar (embora dentro da configuração adequada).
Simplicidade eficiente pode ser apenas a sabedoria suprema!
Os requisitos eram para um compilador de palavras cruzadas mais ou menos completo e um construtor (visual WYSIWYG).
Deixando de lado a parte do construtor WYSIWYG, o esboço do compilador era o seguinte:
Carregue as listas de palavras disponíveis (classificadas por tamanho da palavra, ou seja, 2,3, .., 20)
Encontre os lotes de palavras (ou seja, palavras de grade) na grade criada pelo usuário (por exemplo, palavra em x, y com comprimento L, horizontal ou vertical) (complexidade O (N))
Calcular os pontos de interseção das palavras da grade (que precisam ser preenchidas) (complexidade O (N ^ 2))
Calcule as interseções das palavras nas listas de palavras com as várias letras do alfabeto usadas (isso permite procurar palavras correspondentes usando um modelo, por exemplo, tese de Sik Cambon conforme usada pelo cwc ) (complexidade O (WL * AL))
As etapas .3 e .4 permitem executar esta tarefa:
uma. As interseções das palavras da grade consigo permitem criar um "modelo" para tentar encontrar correspondências na lista de palavras associada de palavras disponíveis para essa palavra da grade (usando as letras de outras palavras que se cruzam com essa palavra que já estão preenchidas em um determinado etapa do algoritmo)
b. As interseções das palavras em uma lista de palavras com o alfabeto permitem encontrar palavras (candidatas) correspondentes que correspondam a um determinado "modelo" (por exemplo, 'A' em 1º lugar e 'B' em 3º lugar etc.)
Portanto, com essas estruturas de dados implementadas, o algoritmo usado ficou assim:
NOTA: se a grade e o banco de dados de palavras forem constantes, as etapas anteriores podem ser feitas apenas uma vez.
A primeira etapa do algoritmo é selecionar um lote de palavras vazio (palavra da grade) aleatoriamente e preenchê-lo com uma palavra candidata da lista de palavras associada (a randomização permite produzir diferentes soluções em execuções consecutivas do algoritmo) (complexidade O (1) ou O ( N))
Para cada slot de palavras ainda vazio (que possui interseções com lotes de palavras já preenchidos), calcule uma taxa de restrição (isso pode variar, simples é o número de soluções disponíveis nessa etapa) e classifique os lotes de palavras vazios por essa taxa (complexidade O (NlogN ) ou O (N))
Percorra os lotes de palavras vazios calculados na etapa anterior e, para cada um, tente várias soluções canceladas (certificando-se de que "a consistência do arco seja mantida", ou seja, a grade tenha uma solução após esta etapa, se essa palavra for usada) e classifique-as de acordo com disponibilidade máxima para o próximo passo (ou seja, o próximo passo tem o máximo de soluções possíveis se essa palavra for usada naquele momento naquele local, etc.) (complexidade O (N * MaxCandidatesUsed))
Preencha essa palavra (marque-a como preenchida e vá para a etapa 2)
Se nenhuma palavra encontrada satisfizer os critérios da etapa .3, tente voltar para outra solução candidata da etapa anterior (os critérios podem variar aqui) (complexidade O (N))
Se for encontrado um retorno, use a alternativa e redefina opcionalmente as palavras já preenchidas que possam precisar ser redefinidas (marque-as como preenchidas novamente) (complexidade O (N))
Se nenhum retorno for encontrado, a solução no poderá ser encontrada (pelo menos com esta configuração, semente inicial etc.)
Caso contrário, quando todos os lotes de palavras forem preenchidos, você terá uma solução
Esse algoritmo faz uma caminhada aleatória consistente da árvore de soluções do problema. Se, em algum momento, houver um beco sem saída, ele retornará a um nó anterior e seguirá outra rota. Até que uma solução encontrada ou o número de candidatos para os vários nós esteja esgotado.
A parte de consistência garante que uma solução encontrada seja realmente uma solução e a parte aleatória permite produzir soluções diferentes em diferentes execuções e, em média, também ter melhor desempenho.
PS. tudo isso (e outros) foram implementados em JavaScript puro (com processamento paralelo e capacidade WYSIWYG)
PS2. O algoritmo pode ser facilmente paralelizado para produzir mais de uma solução (diferente) ao mesmo tempo
Espero que isto ajude
fonte
Por que não usar apenas uma abordagem probabilística aleatória para começar. Comece com uma palavra e, em seguida, escolha repetidamente uma palavra aleatória e tente encaixá-la no estado atual do quebra-cabeça sem quebrar as restrições de tamanho, etc. Se você falhar, comece tudo de novo.
Você ficará surpreso com a frequência com que uma abordagem de Monte Carlo como essa funciona.
fonte
Aqui está um código JavaScript baseado na resposta do nickf e no código Python do Bryan. Apenas publicá-lo no caso de alguém precisar dele em js.
fonte
Eu geraria dois números: comprimento e pontuação de Scrabble. Suponha que uma pontuação baixa no Scrabble signifique que é mais fácil participar (pontuações baixas = muitas letras comuns). Classifique a lista por comprimento descendente e pontuação Scrabble ascendente.
Em seguida, basta descer a lista. Se a palavra não cruzar com uma palavra existente (verifique cada palavra pelo tamanho e pontuação do Scrabble, respectivamente), coloque-a na fila e verifique a próxima palavra.
Enxágue e repita, e isso deve gerar palavras cruzadas.
Claro, tenho certeza de que esse é O (n!) E não é garantido que você complete as palavras cruzadas para você, mas talvez alguém possa melhorá-lo.
fonte
Eu estive pensando sobre este problema. Meu senso é que, para criar palavras cruzadas verdadeiramente densas, você não pode esperar que sua lista limitada de palavras seja suficiente. Portanto, convém pegar um dicionário e colocá-lo em uma estrutura de dados "trie". Isso permitirá que você encontre facilmente palavras que preenchem os espaços restantes à esquerda. Em um experimento, é bastante eficiente implementar uma travessia que, digamos, fornece todas as palavras da forma "c? T".
Portanto, meu pensamento geral é: crie algum tipo de abordagem de força relativamente bruta, conforme descrito aqui para criar uma cruz de baixa densidade e preencha os espaços em branco com palavras do dicionário.
Se alguém tiver adotado essa abordagem, entre em contato.
fonte
Eu estava brincando com o mecanismo gerador de palavras cruzadas e achei isso o mais importante:
0
!/usr/bin/python
uma.
allwords.sort(key=len, reverse=True)
b. faça algum item / objeto como o cursor, que percorrerá a matriz para facilitar a orientação, a menos que você queira iterar por escolha aleatória mais tarde.
o primeiro, pegue o primeiro par e coloque-os em frente e abaixo de 0,0; armazene o primeiro como nosso atual 'líder' de palavras cruzadas.
mova o cursor por ordem diagonal ou aleatória, com maior probabilidade diagonal para a próxima célula vazia
itere sobre as palavras like e use o espaço livre para definir o comprimento máximo da palavra:
temp=[] for w_size in range( len( w_space ), 2, -1 ) : # t for w in [ word for word in allwords if len(word) == w_size ] : # if w not in temp and putTheWord( w, w_space ) : # temp.append( w )
para comparar a palavra com o espaço livre, usei:
após cada palavra usada com sucesso, mude de direção. Faça um loop enquanto todas as células estiverem preenchidas OU você ficará sem palavras OU pelo limite de iterações:
# CHANGE ALL WORDS LIST inexOf1stWord = allwords.index( leading_w ) allwords = allwords[:inexOf1stWord+1][:] + allwords[inexOf1stWord+1:][:]
... e iterar novamente novas palavras cruzadas.
Faça o sistema de pontuação pela facilidade de preenchimento e alguns cálculos de estimativa. Dê pontuação para as palavras cruzadas atuais e restrinja a escolha posterior, acrescentando-a à lista de palavras cruzadas feitas, se a pontuação for atendida pelo seu sistema de pontuação.
Após a primeira sessão de iteração, itere novamente da lista de palavras cruzadas criadas para concluir o trabalho.
Ao usar mais parâmetros, a velocidade pode ser melhorada por um fator enorme.
fonte
Eu obteria um índice de cada letra usada por cada palavra para saber possíveis cruzamentos. Então eu escolheria a palavra maior e a usaria como base. Selecione o próximo grande e cruze-o. Enxague e repita. Provavelmente é um problema de NP.
Outra idéia é a criação de um algoritmo genético em que a métrica de força é quantas palavras você pode colocar na grade.
A parte difícil que acho é quando saber que uma determinada lista não pode ser atravessada.
fonte
Eu codifiquei uma solução JavaScript / jQuery para esse problema:
Exemplo de demonstração: http://www.earthfluent.com/crossword-puzzle-demo.html
Código fonte: https://github.com/HoldOffHunger/jquery-crossword-puzzle-generator
A intenção do algoritmo que usei:
Vou descrever o algoritmo que usei:
Agrupe as palavras de acordo com as que compartilham uma carta comum.
A partir desses grupos, crie conjuntos de uma nova estrutura de dados ("blocos de palavras"), que é uma palavra principal (que percorre todas as outras palavras) e, em seguida, as outras palavras (que percorrem a palavra principal).
Comece o jogo de palavras cruzadas com o primeiro desses blocos de palavras na posição superior esquerda do jogo de palavras cruzadas.
Para o restante dos blocos de palavras, iniciando na posição mais à direita da parte inferior das palavras cruzadas, mova-se para cima e para a esquerda, até que não haja mais espaços disponíveis para preencher. Se houver mais colunas vazias para cima do que para a esquerda, mova-se para cima e vice-versa.
fonte
var crosswords = generateCrosswordBlockSources(puzzlewords);
. Apenas o console registra esse valor. Não se esqueça, há um "modo de trapaça" no jogo, onde você pode simplesmente clicar em "Revelar resposta", para obter o valor imediatamente.Este aparece como um projeto no curso AI CS50 de Harvard. A idéia é formular o problema de geração de palavras cruzadas como um problema de satisfação de restrições e resolvê-lo com retorno com diferentes heurísticas para reduzir o espaço de pesquisa.
Para começar, precisamos de dois arquivos de entrada:
`
`
Um vocabulário de entrada (lista de palavras / dicionário) a partir do qual as palavras candidatas serão escolhidas (como a mostrada a seguir).
a abandon ability able abortion about above abroad absence absolute absolutely ...
Agora o CSP está definido e deve ser resolvido da seguinte maneira:
A seguir, é mostrada a saída obtida usando uma implementação do algoritmo de solução CSP:
`
A animação a seguir mostra as etapas de retrocesso:
Aqui está outro com uma lista de palavras em idioma bengali (bengali):
fonte