Ich löste das Problem, bevor meine eigene Implementierung schreiben in Ihrem Code suchen.Mein erster Versuch war sehr ähnlich zu dem, was Sie bereits :)
%# 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))
Nach dem Profiler Hot Spots zu entdecken, kann eine gewisse Verbesserung in erster Linie durch gemacht wird früh break'ing aus Schleifen, wenn möglich. Der wichtigste Engpass ist der Aufruf der UNIQUE-Funktion. Diese beiden Linien oben, wo wir für gültige Sätze überprüfen können neu geschrieben werden als:
%# 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
Leider ist die Simulation für größere Simulation noch langsam ist. Daher ist meine nächste Lösung eine vektorisierte Version, bei der wir für jede Iteration eine einzige Matrix aller möglichen Sätze von 3 Karten aus der Hand von 12 gezogenen Karten erstellen. Für all diese Kandidatensätze verwenden wir logische Vektoren, um anzuzeigen, welche Eigenschaft vorhanden ist, wodurch die Aufrufe von UNIQUE/NUMEL vermieden werden (wir wollen gleiche oder alle Merkmale auf jeder Karte der Menge).
Ich gebe zu, dass der Code jetzt weniger lesbar und schwerer zu folgen ist (also habe ich beide Versionen zum Vergleich gepostet). Der Grund dafür ist, dass ich versucht habe, den Code so weit wie möglich zu optimieren, so dass jede Iterationsschleife vollständig vektorisiert ist. Hier ist der endgültige Code:
%# 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)
Wenn Sie die Parallel Processing Toolbox haben, können Sie einfach auf die Ebene FOR-Schleife mit einem parallelen parfor ersetzen kann (man könnte die Initialisierung der Matrix M
innerhalb der Schleife verschieben wollen wieder :
» 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.
Und mit einer Million Iterationen (parfor:
Hier sind einige Beispiele für Ausgaben von 50000 Simulationen (parfor mit einem Pool von 2 lokalen Instanzen verwendet) M(:) = false;
mit M = false(FEAT_SZ,set_sz2);
) ersetzen no-parfor 15) für 12:
» 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.
Die Odds Ratio stimmen mit den von Peter Norvig berichteten Ergebnisse.
Ich habe keine Berechnung für dich und ich spreche Matlab nicht, aber ich bin über deine Frage gestolpert, die mich an das Set-Spiel erinnert, das ich letztes Jahr in Scala programmiert habe und auf dem ich veröffentlichen wollte Freshmeat - aber: keine Zeit dafür. Jetzt habe ich Zeit gefunden, einige deutsche Vars, Kommentare und Nachrichten ins Englische zu übersetzen und auf eine [Website zum Download] zu stellen (http://home.arcor.de/hirnstrom/minis/index.html#setgame); Die Ankündigung von Freshmeat wird noch einige Stunden dauern. Ich werde schauen, wie gut es für die Berechnung der Anzahl der Sätze auf einer Seite geeignet ist. –