Wymyśliłem rozwiązanie, które prawdopodobnie nie jest najbardziej wydajne, ale działa wystarczająco dobrze. Gruntownie:
- Posortuj wszystkie słowa malejąco według długości.
- Weź pierwsze słowo i umieść je na tablicy.
- Weź następne słowo.
- Przeszukaj wszystkie słowa, które są już na tablicy i zobacz, czy są jakieś możliwe przecięcia (wspólne litery) z tym słowem.
- Jeśli istnieje możliwe miejsce na to słowo, przejrzyj wszystkie słowa na tablicy i sprawdź, czy nowe słowo przeszkadza.
- Jeśli to słowo nie złamie planszy, umieść je tam i przejdź do kroku 3, w przeciwnym razie kontynuuj wyszukiwanie miejsca (krok 4).
- Kontynuuj tę pętlę, aż wszystkie słowa zostaną umieszczone lub nie można ich umieścić.
To działa, ale często dość kiepska krzyżówka. Wprowadziłem kilka zmian w podstawowej recepturze powyżej, aby uzyskać lepszy wynik.
- Na koniec generowania krzyżówki, nadaj jej punktację na podstawie tego, ile słów zostało umieszczonych (im więcej, tym lepiej), jak duża jest tablica (im mniejsza, tym lepiej) oraz stosunek wysokości do szerokości (im bliżej do 1, tym lepiej). Wygeneruj kilka krzyżówek, a następnie porównaj ich wyniki i wybierz najlepszą.
- Zamiast wykonywać dowolną liczbę iteracji, zdecydowałem się utworzyć jak najwięcej krzyżówek w dowolnym czasie. Jeśli masz tylko małą listę słów, w 5 sekund otrzymasz dziesiątki możliwych krzyżówek. Większą krzyżówkę można wybrać tylko z 5-6 możliwości.
- Umieszczając nowe słowo, zamiast umieszczać je natychmiast po znalezieniu akceptowalnej lokalizacji, nadaj temu miejscu punktację w oparciu o to, jak bardzo zwiększa rozmiar siatki i liczbę przecięć (najlepiej byłoby, gdyby każde słowo było przekreślone 2-3 innymi słowami). Śledź wszystkie pozycje i ich wyniki, a następnie wybierz najlepszą.
Niedawno napisałem własny w Pythonie. Możesz go znaleźć tutaj: http://bryanhelmig.com/python-crossword-puzzle-generator/ . Nie tworzy gęstych krzyżówek w stylu NYT, ale styl krzyżówek, które można znaleźć w książeczce z puzzlami dla dzieci.
W przeciwieństwie do kilku algorytmów, które tam odkryłem, które zaimplementowały losową metodę brutalnej siły do umieszczania słów, jak sugerowało kilka z nich, próbowałem zaimplementować nieco inteligentniejsze podejście brutalnej siły przy umieszczaniu słów. Oto mój proces:
Na koniec masz przyzwoitą krzyżówkę lub układankę wyszukiwania słów, ponieważ są mniej więcej takie same. Zwykle działa dość dobrze, ale daj mi znać, jeśli masz jakieś sugestie dotyczące ulepszeń. Większe siatki działają wykładniczo wolniej; większe listy słów są liniowe. Większe listy słów mają również znacznie większą szansę na lepsze numery umieszczania słów.
źródło
array.sort(key=f)
jest stabilny, co oznacza (na przykład), że zwykłe posortowanie alfabetycznej listy słów według długości spowoduje, że wszystkie 8-literowe słowa zostaną posortowane alfabetycznie.Właściwie napisałem program do generowania krzyżówek około 10 lat temu (był on tajemniczy, ale te same zasady miałyby zastosowanie do normalnych krzyżówek).
Miał listę słów (i powiązanych wskazówek) przechowywanych w pliku posortowanych według malejącej daty użycia (tak, że rzadziej używane słowa znajdowały się na górze pliku). Szablon, w zasadzie maska bitowa reprezentująca czarne i wolne kwadraty, został wybrany losowo z puli dostarczonej przez klienta.
Następnie dla każdego niekompletnego słowa w układance (w zasadzie znajdź pierwszy pusty kwadrat i zobacz, czy ten po prawej (w poprzek), czy ten poniżej (w dół) jest również pusty), przeprowadzono wyszukiwanie plik wyszukuje pierwsze pasujące słowo, biorąc pod uwagę litery znajdujące się już w tym słowie. Jeśli nie było słowa, które by pasowało, po prostu oznaczałeś całe słowo jako niekompletne i przechodziłeś dalej.
Na końcu byłoby kilka niedokończonych słów, które kompilator musiałby wypełnić (i dodać słowo i wskazówkę do pliku, jeśli trzeba). Jeśli nie mieli żadnych pomysłów, mogli ręcznie edytować krzyżówkę, aby zmienić ograniczenia lub po prostu poprosić o całkowitą zmianę generacji.
Gdy plik słów / wskazówek osiągnął określony rozmiar (i dodawał 50-100 wskazówek dziennie dla tego klienta), rzadko zdarzało się, że dla każdej krzyżówki trzeba było wykonać więcej niż dwie lub trzy ręczne poprawki .
źródło
Ten algorytm tworzy 50 gęstych krzyżówek ze strzałkami 6x9 w 60 sekund. Wykorzystuje bazę danych słów (ze słowami i wskazówkami) oraz bazę danych tablic (ze wstępnie skonfigurowanymi tablicami).
Większa baza danych słów znacznie skraca czas generowania, a niektóre tablice są trudniejsze do wypełnienia! Większe deski wymagają więcej czasu na prawidłowe wypełnienie!
Przykład:
Wstępnie skonfigurowana płyta 6x9:
(# oznacza jedną wskazówkę w jednej komórce,% oznacza dwie końcówki w jednej komórce, strzałki nie są pokazane)
Wygenerowana tablica 6x9:
Wskazówki [wiersz, kolumna]:
źródło
Chociaż jest to starsze pytanie, spróbuję odpowiedzieć na podstawie podobnej pracy, którą wykonałem.
Istnieje wiele podejść do rozwiązywania problemów z ograniczeniami (które ogólnie należą do klasy złożoności NPC).
Jest to związane z optymalizacją kombinatoryczną i programowaniem z ograniczeniami. W tym przypadku ograniczeniami są geometria siatki i wymóg unikalności słów itp.
Metody randomizacji / wyżarzania również mogą działać (chociaż w odpowiednich warunkach).
Skuteczna prostota może być po prostu ostateczną mądrością!
Wymagania dotyczyły mniej lub bardziej kompletnego kompilatora krzyżówek i konstruktora (wizualnego WYSIWYG).
Pomijając część kreatora WYSIWYG, zarys kompilatora wyglądał następująco:
Załaduj dostępne listy słów (posortowane według długości słowa, np. 2,3, ..., 20)
Znajdź pola słów (tj. Słowa siatki) na siatce utworzonej przez użytkownika (np. Słowo w x, y o długości L, w poziomie lub w pionie) (złożoność O (N))
Oblicz punkty przecinające się słów siatki (które należy wypełnić) (złożoność O (N ^ 2))
Obliczyć przecięcia słów w listach słów z różnymi literami używanego alfabetu (pozwala to na wyszukanie pasujących słów przy użyciu szablonu, np. Praca Sik Cambona używana przez cwc ) (złożoność O (WL * AL))
Kroki .3 i .4 pozwalają na wykonanie tego zadania:
za. Skrzyżowania ze sobą słów siatki pozwalają na stworzenie "szablonu" do wyszukiwania dopasowań w powiązanej liście słów dostępnych słów dla tego słowa z siatki (używając liter innych przecinających się słów z tym słowem, które są już wypełnione w określonym krok algorytmu)
b. Skrzyżowania słów na liście słów z alfabetem pozwalają znaleźć pasujące (kandydujące) słowa, które pasują do danego „szablonu” (np. „A” na 1. miejscu i „B” na 3. miejscu itp.)
Więc przy tych zaimplementowanych strukturach danych zastosowany algorytm wyglądał następująco:
UWAGA: jeśli siatka i baza słów są stałe, poprzednie kroki można wykonać tylko raz.
Pierwszym krokiem algorytmu jest losowe wybranie pustej szczeliny na słowa (słowo siatki) i wypełnienie jej słowem kandydującym z powiązanej listy słów (randomizacja umożliwia tworzenie różnych rozwiązań w kolejnych wykonaniach algorytmu) (złożoność O (1) lub O ( N))
Dla każdego wciąż pustego pola słów (które mają przecięcia z już wypełnionymi szczelinami), oblicz współczynnik ograniczenia (może się zmieniać, sth simple to liczba dostępnych rozwiązań w tym kroku) i posortuj puste pola słów według tego współczynnika (złożoność O (NlogN ) lub O (N))
Przejrzyj puste pola słów obliczone w poprzednim kroku i dla każdego z nich wypróbuj kilka rozwiązań anulowania (upewniając się, że "spójność łuku jest zachowana", tj. Siatka ma rozwiązanie po tym kroku, jeśli zostało użyte to słowo) i posortuj je według maksymalna dostępność do następnego kroku (tj. następny krok ma maksymalne możliwe rozwiązania, jeśli to słowo jest używane w tym czasie w tym miejscu itp.) (złożoność O (N * MaxCandidatesUsed))
Wypełnij to słowo (zaznacz je jako wypełnione i przejdź do kroku 2)
Jeśli nie znaleziono słowa spełniającego kryteria z kroku .3, spróbuj wrócić do innego kandydującego rozwiązania z jakiegoś poprzedniego kroku (kryteria mogą się tutaj różnić) (złożoność O (N))
Jeśli zostanie znaleziony powrót, użyj alternatywy i opcjonalnie zresetuj wszystkie już wypełnione słowa, które mogą wymagać zresetowania (oznacz je ponownie jako niewypełnione) (złożoność O (N))
Jeśli nie zostanie znaleziona ścieżka cofania, nie można znaleźć rozwiązania (przynajmniej w tej konfiguracji, początkowym seedu itp.)
W przeciwnym razie, gdy wszystkie pola są wypełnione, masz jedno rozwiązanie
Algorytm ten wykonuje losowy, spójny spacer po drzewie rozwiązań problemu. Jeśli w którymś momencie pojawia się ślepy zaułek, cofa się do poprzedniego węzła i podąża inną trasą. Dopóki nie zostanie znalezione rozwiązanie lub liczba kandydatów dla różnych węzłów zostanie wyczerpana.
Część spójności zapewnia, że znalezione rozwiązanie jest rzeczywiście rozwiązaniem, a część losowa pozwala na wytwarzanie różnych rozwiązań w różnych wykonaniach, a także średnio ma lepszą wydajność.
PS. wszystko to (i inne) zostały zaimplementowane w czystym JavaScript (z możliwością przetwarzania równoległego i WYSIWYG)
PS2. Algorytm może być łatwo zrównoleglony w celu uzyskania więcej niż jednego (innego) rozwiązania w tym samym czasie
Mam nadzieję że to pomoże
źródło
Dlaczego nie zastosować na początek losowego podejścia probabilistycznego. Zacznij od słowa, a następnie kilkakrotnie wybierz losowe słowo i spróbuj dopasować je do aktualnego stanu układanki bez łamania ograniczeń dotyczących rozmiaru itp. Jeśli ci się nie uda, po prostu zacznij wszystko od nowa.
Będziesz zaskoczony, jak często takie podejście Monte Carlo działa.
źródło
Oto kod JavaScript oparty na odpowiedzi nickfa i kodzie Bryana w Pythonie. Po prostu opublikuj to na wypadek, gdyby ktoś potrzebował go w js.
źródło
Wygenerowałbym dwie liczby: długość i wynik w scrabble. Załóżmy, że niski wynik w Scrabble oznacza, że łatwiej się przyłączyć (niskie wyniki = wiele popularnych liter). Sortuj listę według długości malejąco i rosnąco punktacji Scrabble.
Następnie po prostu zejdź na dół listy. Jeśli słowo nie krzyżuje się z istniejącym słowem (sprawdź z każdym słowem odpowiednio ich długość i wynik w Scrabble), a następnie umieść je w kolejce i sprawdź następne słowo.
Wypłucz i powtórz, a to powinno wygenerować krzyżówkę.
Oczywiście jestem prawie pewien, że to jest O (n!) I nie ma gwarancji, że ukończysz krzyżówkę za Ciebie, ale może ktoś może to poprawić.
źródło
Myślałem o tym problemie. Mam wrażenie, że aby stworzyć naprawdę gęstą krzyżówkę, nie możesz mieć nadziei, że Twoja ograniczona lista słów będzie wystarczająca. Dlatego możesz chcieć wziąć słownik i umieścić go w strukturze danych „trie”. Pozwoli ci to łatwo znaleźć słowa, które wypełniają pozostawione spacje. W przypadku próby dość wydajne jest zaimplementowanie przejścia, które, powiedzmy, daje wszystkie słowa w postaci „c? T”.
Tak więc moje ogólne myślenie jest następujące: stwórz coś w rodzaju stosunkowo brutalnej siły, jak niektórzy tutaj opisywali, aby stworzyć krzyż o niskiej gęstości i wypełnij puste miejsca słowami ze słownika.
Jeśli ktoś inny zastosował takie podejście, daj mi znać.
źródło
Bawiłem się silnikiem generatora krzyżówek i uznałem za najważniejsze:
0.
!/usr/bin/python
za.
allwords.sort(key=len, reverse=True)
b. stwórz jakiś przedmiot / obiekt jak kursor, który będzie poruszał się po macierzy dla łatwej orientacji, chyba że chcesz później iterować przez losowy wybór.
pierwszy, weź pierwszą parę i umieść je w poprzek i w dół od 0,0; zapisz pierwszą jako naszą obecną krzyżówkę „lider”.
przesuń kursor po przekątnej kolejności lub losowo z większym prawdopodobieństwem po przekątnej do następnej pustej komórki
iteruj po słowach takich jak i użyj wolnej przestrzeni, aby zdefiniować maksymalną długość słowa:
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 )
aby porównać słowo z wolną przestrzenią, której użyłem, tj .:
po każdym pomyślnie użytym słowie zmień kierunek. Zapętlaj, gdy wszystkie komórki są wypełnione LUB skończą ci się słowa LUB przez limit iteracji, a następnie:
# CHANGE ALL WORDS LIST inexOf1stWord = allwords.index( leading_w ) allwords = allwords[:inexOf1stWord+1][:] + allwords[inexOf1stWord+1:][:]
... i powtórz ponownie nową krzyżówkę.
Zrób system punktacji przez łatwość wypełniania i kilka kalkulacji szacunkowych. Podaj wynik dla bieżącej krzyżówki i zawęź późniejszy wybór, dołączając ją do listy wykonanych krzyżówek, jeśli wynik jest zadowalający przez Twój system punktacji.
Po pierwszej sesji iteracji ponownie wykonaj iterację z listy wykonanych krzyżówek, aby zakończyć zadanie.
Dzięki zastosowaniu większej liczby parametrów prędkość można znacznie poprawić.
źródło
Otrzymałbym indeks każdej litery używanej przez każde słowo, aby poznać możliwe krzyże. Wtedy wybrałbym największe słowo i użyłbym go jako podstawy. Wybierz następny duży i przejdź przez niego. Wypłukać i powtórzyć. To prawdopodobnie problem NP.
Innym pomysłem jest stworzenie algorytmu genetycznego, w którym miarą siły jest liczba słów, które można umieścić w siatce.
Najtrudniejsze jest dla mnie to, kiedy dowiedzieć się, jakiej listy nie da się przekroczyć.
źródło
Ten pojawia się jako projekt na kursie AI CS50 z Harvardu. Pomysł polega na sformułowaniu problemu generowania krzyżówek jako problemu spełniania ograniczeń i rozwiązaniu go za pomocą wycofywania się z różnymi heurystykami w celu zmniejszenia przestrzeni wyszukiwania.
Na początek potrzebujemy kilku plików wejściowych:
`
`
Słownictwo wejściowe (lista słów / słownik), z którego zostaną wybrane słowa kandydujące (jak poniżej).
a abandon ability able abortion about above abroad absence absolute absolutely ...
Teraz CSP jest zdefiniowany i należy go rozwiązać w następujący sposób:
Poniżej przedstawiono wyniki uzyskane za pomocą implementacji algorytmu rozwiązywania CSP:
`
Poniższa animacja przedstawia kroki cofania:
Oto kolejny z listą słów w języku Bangla (bengalskim):
źródło
Mam napisane rozwiązanie JavaScript / jQuery dla tego problemu:
Przykładowe demo: http://www.earthfluent.com/crossword-puzzle-demo.html
Kod źródłowy: https://github.com/HoldOffHunger/jquery-crossword-puzzle-generator
Cel zastosowanego przeze mnie algorytmu:
Opiszę algorytm, którego użyłem:
Pogrupuj słowa według tych, które mają wspólną literę.
Z tych grup stwórz zbiory nowej struktury danych („bloki słów”), które jest słowem podstawowym (które przechodzi przez wszystkie inne słowa), a następnie innymi słowami (które przechodzą przez słowo podstawowe).
Rozpocznij krzyżówkę, umieszczając pierwszy z tych bloków słów w lewym górnym rogu krzyżówki.
Dla pozostałych bloków słów, zaczynając od prawej dolnej pozycji krzyżówki, przesuwaj się w górę i w lewo, aż nie będzie już wolnych miejsc do wypełnienia. Jeśli jest więcej pustych kolumn w górę niż w lewo, przejdź do góry i odwrotnie.
źródło
var crosswords = generateCrosswordBlockSources(puzzlewords);
. Po prostu zarejestruj tę wartość w konsoli. Nie zapominaj, że w grze jest „tryb oszukiwania”, w którym wystarczy kliknąć „Pokaż odpowiedź”, aby natychmiast uzyskać wartość.