4

I'm facing this difficult exercise, I already saw other exercises about crosswords (i.e. the last exercise of "p-99: ninety-nine prolog programs", or the one reported in "Prolog programming for AI" by Bratko), but this is different and more difficult, because here we don't have any template to fill.

Text:

Write a Prolog program to generate a crossword. Suppose you have a dictionary of facts like this: w(Word,Lentgth). Write a predicate crossword(C,W,H) to instantiate C, to all possible list (crossword) of lists (crossword row) of character of the dictionary, where a black cell is represented by an asterisk.
Example:

w(online,6).
w(prolog,6).
w(perl,5).
w(emacs,5).
w(linux,5).
w(gnu,3).
w(mac,3).
w(nfs,3).
w(sql,3).
w(web,3).
w(xml,3).
w(a,1).
w(b,1).
w(e,1).
w(i,1).
w(l,1).
w(m,1).
w(n,1).
w(o,1).
w(q,1).
w(r,1).
w(s,1).
w(t,1).
w(u,1).
w(w,1).

?- crossword(C,9,6).
C=[[p,r,o,l,o,g,*,*,e],
   [e,*,n,*,*,n,*,*,m],
   [r,*,l,i,n,u,x,*,a],
   [l,*,i,*,f,*,m,a,c],
   [*,*,n,*,s,q,l,*,s],
   [*,w,e,b,*,*,*,*,*]]

Given Hints

1) You could add to the dictionary these "black" words:

black(*,1).
black(**,2).
black(***,3).
black(****,4).
black(*****,5).

2) Each row is sequence of "white" words (normal words) and "black" words interleaved, and this sequence can begin/finish with both.

3) I suggest you to manage the 2 possibility of the previous point with 2 different clause.

4) A crossword is a matrix of character, so its transpose is still a crossword.

5) The transpose crossword is already instantiated, so you only have to check its feasibility with the dictionary.

This is what came in my mind: create a list of H (heigh of crossword, row) lists (the rows of the crossword), for each list (row) I try to insert words from the dictionary followed by "black" words and then again a word, until the row is full, or the same thing starting from a "black". When the the crossword is full, I transpose it and check is the transpose is a valid crossword too: each word contained is a valid word from the dictionary. I know this is a very inefficient method, I tried with a little dictionary to generate crossword 2x2, it generates correctly the firsts two, but seem to take years for the third. I don't know if there is something wrong in my code, maybe it gets stuck somewhere. Do you know a more efficient way to solve this problem or you see something to be improved in my code?

crossword(C,W,H):- create(C,H),
                   insert_words(C,W,[],_),
                   clpfd:transpose(C,Traspose),
                   isok(Traspose).

insert_words([],_,U,U).
insert_words([H|T],W,Uacc,U):-
                             (add_white(H,[],C,[],Uacc,Udef);
                              add_black(H,[],C,[],Uacc,Udef)),
                              insert_words(T,C,Udef,U).

create(Puzzle,H) :- length(Puzzle,H).


add_white(P,P,0,_,U,U).
add_white(P,Row,R,Used,Uacc,Udef):- 
                                R\=0,
                                w(Word,L),
                                \+member(Word,Used),
                                \+member(Word,Uacc),
                                R2 is R-L,
                                R2 >= 0,
                                atom_chars(Word,Word2),
                                append(Row,Word2,Row2),
                                append(Used,[Word],Used2),
                                append(Uacc,[Word],Uacc2),
                                add_black(P,Row2,R2,Used2,Uacc2,Udef).

add_white(P,Row,R,Used,Uacc,Udef):-
                                   R\=0,
                                   w(Word,L),
                                   \+member(Word,Used),
                                   R2 is R-L,
                                   R2 < 0,
                                   append(Used,[Word],Used2),
                                   add_white(P,Row,R,Used2,Uacc,Udef).

add_white(P,Row,R,Used,Uacc,Udef):-
                                   R\=0,
                                   w(Word,_),
                                   member(Word,Used),
                                   add_white(P,Row,R,Used,Uacc,Udef).


add_black(P,P,0,_,U,U).
add_black(P,Row,R,Used,Uacc,Udef):-
                                   R\=0,
                                   black(Black,L),
                                   R2 is R-L,
                                   R2 >= 0,
                                   atom_chars(Black,Black2),
                                   append(Row,Black2,Row2),
                                   add_white(P,Row2,R2,Used,Uacc,Udef).

add_black(P,Row,R,Used,Uacc,Udef):-
                                   R\=0,
                                   black(_,L),
                                   R2 is R-L,
                                   R2 < 0,
                                   add_black(P,Row,R,Used,Uacc,Udef).

isok([]).
isok([H|T]):-split(H,*,R),
        check(R),
        isok(T).

split(L,C,R):-split2(L,C,X),trim(X,R).

split2(In, Sep, [Left|Rest]) :-
append(Left, [Sep|Right], In), !, split2(Right, Sep, Rest).
split2(In, _Sep, [In]).

trim([],[]).
trim([H|T],[H|T2]):-H\=[],trim(T,T2).
trim([H|T],Y):-H=[],trim(T,Y).

check([]).
check([H|T]):-
               atom_chars(X,H),
               w(X,_),
               check(T).

I'm using this little dictionary, instead of the original one, to test my program and generate simple 2x2 crosswords, but as I said before it seems to get stuck after correctly found 2 solutions.

w(ci,2).
w(ca,2).
w(i,1).
w(a,1).
black(*,1).
rok
  • 2,574
  • 3
  • 23
  • 44
  • all those tests like `\+member(Word,Used)` are really inefficient. A small gain would come from using memberchk/2 instead of member/2. Better gain would come from using sorted data structures, or hashtables – CapelliC Sep 20 '15 at 09:31
  • 1
    this bug could harm the entire solution: `w(perl,5).` should be `w(perl,4).`. BTW, why add to database such irrelevant information as the atom length ? You can get it with - duh - `?- atom_length(perl,L).` – CapelliC Sep 21 '15 at 08:42
  • This was the original text of the exercise, it was said the length added to simplify, but I agree to you that it's not necessary. BTW to test my program I'm not using the original dictionary, but a reduced one, to produce a 2x2 crossword fast, I'm going to edit my question to clarify it. What about what you said in the previous comment? I don't know at all hashtables, can you give some information or a link to start with? thank you – rok Sep 21 '15 at 08:51
  • 1
    Well, since you didn't stated the Prolog you're using, I avoided concrete references to libraries. Hashtables can be implemented in Prolog as any other languages, **if** it offers support for unlimited arity structures, and an hashing facility. But first, just try to replace member/2 with memberchk/2... – CapelliC Sep 21 '15 at 09:15
  • this call `insert_words(C,W,[],_),` is suspicious: why the output variable is unused ? maybe it's what should passed down... – CapelliC Sep 21 '15 at 09:20
  • I'm using swi-prolog. Tried with memberchk/2, without noticeable improvements. My output variable is `C`, the last parameter `_` represents the words used in the entire crossword (instead of variable `Used` of `add_white` and `add_black` that represent the words already used in the current row). I don't need it any more after having completed a crossword. – rok Sep 21 '15 at 10:35

0 Answers0