问题
I'm building a program that fills a hollow cube with many small cubes. Then, makes a connected path through random cubes. The path is found step by step by looking at the direct neighbors of each cube and select anyone of them as the next step. To illustrate, The following picture shows the path that consists of cubes (red cubes),
To build the path I started form some cube, colored it with red, found its neighbor cubes (6 neighbors because we have 6 faces), selected any of them randomly, colored the selected one with red, then find its neighbors by repeating the same process. This method is good, but if a neighbor cube is already red (already belongs to the path) it won't recognize that and it can select it again. To solve this I did the following,
1- I made an array called visited, and I stored the index of any visited cube in it.
2- I stored the neighbors of the current cube in another array called a and compared it with the visited one, and I deleted any common elements, then took a random number from the elements remaining in a.
3- Sometimes all of the neighbors can be visited, so a will become empty. In this case, I looked for any of the current cube's, selected any of them randomly, checked for its neighbors, stored them in the neighbors array (a) and compared that with the visited cubes array. This will keep repeating (while loop) if the neighbors array is not empty. But in my case, the program gets stuck in the while loop. Also, sometime it doesn't get stuck, but the number of cubes in the path becomes less than the specified one.
I have obtained the code that builds the main cube, fills it with small cubes, and gets the neighbors by Hoki in a previous question (Filing the entire volume of a cube with small cubes in MATLAB) I just added the mechanism that builds the path using the neighbors. Here is the code,
%%
clf; figure(1); format compact
h(1) = axes('Position',[0.2 0.2 0.6 0.6]);
%These are the different 8 vertices of the cube, each is defined by its 3 x y z coordinates:
vert = [ 1 1 -1; -1 1 -1; -1 1 1; 1 1 1; -1 -1 1; 1 -1 1; 1 -1 -1; -1 -1 -1];
%These are the 6 faces of the cube, each is defined by connecting 4 of the available vertices:
fac = [1 2 3 4; 4 3 5 6; 6 7 8 5; 1 2 8 7; 6 7 1 4; 2 3 5 8];
%// How many small cube do we want
MainCubeSide = 2 ; %// dimension of the side of the main cube
nCubeOnSide = 5 ; %// number of small cube in one "row/column" of the main cube
nCubesTotal = nCubeOnSide^3 ; %// total number of small cube
% define the Main container cube
MainCube.Vertices = vert *(2/MainCubeSide) ; %// because the cube as defined above has already a side=2
disp (2/ MainCubeSide);
MainCube.Faces = fac ;
MainCube.FaceColor = 'w' ;
hMainCube = patch(MainCube); %// patch function for the first big cube. MainCube can be seen as an object that contains all the infor that the patch function needs.
axis([-1, 1, -1, 1, -1, 1]);
axis equal;
hold on; %wait we didn't finish yet.
material metal;
alpha('color');
alphamap('rampdown');
view(138,24)
%view(3);
%% // generate all the coordinates of each cube first
dstep = MainCubeSide / nCubeOnSide ; %// step size for small cube vertices, this will determine the edge length for the small cube that best fits the required small cubes per edge.
vElem = bsxfun(@plus, vert / nCubeOnSide , -( MainCubeSide/2 - dstep/2)*[1 1 1] ) ; %// elementary cube vertices. Because the small cube was defined in the center of the big cube, to push it to one side we only need to move it by half the size of the main cube (minus half the size of a small cube)
%%
hold on;
coords = zeros( size(vElem,1),size(vElem,2), nCubesTotal ) ; %// To store the coordinates (8 rows * 3 columns) in every of the nCubesTotal slices.
colors = zeros( nCubesTotal , 3 ) ; %// To store the RBG colours for every small cube.
hcube = zeros( nCubesTotal , 1 ) ; %// To store the handles of the patch objects
iNeighbour = zeros( nCubesTotal , 6 ) ; %// To save the index of the neighbours
idc = permute( reshape(1:nCubesTotal,nCubeOnSide,nCubeOnSide,nCubeOnSide) , [3 2 1] ) ;
%// For each cube ...
iCube = 0 ;
for iline=1:nCubeOnSide %// Lines
for icol=1:nCubeOnSide %// Columns
for ih=1:nCubeOnSide %// Slice (height)* all for loops have the smame length, because the total number of cubes = mCubeOnSide^3
iCube = iCube + 1 ;
%// Take the base corner coordinates and add an offset to each coordinate
coords(:,:,iCube) = bsxfun(@plus, vElem , dstep*[(iline-1) (icol-1) (ih-1)]); %the fist one will not have offset, iline-1=0.
%// Save the colour
colors(iCube,:) = rand(1,3) ;
%// Draw the cube and store its info in the cube handler hcube.
hcube(iCube) = patch('Faces', fac, 'Vertices', coords(:,:,iCube), 'FaceColor', colors(iCube,:) ) ; %Remember each slice of coords contains the vertices of one of the small cubes, that's why it corresponds to vertices here.
drawnow %// just for intermediate display, you can comment these 2 lines
pause(0.05) %// just for intermediate display, you can comment these 2 lines
%// save adjacent cubes indices
ixAdj = [iline-1 iline+1 icol-1 icol+1 ih-1 ih+1] ; %// indices of adjacent cubes
idxFalse = (ixAdj<1) | (ixAdj>nCubeOnSide) ; %// detect cube which would be "out" of the main cube
ixAdj(idxFalse) = 1 ; %// just to not get an "indexing" error at this stage
iNeighbour(iCube,:) = [idc(ixAdj(1),icol,ih) idc(ixAdj(2),icol,ih) ...
idc(iline,ixAdj(3),ih) idc(iline,ixAdj(4),ih) ...
idc(iline,icol,ixAdj(5)) idc(iline,icol,ixAdj(6)) ] ;
iNeighbour(iCube,idxFalse) = NaN ;
end
end
end
getNeighbourIndex = @(idx) iNeighbour(idx,~isnan(iNeighbour(idx,:))) ;
set(hcube,'Visible','off') %// turn off all small cubes
cubeOfInterest = 32 ; %// select one cube
%// display the main cube of interest, and it's neighbours in transparency
set(hcube(cubeOfInterest),'Visible','on','FaceColor','r','FaceAlpha',1)
%set(hcube(getNeighbourIndex(CubeOfInterest)),'Visible','on','FaceColor','g','FaceAlpha',.05)
visited= []; %to hold the indices of the visited cubes.
for i=1:124
visited (i) = cubeOfInterest; %the first visited cube is the cube of interest.
a= (getNeighbourIndex(cubeOfInterest)); %get all the neighbors\ indices and store them in an array so we can select randomly from them.
disp (a);
looping=true;
while looping==true %To avoid visiting any previously visited cube.
disp ('program is looping') %just to know if the program is stuck in an infinite loop.
inds = find(ismember(a, visited)); %store the indices of the common elements if found.
a(inds)= []; %delete the indices of the common elements.
if (isempty (a)==1)
temp = randsample((getNeighbourIndex(cubeOfInterest)), 1);
a= getNeighbourIndex(temp);
disp (a)
else
looping=false ;
end
end
x = randsample(a, 1);
set(hcube(x),'Visible','on','FaceColor','r','FaceAlpha',1)
cubeOfInterest= x;
end
Building the path in the code above starts from the line in which I initialize the visited cubes array (visited = []).
Can anyone please tell me why the program gets stuck in the while loop?
EDIT: I've added some code to Hoki's code to make the path continue even if the neighbors are visited. It will select any not visited cube and move to it if it will not make the path disconnected. The code is now working vey well as required. Here is the modified part,
%% // Random path
rng(1) %// set that if you want reproducible results, otherwise comment it
set(hcube,'Visible','off')
startCubeIndex = randi([1 numel(hcube)],1) ; %// random starting cube
%// startCubeIndex = 1 ; %// or fixed one
maxPathLength = 125 ; %// maximum length of path
maxPathLength= maxPathLength+1;
path_terminated = false ; %// condition to get out of loop
path_visited = [] ; %// store the generated path (visited cubes)this to be checked in case 2 as well.
ptIndex = 1 ;
path_visited(1) = startCubeIndex ;
set(hcube( path_visited(ptIndex) ) ,'Visible','on','FaceColor','r','FaceAlpha',1)
availableAll =[1:125];
while ~path_terminated
available_next = getNeighbourIndex( path_visited(ptIndex) ) ; %// get all the neighbours
[~,~,ib] = intersect(path_visited,available_next) ; %// check if we already went through some
if ~isempty(ib)
available_next(ib) = [] ; %// remove visited cube from "available" list
end
nAvail = numel(available_next) ; %// number of actually available neighbour
if nAvail == 0 %// Exit loop if no neighbour available
msgTerm = 'Path blocked. No other neighbour available.' ; %// Reason for terminating path
%//path_terminated = true ;
%//here I want to make the modification.
looping=true; %//To keep looping until we find the correct next move.
counter=0; %//to iterate through the cubes which are not visisted.
while looping==true
counter= counter+1;
jump= availableAll (counter); %//select the first available cube and check if it is suitable or not
if (~isempty (intersect(getNeighbourIndex(jump), path_visited))) %// if the selcted cube has a visited neighbor, it means it is suitable. The path will stay connected.
%good we found it.
ptIndex = ptIndex+1 ;
path_visited(ptIndex) = jump ; %//add the selected cube to the path.
availableAll (availableAll==jump)= []; %//remove the selected cube from the list of available cubes.
looping= false; %//stop looping.
end
%continue
end
else
ptIndex = ptIndex+1 ;
path_visited(ptIndex) = available_next( randi([1 nAvail],1) ) ;
availableAll (availableAll==path_visited(ptIndex))= []; %//remove the selected cube from the list of available cubes.
end
if ptIndex >= maxPathLength %// exit loop if we reached the max number of elements
msgTerm = 'Path terminated. Reached max number of elements.' ; %// Reason for terminating path
path_terminated = true ;
continue
end
%// choose one neighbour randomly among the available ones
set(hcube( path_visited(ptIndex) ) ,'Visible','on','FaceColor','r','FaceAlpha',1) %// highlight new cube
set(hcube( path_visited(1:ptIndex-1) ) ,'Visible','on','FaceColor','g','FaceAlpha',.2) %// shade old cubes
pause(0.05) %// just for intermediate display, you can comment these 2 lines
drawnow %// just for intermediate display, you can comment these 2 lines
end
disp(msgTerm)
The code above is working well. Hoki provided a great help, thanks a lot Hoki.
Thank You.
回答1:
Here's a way to implement the random path. I've explicited the exit conditions. You can add more exit conditions if you want or regroup them, but the mechanism is roughly the same. An extract of the important steps:
while ~path_terminated
available_next = getNeighbourIndex( path_visited(ptIndex) ) ; %// get all the neighbours
[~,~,ib] = intersect(path_visited,available_next) ; %// check if we already went through some
if ~isempty(ib)
available_next(ib) = [] ; %// remove visited cube from "available" list
end
ptIndex = ptIndex+1 ;
path_visited(ptIndex) = available_next( randi([1 nAvail],1) ) ;
Then you add the checking for exit conditions and the coloring of the cubes the way you want.
The basic logic to advance the path:
- Get the list of neighbours.
available_next = getNeighbourIndex( path_visited(ptIndex) ) ;
- check if some of them were already visited.
[~,~,ib]=intersect(path_visited,available_next);
- remove already visited from the "Available" list.
available_next(ib) = [] ;
- choose (randomly) one of the remaining available.
path_visited(ptIndex) = available_next( randi([1 nAvail],1) ) ;
The logic for exit conditions:
- Max number of path element has been reached (if you decide to set a maximum length of path).
if ptIndex >= maxPathLength
- No more "Available" neighbour (i.e. all available neighbour have already been visited).
if numel(available_next) == 0
Full code (after all the cube generation):
%% // Random path
rng(1) %// set that if you want reproducible results, otherwise comment it
set(hcube,'Visible','off')
startCubeIndex = randi([1 numel(hcube)],1) ; %// random starting cube
%// startCubeIndex = 1 ; %// or fixed one
maxPathLength = 100 ; %// maximum length of path
path_terminated = false ; %// condition to get out of loop
path_visited = [] ; %// store the generated path (visited cubes)
ptIndex = 1 ;
path_visited(1) = startCubeIndex ;
set(hcube( path_visited(ptIndex) ) ,'Visible','on','FaceColor','r','FaceAlpha',1)
while ~path_terminated
available_next = getNeighbourIndex( path_visited(ptIndex) ) ; %// get all the neighbours
[~,~,ib] = intersect(path_visited,available_next) ; %// check if we already went through some
if ~isempty(ib)
available_next(ib) = [] ; %// remove visited cube from "available" list
end
nAvail = numel(available_next) ; %// number of actually available neighbour
if nAvail == 0 %// Exit loop if no neighbour available
msgTerm = 'Path blocked. No other neighbour available.' ; %// Reason for terminating path
path_terminated = true ;
continue
end
if ptIndex >= maxPathLength %// exit loop if we reached the max number of elements
msgTerm = 'Path terminated. Reached max number of elements.' ; %// Reason for terminating path
path_terminated = true ;
continue
end
%// choose one neighbour randomly among the available ones
ptIndex = ptIndex+1 ;
path_visited(ptIndex) = available_next( randi([1 nAvail],1) ) ;
set(hcube( path_visited(ptIndex) ) ,'Visible','on','FaceColor','r','FaceAlpha',1) %// highlight new cube
set(hcube( path_visited(1:ptIndex-1) ) ,'Visible','on','FaceColor','g','FaceAlpha',.2) %// shade old cubes
pause(0.05) %// just for intermediate display, you can comment these 2 lines
drawnow %// just for intermediate display, you can comment these 2 lines
end
disp(msgTerm)
On a cube with 8
elements on the side (so 512 cubes total), if you set maxPathLength
to 100, the path visits 100 elements without stopping:
If you do not set the max length (or just set it to the max number of cube or even more), then the path generation goes on until the cube get "stuck" (e.g. until it arrives in a place where all the neighbours have already been visited):
EDIT:
A variation of the logic which allow you to choose the path generation mode between 3 options:
open
: No constraint, a cube can be revisited anytime (path terminates only when max number of element is reached).stuck
: A cube may only be revisited if no other free path can be foundnever
: A cube cannot be revisited (the path terminates if stuck).
The code is not that different from the initial one but instead of just modifying I prefer to give the full modified code below, so you can see how the different functionalities are implemented.
%% // Set options
cube_revisit_options = {'open','stuck','never'} ;
%// "open" : No constraint, a cube can be revisited anytime (path
%// terminates only when max number of element is reached).
%// "stuck" : A cube may only be revisited if no other free path can be found
%// "never" : A cube cannot be revisited (the path terminates if stuck).
cube_revisit_mode = 'stuck' ;
cube_path_history = true ; %// set to false to display ALL path history, otherwise only "nHist" points displayed
nHist = 30 ; %// number of cubes in the history "trail"
alphatrail = linspace(0.1,0.9,nHist) ; %// decreasing transparency values for the trail
cmaptrail = cool(nHist) ; %// colormap for the trail
% cmaptrail = winter(nHist) ; %// other nice colormaps you can try
% cmaptrail = flipud(hot(nHist)) ;
%% // go for it
set(hcube,'Visible','off')
rng(2) %// set that if you want reproducible results, otherwise comment it
startCubeIndex = randi([1 numel(hcube)],1) ; %// random starting cube
%// startCubeIndex = 1 ; %// or fixed one
maxPathLength = 1000 ; %// maximum length of path
path_terminated = false ; %// condition to get out of loop
path_visited = [] ; %// store the generated path (visited cubes)
ptIndex = 1 ;
path_visited(1) = startCubeIndex ;
set(hcube( path_visited(ptIndex) ) ,'Visible','on','FaceColor','r','FaceAlpha',1)
while ~path_terminated
%// exit loop if we reached the max number of elements
if ptIndex >= maxPathLength
msgTerm = 'Path terminated. Reached max number of elements.' ; %// Reason for terminating path
path_terminated = true ;
continue
end
all_neighbours = getNeighbourIndex( path_visited(ptIndex) ) ; %// get all the neighbours
available_next = setdiff(all_neighbours,path_visited,'stable') ; %// find only "unvisited" cubes
nAvail = numel(available_next) ; %// number of actually available neighbour
switch cube_revisit_mode
case 'open'
%// any neighbour can be selected
available_next = all_neighbours ;
case 'stuck'
%// visited neighbour can only be selected if no other choice
if nAvail == 0
fprintf(2,'Got stuck cube %d at iteration %d. Escaping ...\n',path_visited(ptIndex),ptIndex);
available_next = all_neighbours ;
end
case 'never'
%// visited neighbour CANNOT be selected - Exit loop if no neighbour available
if nAvail == 0
msgTerm = 'Path blocked. No other neighbour available.' ; %// Reason for terminating path
path_terminated = true ;
continue
end
end
nAvail = numel(available_next) ; %// recalculate in case we just changed it above
%// choose one neighbour randomly among the available ones
ptIndex = ptIndex+1 ;
path_visited(ptIndex) = available_next( randi([1 nAvail],1) ) ;
%// recolor
if cube_path_history
%// draw only "N" history cube, in a different color and with decreasing transparency
idxTrace = max(1,size(path_visited,2)-nHist):size(path_visited,2)-1 ;
set(hcube( path_visited(1:max(idxTrace(1)-1,1) ) ) ,'Visible','off') %// disable very old cubes
for ic=1:length(idxTrace)
set(hcube( path_visited(idxTrace(ic)) ) ,'Visible','on','FaceColor',cmaptrail(ic,:),'FaceAlpha',alphatrail(ic)) %// shade old cubes
end
else
%// draw ALL history cube, same color and transparency
set(hcube( path_visited(1:ptIndex-1) ) ,'Visible','on','FaceColor','g','FaceAlpha',.1) %// shade old cubes uniformly
end
set(hcube( path_visited(ptIndex) ) ,'Visible','on','FaceColor','r','FaceAlpha',1) %// highlight new cube
drawnow %// just for intermediate display, you can comment these 2 lines
pause(0.010) %// just for intermediate display, you can comment these 2 lines
end
disp(msgTerm)
An example for 10 cubes/side with maxPathLength=1000
:
来源:https://stackoverflow.com/questions/31964909/program-stucks-in-infinite-loop-although-there-is-a-condition-to-terminate-it-m