如何获得N个项目的所有可能组合,以M项目组合,每个项目在同一组中两个项目的出现值相同?

时间:2018-06-16 18:08:09

标签: algorithm math combinatorics

请参阅最后的修改

我正在努力解决这个问题,我想知道是否存在可以帮助我的东西:)。因此,让我们定义要解决的问题:

示例: 我们有12项称为1 2 3 4 5 6 7 8 9 A B C

结果可能是

1 - 1234 5678 9ABC

2 - 1259 346B 789A

以及其他人验证(X> 1,X等于):

" 1"与{2,3,4,5,6,7,8,9,A,B,C}发生X次相同的集合

" 2"与{1,3,4,5,6,7,8,9,A,B,C}在同一组中发生X(与以前相同的X)次

更新

这类似于社交高尔夫球手的问题。 不同之处在于,我并不要求每个玩家在每个玩家中再次玩相同的时间,但是他会玩同样数量的玩家。 我发现了一个基于"复杂"的解决方案。算法但也有更优雅的方法可以做到。

12的结果是

[1] (1 12 11 10)    (2 9 8 7)       (3 6 5 4)
[2] (1 9 6 5)       (3 12 8 7)      (2 11 4 10)
[3] (10 9 3 8)      (7 11 6 5)      (1 4 12 2)
[4] (8 11 6 5)      (7 10 4 1)      (12 9 3 2)
[5] (5 12 2 10      (1 8 4 9)       (11 3 7 6)
[6] (6 12 2 10)     (1 3 9 11)      (4 8 7 5)
每个玩家至少与其他玩家竞争一次

每位球员的对手名单是:

[1]  plays against 18 players  : [10, 10, 11, 11, 12, 12, 2, 3, 4, 4, 4, 5, 6, 7, 8, 9, 9, 9]
[2]  plays against 18 players : [1, 10, 10, 10, 11, 12, 12, 12, 12, 3, 4, 4, 5, 6, 7, 8, 9, 9]
[3]  plays against 18 players : [1, 10, 11, 11, 12, 12, 2, 4, 5, 6, 6, 7, 7, 8, 8, 9, 9, 9]
[4]  plays against 18 players : [1, 1, 1, 10, 10, 11, 12, 2, 2, 3, 5, 5, 6, 7, 7, 8, 8, 9]
[5]  plays against 18 players : [1, 10, 11, 11, 12, 2, 3, 4, 4, 6, 6, 6, 6, 7, 7, 8, 8, 9]
[6]  plays against 18 players : [1, 10, 11, 11, 11, 12, 2, 3, 3, 4, 5, 5, 5, 5, 7, 7, 8, 9]
[7]  plays against 18 players : [1, 10, 11, 11, 12, 2, 3, 3, 4, 4, 5, 5, 6, 6, 8, 8, 8, 9]
[8]  plays against 18 players : [1, 10, 11, 12, 2, 3, 3, 4, 4, 5, 5, 6, 7, 7, 7, 9, 9, 9]
[9]  plays against 18 players : [1, 1, 1, 10, 11, 12, 2, 2, 3, 3, 3, 4, 5, 6, 7, 8, 8, 8]
[10] plays against 18 players : [1, 1, 11, 11, 12, 12, 12, 2, 2, 2, 3, 4, 4, 5, 6, 7, 8, 9]
[11] plays against 18 players : [1, 1, 10, 10, 12, 2, 3, 3, 4, 5, 5, 6, 6, 6, 7, 7, 8, 9]
[12] plays against 18 players : [1, 1, 10, 10, 10, 11, 2, 2, 2, 2, 3, 3, 4, 5, 6, 7, 8, 9]

该算法在28名玩家之后有点长(以分钟计)...

1 个答案:

答案 0 :(得分:0)

以下是SWI Prolog中可能的答案,具体取决于我是否正确理解了问题:

  

给定一组N个元素 K ,找到一组 K 的子集(集合   "配对")使得每个子集的基数为    PairingSize K 的每个元素都出现在子集的 PairingDegree 中。

以下似乎可行。花了很长时间才想到一个很好的技巧来做到这一点。

:- use_module(library(tabling)).

% ===
% Settings
% ===

% We have 12 items, which we represent as a list of items.
% We can then "pull in" the items in a predicate by unifiying with L, then
% iterate/recurse over the length of L. If a constraint / checking predicate 
% fails at position "n", we can get out from the recursion elegantly without
% a cut, as there is no backtracking.
% The alternative is to declare the individual items as facts: item(i1),
% item(i2) etc. In that case, iterating/recursing over the items involves
% fighting Prolog's backtracking using cuts. Feels bad, man.

items([i1,i2,i3,i4,i5,i6,i7,i8]).

% Set the cardinality of the items here. It is a tabled predicate so that it
% is not computed again and again.

:- table items_card/1.

items_card(Cardinality) :- items(Items), length(Items,Cardinality).

% Set the "pairing size" here to avoid having to pass it around as a variable.

pairing_size(4).

% Set the "pairing degree" here to avoid having to pass it around as a variable

pairing_degree(4).

% ===
% Generate selections of "Len" elements selected from "ListIn", yielding "ListOut".
% select_sublist(+ListIn, +Len, -ListOut)
% ===
% Backtracking generates the selections. There is a canonical order to the generated
% alternatives.
%
% ?- select_sublist([a,b,c,d],2,X).
% X = [a, b] ;
% X = [a, c] ;
% X = [a, d] ;
% X = [b, c] ;
% X = [b, d] ;
% X = [c, d] ;
% false.

select_sublist([H|R], Len, [H|ListMid]) :-
   Len > 0,
   SubLen is Len-1,
   select_sublist(R, SubLen, ListMid).

select_sublist([_|R], Len, ListMid) :-
   Len > 0,
   select_sublist(R, Len, ListMid).

select_sublist(_, 0, []).

% ===
% Generate all possible unique pairings (structurally, a pairing is a list)
% ===
% This is done using bagof/3 fully generating all the solutions of select_sublist/3.
% Each pairing is ordered internally in a canonical way (i.e. [i1,i3,i5,i6] instead of
% [i5,i3,i1,i6]) and the list of pairings canonically ordered, too, as given by select_sublist/3.
% There will be O(length(items)^pairing_size) pairings.

build_all_pairings(Pairings) :-
   items(Items),
   pairing_size(Psize),
   bagof(Px, select_sublist(Items, Psize, Px), Pairings).

% ===
% Generate a mapping index -> Pairing 
% ===
% The complete list of unique pairings is the structure that we iterate over in several
% "contexts on the stack" (i.e. there will be several 'fingers' pointing into that list). We
% have to do that explicitly and cannot hand off the responsiblity of doing this to the
% Prolog stack and backtracking mechanism. We need to compute the "next pairing" 
% often. 
% In an pointer&struct-using language, one would have a linked list of "pairings",
% and a given "pointer to pairing" would thus immediately yield the "next pairing".
% Here we seem to need to explicitly search through the list (or maybe use the dynamic 
% database or tabling of the search predicate). 
% We do the following: a position in the list of unique pairings is given by an index
% 0..L-1, and we create a dict mapping indexes to pairings.

structify(Pairings, DictPs) :-
   x_structify_recur(Pairings, 0, _{}, DictPs).

x_structify_recur([P|Ps], Index, DictIn, DictOut) :-
   DictMid = DictIn.put(Index, P),
   IndexNext is Index+1,
   x_structify_recur(Ps, IndexNext, DictMid, DictOut).

x_structify_recur([], _, DictWind, DictWind).

% ===
% Pairing Evaluation
% ===
% Evaluate a list of pairings. The "Result" may become:
%
% "match"    - Every item appears exactly "pairing degree" times in the list of pairings; this means we have a solution!
% "over"     - At least one item appears more often than "pairing degree" times; this is not a solution!
% "overex"   - Over signalled via an exception (early return). In this case, the Dict is unset on return.
% "hopeless" - There are less than "pairing size" items which appear less than "pairing degree" times; 
%              adding another pairing will lead to "over" (this is a slight optimization, which could be optimized further)
% "under"    - Any other case. Pairing may be added to possibly reach "match".
%
% I am not completely sure how to handle "early return if 'over' case" without making the code messy,
% let's try with an Exception.
%
% Example with 12 items, pairing size 3, degree 3
%
% ?- appraise_pairings([[i1,i2,i3],[i2,i4,i5]],R,Pcd)
%   R = under,
%   Pcd = _2266{i1:1, i10:0, i11:0, i12:0, i2:2, i3:1, i4:1, i5:1, i6:0, i7:0, i8:0, i9:0}.
%
% ?- appraise_pairings([[i1,i2,i3],[i2,i4,i5],[i2,i6,i7],[i2,i8,i10]],R,Pcd).
%   R = overex.
%
% ?- appraise_pairings([[i1,i2,i3,i4,i5,i6,i7,i8,i9,i10,i11,i12],
%                       [i1,i2,i3,i4,i5,i6,i7,i8,i9,i10,i11,i12], 
%                       [i1,i2,i3,i4,i5,i6,i7,i8,i9,i10,i11,i12]],R,Pcd).
%   R = match,
%   Pcd = _294{i1:3, i10:3, i11:3, i12:3, i2:3, i3:3, i4:3, i5:3, i6:3, i7:3, i8:3, i9:3}.
%
% ?- appraise_pairings([[i1,i2,i3,i4,i5,i6,i7,i8,i9,i10,i11,i12],
%                       [i1,i2,i3,i4,i5,i6,i7,i8,i9,i10], 
%                       [i1,i2,i3,i4,i5,i6,i7,i8,i9,i10,i11,i12]],R,Pcd).
%
%   R = hopeless,
%   Pcd = _34{i1:3, i10:3, i11:2, i12:2, i2:3, i3:3, i4:3, i5:3, i6:3, i7:3, i8:3, i9:3}.

appraise_pairings(Pairings,Result,PcdOut) :- 
   catch(appraise_pairings_catchable(Pairings,Result,PcdOut),over,Result=overex).

appraise_pairings_catchable(Pairings,Result,PcdOut) :-
   initial_pairing_count_dict(PcdInit),
   % call this may throw to return early
   update_pcd_with_pairings_recur(Pairings,PcdInit,PcdOut), 
   % the above did not throw - appraise the full result
   items(Items),
   appraise_filled_pcd_recur(Items,PcdOut,0,0,Result).

% increase values kept in dict by recursing over pairings

update_pcd_with_pairings_recur([Pairing|Rest],PcdIn,PcdOut) :-
   update_pcd_with_items_recur(Pairing,PcdIn,PcdMid),
   update_pcd_with_pairings_recur(Rest,PcdMid,PcdOut).

update_pcd_with_pairings_recur([],PcdWind,PcdWind).

% increase values kept in dict by 1 by recursing over items

update_pcd_with_items_recur([Item|Rest],PcdIn,PcdOut) :-
   pairing_degree(Pdeg),
   U is PcdIn.Item + 1,
   % We can already "return early" here with "over" if U is above "pairing degree".
   ( U > Pdeg -> 
      throw(over) ; 
      ( PcdMid = PcdIn.put(Item,U), update_pcd_with_items_recur(Rest,PcdMid,PcdOut) ) ).

update_pcd_with_items_recur([],PcdWind,PcdWind).

% Appraise the "item->item occurence count" function in the "Pcd" dictionary,
% by recursing over all items. Initially, the "counts" are all 0 and the "Result" is unset.
% Once all the items have been checked, the Result is set based on the "counts".
% Note that we can break off early if we find an item that is "over".

appraise_filled_pcd_recur([Item|_Rest],Pcd,_MatchCount,_UnderCount,over) :-
   pairing_degree(Pdeg),
   Pcd.Item > Pdeg,!. % early return here with "over"

appraise_filled_pcd_recur([Item|Rest],Pcd,MatchCount,UnderCount,Result) :-
   pairing_degree(Pdeg),
   Pcd.Item == Pdeg,!,
   U is MatchCount + 1,
   appraise_filled_pcd_recur(Rest,Pcd,U,UnderCount,Result).

appraise_filled_pcd_recur([Item|Rest],Pcd,MatchCount,UnderCount,Result) :-
   pairing_degree(Pdeg),
   Pcd.Item < Pdeg,!,
   U is UnderCount + 1,
   appraise_filled_pcd_recur(Rest,Pcd,MatchCount,U,Result).

% .... Result is "match" if every item appears with the sought-for pairing degree:

appraise_filled_pcd_recur([],_,MatchCount,_UnderCount,match) :- items_card(C), MatchCount == C, !.

% .... Result is "hopeless" if there are less than pairing size items left with undercount:

appraise_filled_pcd_recur([],_,_MatchCount,UnderCount,hopeless) :- pairing_size(S), UnderCount < S, !.

% .... Result is "under" in any other case (for now):

appraise_filled_pcd_recur([],_,_,_,under).

% ===
% This creates a SWI Prolog dictionary to count number of occurrences of an item.
% The "initial dict" is "0 everywhere".
% ===
% ?- initial_pairing_count_dict(X).
% X = _3296{i1:0, i10:0, i11:0, i12:0, i2:0, i3:0, i4:0, i5:0, i6:0, i7:0, i8:0, i9:0}.

:- table initial_pairing_count_dict/1. 

initial_pairing_count_dict(Pcd) :- 
   items(Items), 
   x_putzero_recur(_{},Items,Pcd).

x_putzero_recur(PcdIn,[Item|Rest],PcdOut) :- 
   PcdMid = PcdIn.put(Item,0),
   x_putzero_recur(PcdMid,Rest,PcdOut).

x_putzero_recur(PcdWind,[],PcdWind).

% ===
% Here is where it's done!
% ===
% Choose a set of pairings (putting them into a canonical list of pairings, a 
% list of lists) such that the pairing degree of every item (i.e. the number of times
% an item appears in a pairing) is equal to the "pairing degree". 
% Several "Cursors" point into the canonically ordered list of possible pairings.
% The cursors form a stack. We manage the cursors ourselves and do not let Prolog do it via
% its execution stack (Could we do it? I didn't find a nice way to do it)

find_matches(MatchesOut) :- 
   build_all_pairings(Pairings),
   structify(Pairings, DictPs),
   CursorStack  = [0],
   PairingStack = [DictPs.0],
   length(Pairings, NumPs),
   % format('There are ~d pairings~n', [NumPs]),
   find_matches_recur(NumPs, DictPs, CursorStack, PairingStack, [], MatchesOut).

% find_matches_recur MUST be tailrecursive because this is an iteration over possible cursor/stack
% states (a large number) and we really don't want to make this fully recursive.
% To get rid of useless choicepoints, we can just add a "!" in the middle... this keeps us from
% the stack sky-high

find_matches_recur(NumPs, DictPs, CursorStackIn, PairingStackIn, MatchesIn, MatchesOut) :-
   log_stack(CursorStackIn),
   appraise_pairings(PairingStackIn, Result, _),
   format('Obtained ~w for ~w~n', [Result, PairingStackIn]),
   on_result(Result, NumPs, DictPs, CursorStackIn, CursorStackMid, PairingStackIn, PairingStackMid, MatchesIn, MatchesMid),
   % cut the stack off
   !,
   % if the cursor stack has reduced to the empty stack, we are done and this is the last instruction, otherwise tail-recurse
   ( CursorStackMid == [] 
     -> 
     MatchesOut = MatchesIn
     ; find_matches_recur(NumPs, DictPs, CursorStackMid, PairingStackMid, MatchesMid, MatchesOut) ).

% log

log_stack(CursorStack) :-
   reverse(CursorStack,P),
   format('Current stack ~w~n', [P]).

% ===
% Build the stack of pairings from the stack of cursors
% ===

%build_pairing_stack(DictPs, CursorStackIn, PairingStackIn, PairingStackOut) :-
%   [MyCursor|RestOfCursors] = CursorStackIn,
%   MyPairing                = DictPs.MyCursor,
%   build_pairing_stack(DictPs, RestOfCursors, [MyPairing|PairingStackIn], PairingStackOut).
%
%build_pairing_stack(_, [], PairingStackWind, PairingStackWind).

% ===
% What to do depending on intermediate Result
% ===

% over or hopeless

on_result(R, NumPs, DictPs, CursorStackIn, CursorStackOut, PairingsStackIn, PairingsStackOut, MatchesWind, MatchesWind) :- 
   ( R == over ; R == overex ; R == hopeless ), !, advance_cursor(NumPs, DictPs, CursorStackIn, CursorStackOut, PairingsStackIn, PairingsStackOut).

% under

on_result(under, NumPs, DictPs, CursorStackIn, CursorStackOut, PairingStackIn, PairingStackOut, MatchesWind, MatchesWind) :- 
   ( deepen_stack(NumPs, DictPs, CursorStackIn, CursorStackOut, PairingStackIn, PairingStackOut) 
     -> 
     true 
     ; advance_cursor(NumPs, DictPs, CursorStackIn, CursorStackOut, PairingStackIn, PairingStackOut) ).

% match

on_result(match, NumPs, DictPs, CursorStackIn, CursorStackOut, PairingStackIn, PairingStackOut, MatchesIn, MatchesOut) :-
   MatchesOut = [PairingStackIn|MatchesIn],
   log_match(MatchesOut),
   advance_cursor(NumPs, DictPs, CursorStackIn, CursorStackOut, PairingStackIn, PairingStackOut).

% log

log_match(Matches) :- 
   length(Matches,Solutions),
   format('*** ~d solutions so far ***~n', [Solutions]).

% ===
% Advance the lowermost cursor on the stack
% ===
% This may cause the stack to be "popped", possibly down to the empty stack if we are done

advance_cursor(NumPs, DictPs, CursorStackIn, CursorStackOut, PairingStackIn, PairingStackOut) :- 
   CursorStackIn  = [MyCursor|RestOfCursors],
   PairingStackIn = [_|RestOfPairings],
   NextMyCursor is MyCursor+1,
   ( NextMyCursor >= NumPs
     -> 
     advance_cursor(NumPs, DictPs, RestOfCursors, CursorStackOut, RestOfPairings, PairingStackOut) 
     ; 
     ( CursorStackOut = [NextMyCursor|RestOfCursors] , PairingStackOut = [DictPs.NextMyCursor|RestOfPairings] )).

advance_cursor(_, _, [], [], [], []).

% ===
% Deepen the stack by one
% ===
% This means creating a new "stack frame" that is one index further than the current "stack frame"
% This may fail if this results in a bad index

deepen_stack(NumPs, DictPs, CursorStackIn, CursorStackOut, PairingStackIn, PairingStackOut) :- 
   CursorStackIn = [MyCursor|_],
   NextMyCursor is MyCursor+1,
   ( NextMyCursor >= NumPs
     ->
     fail 
     ; ( CursorStackOut = [NextMyCursor|CursorStackIn], PairingStackOut = [DictPs.NextMyCursor|PairingStackIn] ) ). 

可悲的是,它并没有在在线Swish界面中运行:

  

沙盒限制!   无法导出可以调用哪个谓词   来自&#39; $ dicts&#39;:&#39;。&#39;(C,D,E)

然后它只是:

?- find_matches(MatchesOut).