Prolog 如何从带有条件的字母表中生成长度为N的所有单词

Prolog 如何从带有条件的字母表中生成长度为N的所有单词,prolog,Prolog,从字母表{a,b,c,d,e,f}生成长度为N的所有单词,其中4个字母出现两次,所有其他字母出现一次或根本不出现 我发现了这个从字母表生成单词的代码 letter(X) :- member(X, [a, b, c, d, e, f]). word(0, []). word(N, [C|W]) :- N > 0, N1 is N-1, letter(C), word(N1, W). words(N, L) :- findall(W, word(N,

从字母表{a,b,c,d,e,f}生成长度为N的所有单词,其中4个字母出现两次,所有其他字母出现一次或根本不出现

我发现了这个从字母表生成单词的代码

letter(X) :- member(X, [a, b, c, d, e, f]).

word(0, []).
word(N, [C|W]) :-
    N > 0,
    N1 is N-1,
    letter(C),
    word(N1, W).

words(N, L) :-
    findall(W, word(N, W), Ws),
    maplist(atomic_list_concat, Ws, L).
我决定把所有的信都扔了,然后挑选那些符合我条件的信,并计算字母的出现次数

count_occurrences(List, Occ):-
    findall([X,L], (bagof(true,member(X,List),Xs), length(Xs,L)), Occ).

但是它花费了太多的时间,还有什么其他的解决方案可以找到呢?

即使使用Prolog,当N>4时暴力生成也会花费很多时间。当然,序言推理比“C”中的暴力强制慢

解决方案是智能生成:

  • 仅生成所需的配置:
    • 从6个字符中选择4个字符,让它们出现2次
    • 在剩余的部分中,让它们出现0或1次
  • 约束:您将需要swi prolog中的CLP库
    • 可以限制引用的数量
  • 关于第一个解决方案,以下是一些代码:

    generate(N, O) :-
        getComponents(Xs),
        combineComponents(Xs, Ys),
        generateN(N, Ys, O).
        
    getComponents([twice(A, B, C, D), zeroOrOne(X, Y)]) :-
        Ls = [a, b, c, d, e, f],
        member2(A, Ls, Rs), 
        member2(B, Rs, Ts), 
        member2(C, Ts, Ss), 
        member2(D, Ss, Xs),
        Xs = [X, Y].
    
    member2(X, [X|Rs], Rs).
    member2(X, [F|Rs], [F|Ts]) :- member2(X, Rs, Ts).
    
    combineComponents([twice(A, B, C, D), zeroOrOne(X, Y)], Out6) :-
        twice(A, Out1, []),
        twice(B, Out2, Out1),
        twice(C, Out3, Out2),
        twice(D, Out4, Out3),
        zeroOrOne(X, Out5, Out4),
        zeroOrOne(Y, Out6, Out5).
    
    generateN(N, Ys, A) :-
        append(A, _, Ys),
        length(A, N).
    
    twice(A, [A, A|R], R).
    zeroOrOne(A, R, R).
    zeroOrOne(A, [A|R], R).
    
    有3个步骤:

  • getComponents/1将组件分为两部分返回,一部分是可以使用两次的字母,另一部分是零次或一次出现的字母。member2/3与传统的member/2类似,但返回参数2的未使用元素

  • combineComponents/2使用子谓词zeroOrOne/3和tweeps/3组合给定数字中的字母。这里,我使用差异列表方法来避免连接结果组件

  • generateN/3,给定N个元素及其多重性,生成一个包含N个成员的列表

    一些结果:N=8 [c,d,d,e,e,a,a,f] [b,d,d,e,e,a,a,f]

  • 你没有说要在结果中排列字母。此解决方案具有重复的解决方案,例如,两个顺序不同的解决方案,例如

    [a,a,c,c,b,b,f,f]
    [c,c,b,b,f,f,a,a]
    

    所以这对你来说是一种锻炼

    我试图使这个解决方案尽可能有效,至少对于一个晚上的小拼图来说是如此。欢迎提出改进建议

    从字母表{a,b,c,d,e,f}生成长度为N的所有单词,其中4个字母出现两次,所有其他字母出现一次或根本不出现

    我的理解是,这意味着任何解决方案的长度都是8到10:必须显示4*2个字符,并且最多还有两个其他可选字符。我的解决方案的结构是首先构造一个由这些可选字符组成的“基字”。然后,我们在这个基字中插入另外四个字符的两个副本

    所以,这个双重插入首先:

    insert2(Xs, Y) -->
        [Y],
        insert(Xs, Y).
    insert2([X | Xs], Y) -->
        [X],
        insert2(Xs, Y).
    
    insert(Xs, Y) -->
        [Y],
        list(Xs).
    insert([X | Xs], Y) -->
        [X],
        insert(Xs, Y).
    
    list([]) -->
        [].
    list([X | Xs]) -->
        [X],
        list(Xs).
    
    例如:

    ?- phrase(insert2([b], a), List).
    List = [a, a, b] ;
    List = [a, b, a] ;
    List = [b, a, a].
    
    ?- puzzle_(List).
    List = [_2278, _2278, _2230, _2230, _2194, _2194, _2170, _2170] ;
    List = [_2278, _2230, _2278, _2230, _2194, _2194, _2170, _2170] ;
    List = [_2278, _2230, _2230, _2278, _2194, _2194, _2170, _2170] ;
    List = [_2278, _2230, _2230, _2194, _2278, _2194, _2170, _2170] ;
    List = [_2278, _2230, _2230, _2194, _2194, _2278, _2170, _2170] ;
    List = [_2278, _2230, _2230, _2194, _2194, _2170, _2278, _2170] .
    
    ?- puzzle_(List), label(List).
    List = [a, a, b, b, c, c, d, d] ;
    List = [a, a, b, b, c, c, e, e] ;
    List = [a, a, b, b, c, c, f, f] ;
    List = [a, a, b, b, d, d, c, c] ;
    List = [a, a, b, b, d, d, e, e] ;
    List = [a, a, b, b, d, d, f, f] ;
    List = [a, a, b, b, e, e, c, c] ;
    List = [a, a, b, b, e, e, d, d] .
    
    请注意,这是为了避免在末尾留下选择点(在SWI Prolog中)而精心编写的。更重要的是,它的编写也是为了避免重复的解决方案;如果您实现双重插入,即“插入到列表中,然后插入到该结果中”,您将得到重复的结果,在指数算法中,这些结果将受到影响

    因此,基本词汇是:

    baseword([]).
    baseword([_X]).
    baseword([_X, _Y]).
    
    描述所有可能解的结构的谓词是:

    puzzle_(CharacterPlaceholders) :-
        baseword(BaseWord),
        phrase(insert2(BaseWord, _A), WordWith2A),
        phrase(insert2(WordWith2A, _B), WordWith2A2B),
        phrase(insert2(WordWith2A2B, _C), WordWith2A2B2C),
        phrase(insert2(WordWith2A2B2C, _D), WordWith2A2B2C2D),
        CharacterPlaceholders = WordWith2A2B2C2D.
    
    例如:

    ?- phrase(insert2([b], a), List).
    List = [a, a, b] ;
    List = [a, b, a] ;
    List = [b, a, a].
    
    ?- puzzle_(List).
    List = [_2278, _2278, _2230, _2230, _2194, _2194, _2170, _2170] ;
    List = [_2278, _2230, _2278, _2230, _2194, _2194, _2170, _2170] ;
    List = [_2278, _2230, _2230, _2278, _2194, _2194, _2170, _2170] ;
    List = [_2278, _2230, _2230, _2194, _2278, _2194, _2170, _2170] ;
    List = [_2278, _2230, _2230, _2194, _2194, _2278, _2170, _2170] ;
    List = [_2278, _2230, _2230, _2194, _2194, _2170, _2278, _2170] .
    
    ?- puzzle_(List), label(List).
    List = [a, a, b, b, c, c, d, d] ;
    List = [a, a, b, b, c, c, e, e] ;
    List = [a, a, b, b, c, c, f, f] ;
    List = [a, a, b, b, d, d, c, c] ;
    List = [a, a, b, b, d, d, e, e] ;
    List = [a, a, b, b, d, d, f, f] ;
    List = [a, a, b, b, e, e, c, c] ;
    List = [a, a, b, b, e, e, d, d] .
    
    枚举所有变体需要多长时间

    ?- time((puzzle_(CharacterPlaceholders), false)).
    % 845,559 inferences, 0.040 CPU in 0.040 seconds (100% CPU, 21324130 Lips)
    false.
    
    看起来很合理

    鉴于这些结构,仍然需要填充占位符。这包括将字母分配给列表中的变量(每个字母只分配一次,但如果变量在列表中出现两次,这将使字母也出现两次):

    例如:

    ?- phrase(insert2([b], a), List).
    List = [a, a, b] ;
    List = [a, b, a] ;
    List = [b, a, a].
    
    ?- puzzle_(List).
    List = [_2278, _2278, _2230, _2230, _2194, _2194, _2170, _2170] ;
    List = [_2278, _2230, _2278, _2230, _2194, _2194, _2170, _2170] ;
    List = [_2278, _2230, _2230, _2278, _2194, _2194, _2170, _2170] ;
    List = [_2278, _2230, _2230, _2194, _2278, _2194, _2170, _2170] ;
    List = [_2278, _2230, _2230, _2194, _2194, _2278, _2170, _2170] ;
    List = [_2278, _2230, _2230, _2194, _2194, _2170, _2278, _2170] .
    
    ?- puzzle_(List), label(List).
    List = [a, a, b, b, c, c, d, d] ;
    List = [a, a, b, b, c, c, e, e] ;
    List = [a, a, b, b, c, c, f, f] ;
    List = [a, a, b, b, d, d, c, c] ;
    List = [a, a, b, b, d, d, e, e] ;
    List = [a, a, b, b, d, d, f, f] ;
    List = [a, a, b, b, e, e, c, c] ;
    List = [a, a, b, b, e, e, d, d] .
    
    分别枚举长度为8、9和10的所有解的成本是多少

    ?- between(8, 10, Length), length(List, Length), time((puzzle_(List), label(List), false)).
    % 7,306,838 inferences, 0.334 CPU in 0.334 seconds (100% CPU, 21869592 Lips)
    % 128,616,758 inferences, 6.052 CPU in 6.052 seconds (100% CPU, 21252001 Lips)
    % 918,924,038 inferences, 46.998 CPU in 46.999 seconds (100% CPU, 19552521 Lips)
    false.
    

    对于这个问题,您希望使用Prolog“代码”来表示您的 不是序言中的“原子”这个词。 “代码”包含在双引号中,是语法上的糖类 要一份清单。 “代码”列表中的项目是与每个字符的unicode编号相对应的数字。 与“原子”相比,“代码”的巨大优势在于,您可以使用所有 可用于创建、检查、切片和切割列表的设施;其中最值得注意的是:DCG

    :- system:set_prolog_flag(double_quotes,codes) .
    
    :- [library(lists)] .
    
    main(N0)
    :-
    main(N0,WORDz) ,
    system:format('one possibility is "~s" .~n',[WORDz]) ,
    fail ;
    true
    .
    
    main(N0,WORDz)
    :-
    main("abcdef",N0,WORDz)
    .
    
    main(ALPHABETz0,N0,WORDz)
    :-
    constrain(N0,WORDz) ,
    generate(ALPHABETz0,WORDz)
    .
    
    constrain(N0,WORDz0)
    :-
    prolog:length(WORDz0,N0)
    .
    
    generate(ALPHABETz0,WORDz)
    :-
    prolog:phrase(generate(ALPHABETz0),WORDz)
    .
    
    generate(ALPHABETz0)
    -->
    {
        lists:select(A,ALPHABETz0,ALPHABETz1) ,
        lists:select(B,ALPHABETz1,ALPHABETz2) ,
        lists:select(C,ALPHABETz2,ALPHABETz3) ,
        lists:select(D,ALPHABETz3,ALPHABETz4) ,
        lists:select(E,ALPHABETz4,ALPHABETz5) ,
        lists:select(F,ALPHABETz5,_LPHABETz6)
    } ,
    double(A) ,
    double(B) ,
    double(C) ,
    double(D) ,
    optional(E) ,
    optional(F)
    .
    
    double(IT) --> [IT] , [IT] .
    
    optional(IT) --> [IT] .
    
    optional(_IT) --> [] .
    

    我同意使用列表,但是为什么使用
    code
    而不是
    chars