J'abordé le problème par écrit ma propre mise en œuvre avant de regarder votre code.Ma première tentative était très semblable à ce que vous avez déjà eu :)
%# some parameters
NUM_ITER = 100000; %# number of simulations to run
DRAW_SZ = 12; %# number of cards we are dealing
SET_SZ = 3; %# number of cards in a set
FEAT_NUM = 4; %# number of features (symbol,color,number,shading)
FEAT_SZ = 3; %# number of values per feature (eg: red/purple/green, ...)
%# cards features
features = {
'oval' 'squiggle' 'diamond' ; %# symbol
'red' 'purple' 'green' ; %# color
'one' 'two' 'three' ; %# number
'solid' 'striped' 'open' %# shading
};
fIdx = arrayfun(@(k) grp2idx(features(k,:)), 1:FEAT_NUM, 'UniformOutput',0);
%# list of all cards. Each card: [symbol,color,number,shading]
[W X Y Z] = ndgrid(fIdx{:});
cards = [W(:) X(:) Y(:) Z(:)];
%# all possible sets: choose 3 from 12
setsInd = nchoosek(1:DRAW_SZ,SET_SZ);
%# count number of valid sets in random draws of 12 cards
counterValidSet = 0;
for i=1:NUM_ITER
%# pick 12 cards
ord = randperm(size(cards,1));
cardsDrawn = cards(ord(1:DRAW_SZ),:);
%# check for valid sets: features are all the same or all different
for s=1:size(setsInd,1)
%# set of 3 cards
set = cardsDrawn(setsInd(s,:),:);
%# check if set is valid
count = arrayfun(@(k) numel(unique(set(:,k))), 1:FEAT_NUM);
isValid = (count==1|count==3);
%# increment counter
if isValid
counterValidSet = counterValidSet + 1;
break %# break early if found valid set among candidates
end
end
end
%# ratio of found-to-notfound
fprintf('Size=%d, Set=%d, NoSet=%d, Set:NoSet=%g\n', ...
DRAW_SZ, counterValidSet, (NUM_ITER-counterValidSet), ...
counterValidSet/(NUM_ITER-counterValidSet))
Après avoir utilisé le profileur pour découvrir les points chauds, une certaine amélioration peut être principalement au début de-break'ing de boucles lorsque cela est possible. Le principal goulot d'étranglement est l'appel à la fonction UNIQUE. Ces deux lignes ci-dessus où nous vérifions des ensembles valides peuvent être réécrites:
%# check if set is valid
isValid = true;
for k=1:FEAT_NUM
count = numel(unique(set(:,k)));
if count~=1 && count~=3
isValid = false;
break %# break early if one of the features doesnt meet conditions
end
end
Malheureusement, la simulation est encore lent pour la simulation plus grande. Ainsi ma prochaine solution est une version vectorisée, où pour chaque itération, nous construisons une matrice unique de tous les ensembles possibles de 3 cartes de la main de 12 cartes tirées. Pour tous ces ensembles candidats, nous utilisons des vecteurs logiques pour indiquer quelle fonction est présente, évitant ainsi les appels à UNIQUE/NUMEL (nous voulons des caractéristiques toutes identiques ou toutes différentes sur chaque carte de l'ensemble). Je reconnais que le code est maintenant moins lisible et plus difficile à suivre (j'ai donc posté les deux versions pour comparaison). La raison en est que j'ai essayé d'optimiser le code autant que possible, de sorte que chaque boucle d'itération soit entièrement vectorisée. Voici le code final:
%# some parameters
NUM_ITER = 100000; %# number of simulations to run
DRAW_SZ = 12; %# number of cards we are dealing
SET_SZ = 3; %# number of cards in a set
FEAT_NUM = 4; %# number of features (symbol,color,number,shading)
FEAT_SZ = 3; %# number of values per feature (eg: red/purple/green, ...)
%# cards features
features = {
'oval' 'squiggle' 'diamond' ; %# symbol
'red' 'purple' 'green' ; %# color
'one' 'two' 'three' ; %# number
'solid' 'striped' 'open' %# shading
};
fIdx = arrayfun(@(k) grp2idx(features(k,:)), 1:FEAT_NUM, 'UniformOutput',0);
%# list of all cards. Each card: [symbol,color,number,shading]
[W X Y Z] = ndgrid(fIdx{:});
cards = [W(:) X(:) Y(:) Z(:)];
%# all possible sets: choose 3 from 12
setsInd = nchoosek(1:DRAW_SZ,SET_SZ);
%# optimizations: some calculations taken out of the loop
ss = setsInd(:);
set_sz2 = numel(ss)*FEAT_NUM/SET_SZ;
col = repmat(1:set_sz2,SET_SZ,1);
col = FEAT_SZ.*(col(:)-1);
M = false(FEAT_SZ,set_sz2);
%# progress indication
%#hWait = waitbar(0./NUM_ITER, 'Simulation...');
%# count number of valid sets in random draws of 12 cards
counterValidSet = 0;
for i=1:NUM_ITER
%# update progress
%#waitbar(i./NUM_ITER, hWait);
%# pick 12 cards
ord = randperm(size(cards,1));
cardsDrawn = cards(ord(1:DRAW_SZ),:);
%# put all possible sets of 3 cards next to each other
set = reshape(cardsDrawn(ss,:)',[],SET_SZ)';
set = set(:);
%# check for valid sets: features are all the same or all different
M(:) = false; %# if using PARFOR, it will complain about this
M(set+col) = true;
isValid = all(reshape(sum(M)~=2,FEAT_NUM,[]));
%# increment counter if there is at least one valid set in all candidates
if any(isValid)
counterValidSet = counterValidSet + 1;
end
end
%# ratio of found-to-notfound
fprintf('Size=%d, Set=%d, NoSet=%d, Set:NoSet=%g\n', ...
DRAW_SZ, counterValidSet, (NUM_ITER-counterValidSet), ...
counterValidSet/(NUM_ITER-counterValidSet))
%# close progress bar
%#close(hWait)
Si vous avez la boîte à outils de traitement parallèle, vous pouvez facilement remplacer la plaine boucle FOR avec un parfor parallèle (vous pouvez déplacer l'initialisation de la matrice M
dans la boucle à nouveau : remplacer M(:) = false;
avec M = false(FEAT_SZ,set_sz2);
)
Voici quelques exemples de sorties (simulations parfor 50000 utilisées avec un pool de 2 instances locales):
» tic, SET_game2, toc
Size=12, Set=48376, NoSet=1624, Set:NoSet=29.7882
Elapsed time is 5.653933 seconds.
» tic, SET_game2, toc
Size=15, Set=49981, NoSet=19, Set:NoSet=2630.58
Elapsed time is 9.414917 seconds.
Et avec un million d'itérations (parfor 12, no-parfor 15):
» tic, SET_game2, toc
Size=12, Set=967516, NoSet=32484, Set:NoSet=29.7844
Elapsed time is 110.719903 seconds.
» tic, SET_game2, toc
Size=15, Set=999630, NoSet=370, Set:NoSet=2701.7
Elapsed time is 372.110412 seconds.
Le odds ratio d'accord avec les résultats rapportés par Peter Norvig.
Je n'ai pas de calcul pour vous, et je ne parle pas matlab, mais je suis tombé sur votre question, qui m'a rappelé le jeu-set que j'avais programmé l'année dernière en scala, et que je voulais publier sur freshmeat - mais: pas le temps pour ça. Maintenant, j'ai trouvé le temps de traduire quelques vars allemands, des commentaires et des messages en anglais, et de le mettre sur un [site web à télécharger] (http://home.arcor.de/hirnstrom/minis/index.html#setgame); L'annonce de freshmeat prendra encore quelques heures. Je vais regarder, à quel point il est adapté pour calculer le nombre d'ensembles sur une page. –