转载自:https://mp.weixin.qq.com/s/PbopBAcQo9q37_sUanh4zw
给定一个填字游戏和一组单词的空的(或几乎空的)框架。问题是将这些词放入框架中。
特定的填字游戏在文本文件中指定,该文件首先以任意顺序列出单词(每行一个单词)。然后,在空行之后,定义填字游戏框架。在此框架规范中,空字符位置由点(.)表示。为了简化解决方案,字符位置还可以包含预定义的字符值。然而谜题在文件p7_09a.dat中定义,其它谜题示例为p7_09b.dat和p7_09d.dat。还有一个没有解的谜题示例(p7_09c.dat)。
单词是至少两个字符的字符串(字符列表)。纵和横字谜框架中字符位置的水平或垂直顺序称为网格。我们的问题是找到一种在网格上放置相容单词的方法。
提示:
1)问题不容易。您将需要一些时间来彻底了解它。因此,不要太早放弃!请记住,目标是一个清晰的解,而不仅仅是快速而肮脏的骇客!
(2)读取数据文件是一个棘手的问题,文件p7_09-readfile.pl中提供了解决方法。使用谓词read_lines/2。
(3)出于效率的考虑,至少对于较大的谜题,以特定顺序对单词和位置进行排序非常重要。对于这一部分问题,“Prolog 列表练习 3” 的解决方案可能会非常有帮助。
程序文件是:p7_09.pl
:- ensure_loaded('p7_09-readfile.pl'). % 用于读取数据文件
crossword :-
write('usage: crossword(File)'), nl,
write('or crossword(File,Opt) with Opt one of 0,1, or 2'), nl,
write('or crossword(File,Opt,debug) for extra output'), nl.
:- crossword.
% crossword/1 无需优化即可运行(建议不要用于大文件)
crossword(FileName) :-
crossword(FileName,0).
% crossword/2 以给定的优化运行,没有调试输出
crossword(FileName,Opt) :-
crossword(FileName,Opt,nodebug).
% crossword/3 以给定的优化和给定的调试方式运行
crossword(FileName,Opt,Debug) :-
read_lines(FileName,Lines), % 从文件p99-readfile.pl的谓词 read_lines/2 返回一个字符列表
separate(Lines,Words,FrameLines),
length(Words,NWords),
construct_squares(FrameLines,Squares,MaxRow,MaxCol),
debug_write(Debug,Squares),
construct_sites(Squares,MaxRow,MaxCol,Sites),
length(Sites,NSites),
check_lengths(NWords,NSites),
solve(Words,Sites,Opt,Debug), % 做实际的工作
show_result(Squares,MaxRow,MaxCol).
debug_write(debug,X) :-
!,
write(X), nl, nl.
debug_write(_,_).
check_lengths(N,N) :- !.
check_lengths(NW,NS) :-
NW \= NS,
write('Number of words does not correspond to number of sites.'), nl,
fail.
% 输入准备
% 解析数据文件并将单词列表与框架描述分离
separate(Lines,Words,FrameLines) :-
trim_lines(Lines,LinesT),
parse_non_empty_lines(LinesT-L1,Words), % 差异列表!
parse_empty_lines(L1-L2),
parse_non_empty_lines(L2-L3,FrameLines),
parse_empty_lines(L3-[]).
% 删除行尾的空白
trim_lines([],[]).
trim_lines([L|Ls],[LT|LTs]) :-
trim_line(L,LT),
trim_lines(Ls,LTs).
trim_line(L,LT) :-
reverse(L,RL),
rm_white_space(RL,RLT),
reverse(RLT,LT).
rm_white_space([X|Xs],L) :-
char_type(X,white),
!,
rm_white_space(Xs,L).
rm_white_space(L,L).
% 将单词行与框架行分开
parse_non_empty_lines([L|L1]-L2,[L|Ls]) :-
L \= [],
!,
parse_non_empty_lines(L1-L2,Ls).
parse_non_empty_lines(L-L,[]).
parse_empty_lines([[]|L1]-L2) :-
!,
parse_empty_lines(L1-L2).
parse_empty_lines(L-L).
% 一个方框是单个字符的位置。作为Prolog项,方框的形式为sq(Row,Col,X),其中X表示字符,Row和Col定义拼图框内的位置。方框只是所有sq/3项的列表。
construct_squares(FrameLines,Squares,MaxRow,MaxCol) :- %(+,-,+,+)
construct_squares(FrameLines,SquaresList,1),
flatten(SquaresList,Squares),
maxima(Squares,0,0,MaxRow,MaxCol).
construct_squares([],[],_). %(+,-,+)
construct_squares([FL|FLs],[SL|SLs],Row) :-
construct_squares_row(FL,SL,Row,1),
Row1 is Row+1,
construct_squares(FLs,SLs,Row1).
construct_squares_row([],[],_,_). % (+,-,+,+)
construct_squares_row(['.'|Ps],[sq(Row,Col,_)|Sqs],Row,Col) :- !,
Col1 is Col+1,
construct_squares_row(Ps,Sqs,Row,Col1).
construct_squares_row([X|Ps],[sq(Row,Col,X)|Sqs],Row,Col) :-
char_type(X,alpha), !,
Col1 is Col+1,
construct_squares_row(Ps,Sqs,Row,Col1).
construct_squares_row([_|Ps],Sqs,Row,Col) :-
Col1 is Col+1,
construct_squares_row(Ps,Sqs,Row,Col1).
% maxima(Squares,0,0,MaxRow,MaxCol) :- 确定最大尺寸规格
maxima([],MaxRow,MaxCol,MaxRow,MaxCol).
maxima([sq(Row,Col,_)|Sqs],AccRow,AccCol,MaxRow,MaxCol) :-
AccRow1 is max(AccRow,Row),
AccCol1 is max(AccCol,Col),
maxima(Sqs,AccRow1,AccCol1,MaxRow,MaxCol).
% 位置确定 -----------------------------------------------
% construct_sites/4 遍历该框架两次,以收集列表中的所有位置
construct_sites(Squares,MaxRow,MaxCol,Sites) :- % (+,+,+,-)
construct_sites_h(Squares,MaxRow,MaxCol,1,SitesH,[]), %水平
construct_sites_v(Squares,MaxRow,MaxCol,1,Sites,SitesH). %垂直
% 水平位置
construct_sites_h(_,MaxRow,_,Row,Sites,Sites) :-
Row > MaxRow,
!.
construct_sites_h(Squares,MaxRow,MaxCol,Row,Sites,AccSites) :-
construct_sites_h(Squares,MaxRow,MaxCol,Row,1,AccSites1,AccSites),
Row1 is Row+1,
construct_sites_h(Squares,MaxRow,MaxCol,Row1,Sites,AccSites1).
construct_sites_h(_,_,MaxCol,_,Col,Sites,Sites) :-
Col > MaxCol,
!.
construct_sites_h(Squares,MaxRow,MaxCol,Row,Col,Sites,AccSites) :-
construct_site_h(Squares,Row,Col,Site,Incr), !,
Col1 is Col+Incr,
AccSites1 = [Site|AccSites],
construct_sites_h(Squares,MaxRow,MaxCol,Row,Col1,Sites,AccSites1).
construct_sites_h(Squares,MaxRow,MaxCol,Row,Col,Sites,AccSites) :-
Col1 is Col+1,
construct_sites_h(Squares,MaxRow,MaxCol,Row,Col1,Sites,AccSites).
construct_site_h(Squares,Row,Col,[X,Y|Cs],Incr) :-
memberchk(sq(Row,Col,X),Squares),
Col1 is Col+1,
memberchk(sq(Row,Col1,Y),Squares),
Col2 is Col1+1,
continue_site_h(Squares,Row,Col2,Cs,3,Incr).
continue_site_h(Squares,Row,Col,[X|Cs],Acc,Incr) :-
memberchk(sq(Row,Col,X),Squares), !,
Acc1 is Acc+1,
Col1 is Col+1,
continue_site_h(Squares,Row,Col1,Cs,Acc1,Incr).
continue_site_h(_,_,_,[],Incr,Incr).
% 垂直
construct_sites_v(_,_,MaxCol,Col,Sites,Sites) :-
Col > MaxCol,
!.
construct_sites_v(Squares,MaxRow,MaxCol,Col,Sites,AccSites) :-
construct_sites_v(Squares,MaxRow,MaxCol,1,Col,AccSites1,AccSites),
Col1 is Col+1,
construct_sites_v(Squares,MaxRow,MaxCol,Col1,Sites,AccSites1).
construct_sites_v(_,MaxRow,_,Row,_,Sites,Sites) :-
Row > MaxRow,
!.
construct_sites_v(Squares,MaxRow,MaxCol,Row,Col,Sites,AccSites) :-
construct_site_v(Squares,Row,Col,Site,Incr),
!,
Row1 is Row+Incr,
AccSites1 = [Site|AccSites],
construct_sites_v(Squares,MaxRow,MaxCol,Row1,Col,Sites,AccSites1).
construct_sites_v(Squares,MaxRow,MaxCol,Row,Col,Sites,AccSites) :-
Row1 is Row+1,
construct_sites_v(Squares,MaxRow,MaxCol,Row1,Col,Sites,AccSites).
construct_site_v(Squares,Row,Col,[X,Y|Cs],Incr) :-
memberchk(sq(Row,Col,X),Squares),
Row1 is Row+1,
memberchk(sq(Row1,Col,Y),Squares),
Row2 is Row1+1,
continue_site_v(Squares,Row2,Col,Cs,3,Incr).
continue_site_v(Squares,Row,Col,[X|Cs],Acc,Incr) :-
memberchk(sq(Row,Col,X),Squares), !,
Acc1 is Acc+1,
Row1 is Row+1,
continue_site_v(Squares,Row1,Col,Cs,Acc1,Incr).
continue_site_v(_,_,_,[],Incr,Incr).
% ----------------------------------------------
:- ensure_loaded('p1_28.pl'). %lsort and lfsort参考Prolog列表练习 3
% solve/4 对单词和位置列表进行优化
solve(Words,Sites,0,Debug) :- !, % 未排序
solve(Words,Sites,Debug).
solve(Words,Sites,1,Debug) :- !, % 按长度排序
lsort(Words,Words1,desc),
lsort(Sites,Sites1,desc),
solve(Words1,Sites1,Debug).
solve(Words,Sites,2,Debug) :- % 按长度频率排序
lfsort(Words,Words1),
lfsort(Sites,Sites1),
solve(Words1,Sites1,Debug).
% solve/3 对准备好的单词和位置进行debug_write,然后调用solve/2进行实际工作
solve(Words,Sites,Debug) :-
debug_write(Debug,Words),
debug_write(Debug,Sites),
solve(Words,Sites).
% solve/2 做实际的工作:为每个单词找到合适的位置
solve([],[]).
solve([W|Words],Sites) :-
select(W,Sites,SitesR),
solve(Words,SitesR).
% --------------------------------------------------------------------------
show_result(Squares,MaxRow,MaxCol) :-
show_result(Squares,MaxRow,MaxCol,1), nl.
show_result(_,MaxRow,_,Row) :-
Row > MaxRow,
!.
show_result(Squares,MaxRow,MaxCol,Row) :-
show_result(Squares,MaxRow,MaxCol,Row,1),
nl,
Row1 is Row+1,
show_result(Squares,MaxRow,MaxCol,Row1).
show_result(_,_,MaxCol,_,Col) :-
Col > MaxCol,
!.
show_result(Squares,MaxRow,MaxCol,Row,Col) :-
(
memberchk(sq(Row,Col,X),Squares),
!,
write(X);
write(' ')
),
Col1 is Col+1,
show_result(Squares,MaxRow,MaxCol,Row,Col1).
程序文件:p7_09-readfile.pl
% 用于读取文本文件并将文本拆分为行的辅助谓词。应对不同的行尾惯例。适用于UNIX,DOS/Windows和Mac文件系统。
% read_lines(File,Lines) :- 读取文本文件File并将文本分成几行。行是字符列表的列表,每个字符列表都是一个文本行。
% (+,-) (atom, list-of-charlists)
read_lines(File,Lines) :-
seeing(Old), see(File),
get_char(X), read_file(X,CharList), % 将整个文件读入字符列表
parse_charlist(CharList-[],Lines), % 使用差异列表解析行
see(Old).
read_file(end_of_file,[]) :- !.
read_file(X,[X|Xs]) :-
get_char(Y),
read_file(Y,Xs).
parse_charlist(T-T,[]) :- !.
parse_charlist(X1-X4,[L|Ls]) :-
parse_line(X1-X2,L),
parse_eol(X2-X3), !,
parse_charlist(X3-X4,Ls).
parse_eol([]-[]) :- !. % 文件末尾不是行尾
parse_eol(['\r','\n'|R]-R) :- !. % DOS/Windows
parse_eol(['\n','\r'|R]-R) :- !. % Mac (?)
parse_eol(['\r'|R]-R) :- !. % Mac (?)
parse_eol(['\n'|R]-R). % UNIX
parse_line([]-[],[]) :- !. % 文件末尾不是行尾
parse_line([X|X1]-[X|X1],[]) :-
eol_char(X), !.
parse_line([X|X1]-X2,[X|Xs]) :-
\+ eol_char(X),
parse_line(X1-X2,Xs).
eol_char('\r').
eol_char('\n').
测试:
?- consult('p7_09.pl').
usage: crossword(File)
or crossword(File,Opt) with Opt one of 0,1, or 2
or crossword(File,Opt,debug) for extra output
true.
?- time(crossword('p7_09b.dat',0)).
P TUEBINGEN TRAUBENZUCKER
R A E A R I
O TEMPERAMENT FORTUNA V
T T F E N I
EGERIA ZEUS T SAMPAN K E
K R E T U E R
T S WALZER LIANE MADONNA
O A A A TAL N U K
RELIGION R N R TIARA
A L G K U I S I S
T O E STOIKER L S
GRANAT F L E OSTEN
E S F L I C
G TURKMENEN VENDETTA H
I B N R L T T R
ISEL T U H STETTIN T O
S T A E P I T DER N
E E S R E BRIEFTAUBE A O
KARRE T L T A I K G
U A E AAL M T T R
N ALLENSTEIN N I A I A
D L T K S L O P
EOSIN USAMBARA SERBIEN H
E H R R E I
HANNIBAL MELASSE NONNE
% 439,739,214 inferences, 207.470 CPU in 208.519 seconds (99% CPU, 2119533 Lips)
true .
?- time(crossword('p7_09b.dat',1)).
P TUEBINGEN TRAUBENZUCKER
R A E A R I
O TEMPERAMENT FORTUNA V
T T F E N I
EGERIA ZEUS T SAMPAN K E
K R E T U E R
T S WALZER LIANE MADONNA
O A A A TAL N U K
RELIGION R N R TIARA
A L G K U I S I S
T O E STOIKER L S
GRANAT F L E OSTEN
E S F L I C
G TURKMENEN VENDETTA H
I B N R L T T R
ISEL T U H STETTIN T O
S T A E P I T DER N
E E S R E BRIEFTAUBE A O
KARRE T L T A I K G
U A E AAL M T T R
N ALLENSTEIN N I A I A
D L T K S L O P
EOSIN USAMBARA SERBIEN H
E H R R E I
HANNIBAL MELASSE NONNE
% 19,635,723 inferences, 8.369 CPU in 8.419 seconds (99% CPU, 2346202 Lips)
true .
time(crossword('p7_09b.dat',2)).
P TUEBINGEN TRAUBENZUCKER
R A E A R I
O TEMPERAMENT FORTUNA V
T T F E N I
EGERIA ZEUS T SAMPAN K E
K R E T U E R
T S WALZER LIANE MADONNA
O A A A TAL N U K
RELIGION R N R TIARA
A L G K U I S I S
T O E STOIKER L S
GRANAT F L E OSTEN
E S F L I C
G TURKMENEN VENDETTA H
I B N R L T T R
ISEL T U H STETTIN T O
S T A E P I T DER N
E E S R E BRIEFTAUBE A O
KARRE T L T A I K G
U A E AAL M T T R
N ALLENSTEIN N I A I A
D L T K S L O P
EOSIN USAMBARA SERBIEN H
E H R R E I
HANNIBAL MELASSE NONNE
% 143,513 inferences, 0.125 CPU in 0.126 seconds (100% CPU, 1144743 Lips)
true .
?-
数据文件:p7_09a.dat
LINUX
PROLOG
PERL
ONLINE
GNU
XML
NFS
SQL
EMACS
WEB
MAC
...... .
. . . .
. ..... .
. . . ...
. ... .
...
数据文件:p7_09b.dat
AAL
DER
TAL
TAT
ISEL
TELL
ZANK
ZEUS
ALSEN
BLASE
EOSIN
ETTAL
KARRE
LIANE
NEEFS
NONNE
OSTEN
STUHL
TIARA
ANKARA
EGERIA
GRANAT
HIRTEN
MISERE
SAMPAN
TILSIT
WAGGON
FORTUNA
ITALIEN
MADONNA
MELASSE
REAUMUR
RIVIERA
SEKUNDE
SERBIEN
SKELETT
SKRUPEL
STETTIN
STOIKER
HANNIBAL
REGISTER
RELIGION
STANNIOL
TRUEFFEL
UNTERTAN
USAMBARA
VENDETTA
TUEBINGEN
TURKMENEN
ALLENSTEIN
ATTRAKTION
BRIEFTAUBE
TATTERSALL
PROTEKTORAT
TEMPERAMENT
KRANKENKASSE
CHRONOGRAPHIE
TRAUBENZUCKER
WALZER
. ......... .............
. . . . . .
. ........... ....... .
. . . . . .
...... .... . ...... . .
. . . . . . .
. . ...... ..... .......
. . . . ... . . .
........ . . . .....
. . . . . . . . .
. . . ....... . .
...... . . . .....
. . . . . .
. ......... ........ .
. . . . . . . .
.... . . . ....... . .
. . . . . . . ... .
. . . . . .......... . .
..... . . . . . . .
. . . ... . . . .
. .......... . . . . .
. . . . . . . .
..... ........ ....... .
. . . . . .
........ ....... .....
数据文件:p7_09c.dat
AAL
DER
TAL
TAT
ISEL
TELL
ZANK
ZEUS
ALSEN
BLASE
EOSIN
ETTAL
KARREN
LIANE
NEEFS
NONNE
OSTEN
STUHL
TIARA
ANKARA
EGERIA
GRANAT
HIRTEN
MISERE
SAMPAN
TILSIT
WAGGON
FORTUNA
ITALIEN
MADONNA
MELASSE
REAUMUR
RIVIERA
SEKUNDE
SERBIEN
SKELETT
SKRUPEL
STETTIN
STOIKER
HANNIBAL
REGISTER
RELIGION
STANNIOL
TRUEFFEL
UNTERTAN
USAMBARA
VENDETTA
TUEBINGEN
TURKMENEN
ALLENSTEIN
ATTRAKTION
BRIEFTAUBE
TATTERSALL
PROTEKTORAT
TEMPERAMENT
KRANKENKASSE
CHRONOGRAPHIE
TRAUBENZUCKER
WALZER
. ......... .............
. . . . . .
. ........... ....... .
. . . . . .
...... .... . ...... . .
. . . . . . .
. . ...... ..... .......
. . . . ... . . .
........ . . . .....
. . . . . . . . .
. . . ....... . .
...... . . . .....
. . . . . .
. ......... ........ .
. . . . . . . .
.... . . . ....... . .
. . . . . . . ... .
. . . . . .......... . .
..... . . . . . . .
. . . ... . . . .
. .......... . . . . .
. . . . . . . .
..... ........ ....... .
. . . . . .
........ ....... .....
数据文件:p7_09d.dat
BANI
HAUS
NETZ
LENA
ANKER
ARIEL
GASSE
INNEN
ORADE
SESAM
SIGEL
ANGOLA
AZETAT
EKARTE
NATTER
NENNER
NESSEL
RITTER
SOMMER
TAUNUS
TRANIG
AGENTUR
ERRATEN
ERREGER
GELEISE
HAENDEL
KAROSSE
MANAGER
OSTEREI
SIDERIT
TERRIER
ANATOMIE
ANPASSEN
BARKASSE
BEDANKEN
DEKADENT
EINLADEN
ERLASSEN
FRAGMENT
GARANTIE
KRAWATTE
MEISTERN
REAKTION
TENTAKEL
TRIANGEL
UEBERALL
VERGEBEN
AFRIKANER
BESTELLEN
BULLAUGEN
SANTANDER
VERBERGEN
ALLENSTEIN
AUSTRALIEN
BETEILIGEN
NATALITAET
OBERHAUSEN
UNTERSTAND
LEUMUND
........ ........ .......
. . . . . . .
. . . .......... . . .
....... . . . ........
. . . . . . . . . . . .
. . . . ...... . . . .
. . . . . ........ .
. . ...... . . . . . . .
. . . . . . . . .
...... ...... . . ......
. . . . . . . . .
....... . . . ....... .
. . . . . .
. . ....... ........ .
. . . . . .
...... . ....... ........
. . . . . . .
. . ......... . . .
. . . . . . . .....
. . ....... . . .
.......... . . . .
. . . . ......... .
. ......... . . . .
. . . . . . .
........ ......... .....