Using cell fun to extract data from a cell containing objects

16 views (last 30 days)
Alexander James on 30 Apr 2020
Edited: Alexander James on 24 May 2020
Hello, everyone, first of I'm just beginning to learn MATLAB and trying to write an object oriented program to plant and manage a forest
I'm trying to programme an opertation which will "plant" a tree then check it's position againt all of the other tree's in the domain. Tree's cannot be planted closer than 0.5 away from each other. I succeded partially but then realised I was only checking the postion of the newly planted tree against the preceeding one, not against all the other trees in the "forest". I tried to change the operation to sqrt((Forest{i}.x - Forest{1:i-1}.x)^2 + (Forest{i}.y - Forest{1:i-1}.y))^2 < 0.5 but the planting of the third tree, I recieved this error
Expected one output from a curly brace or dot indexing expression, but there were 2 results.
I looked into and I cannot check the position against all of the objects in the cell this way. I looked into using cellfun but I'm not sure that's the right approach as it's ouput only be one figure, would it best to create a for loop within the while loop? Or some other apporach
Thanks
Forest={};
first = Oak((68-1).*rand(1,1)+1, (78-1).*rand(1,1)+1,0.25);
Forest{1}=first;
i=1;
while numel(Forest)<Size_of_intialforest
i=i+1;
if rand(1,1)>0.5
shoot(i) = Oak((68-1).*rand(1,1)+1, (78-1).*rand(1,1)+1,0.25);
else
shoot(i) = Birch((68-1).*rand(1,1)+1, (78-1).*rand(1,1)+1,0.2);
end
Forest{i}= shoot(i)
% Check location
if sqrt((Forest{i}.x - Forest{i-1}.x)^2 + (Forest{i}.y - Forest{i-1}.y))^2 < 0.5
Forest{i} = {}; % Tree planted too close to the last and is removed by the local community
i=i-1; % step the clock back one to replant the next tree
end
%if sqrt((Forest{i}.x - cellfun(Tree.x,Forest{1:i-1})^2 + (Forest{i}.y - cellfun(Tree.y,Forest{1:i-1}))^2 < 0.5
end

Stephen Cobeldick on 30 Apr 2020
"Expected one output from a curly brace or dot indexing expression, but there were 2 results."
The error is because of how you are using this syntax:
Forest{1:i-1}
Read these to know what that syntax actually does:
It would probably be easier to use a non-scalar structure (rather than that cell array of scalar structures/objects):
Alexander James on 30 Apr 2020
It would probably be easier to use a non-scalar structure (rather than that cell array of scalar structures/objects):
Thank you for your reply, I don't understant what a non-scalar structure implies at the moment but I'll look into it

Guillaume on 30 Apr 2020
Edited: Guillaume on 30 Apr 2020
"I don't understant what a non-scalar structure implies at the moment but I'll look into it"
A non-scalar structure is simply a structure array:
s(1).x = 1234;
s(2).x = 4567;
%s is a 1x2 structure with field x
Whereas at the moment what you're doing is storing 1x1 structures (actually from your description I think it's 1x1 objects) into a 1D cell array, so instead you have:
c{1} = struct('x', 1234);
c{2} = struct('x', 4567);
I think you're using objects not structures, but what Stephen said about structures also applies to objects. You can use an array of objects as long as they are the same class, instead of storing them in a cell array.
However, in your case it appears that you're using two different classes. In that case, in order to put all these objects in an array, you will have to derive each class from matlab.mixin.heterogeneous. If it's beyond your current level, then stay with a cell array but you have to be aware that indeed you won't be able to write anything like Forest{a:b}. You will have to loop over the elements of the cell array either with a explicit loop or with cellfun and co.
To calculate the distance of a tree against all other trees:
curtree = Forest{i};
dist = cellfun(@(tree) hypot(tree.x - curtree.x, tree.y - curtree.y), Forest(1:i-1));
if any(dist < 0.5)
%tree is to close to at least one other tree
%...
end
edit: actually looking a bit more closely at your code, it looks like your Oak and Birch are already heterogeneous arrays since you have:
shoot(i) = Oak(..)
else
shoot(i) = Birch(..)
Functionally, there's no difference between the cell array Forest of scalar objects and the non-scalar heterogeneous object shoot. So you could also do:
dist = hypot([shoot(1:i-1).x] - shoot(i).x, [shoot(1:i-1).y] - shoot(i).y);
if any(dist < 0.5)
%tree is to close to at least one other tree
%...
end
and never bother with Forest.

Guillaume on 6 May 2020
"I agree this is somewhat strange and not what I'd planned the relationship of my class hierarchy to be, however i just wanted this method to work, learn from the process"
That's fine and experimenting by trial and error is a good way to learn. That's also the intent of my comment on design considerations. As you get more experience with OOP, you'll learn the design patterns that work and come up with better class hierarchies.
With regards to your first question. I don't understand it fully, I think because I don't clearly understand how each class is designed. It would be helpfull if you could attach the m file of each class.
To clarify what this does:
distances = hypot([this.Forest.x] - this.x, [this.Forest.y] - this.y);
it is equivalent to:
temp_xarray = zeros(1, numel(this.Forest)); %[this.Forest.x] creates a row vector with the same number of elements as this.Forest
temp_yarray = zeros(1, numel(this.Forest)); %[this.Forest.y] creates a row vector with the same number of elements as this.Forest
for idx = 1:numel(this.Forest)
temp_xarray = this.Forest(idx).x; %this is how [this.Forest.x] fills the array
temp_yarray = this.Forest(idx).y; %this is how [this.Forest.y] fills the array
end
distances = hypot(temp_xarray - this.x, temp_yarray - this.y); %vectorised distance calculation
However, if you were to use an explicit loop as above, then you'd be better off dispencing with the temporary arrays and filling the distance array directly:
distances = zeros(1, nume(this.Forest));
for idx = 1:numel(this.Forest)
distances(idx) = hypot(this.Forest(idx).x - this.x, this.Forest(idx).y - this.y);
end
With regards to question two, again it would help to see your actual classes. It doesn't sound that at the moment you've created any private properties or methods of the class (it's fine to start with but you miss on a lot of the benefit of OOP if everything is public) so any class should be able to call a method of any other class. However, whether within the same class or from another class, in order to call a (non-static) class method, it must be called on an instance of the class. That is:
obj.methodOfClassA(foo); %can also be written as:
% methodOfClassA(obj, foo);
then obj has to be an object of classA. (I'm ignoring the fact that in matlab it doesn't have to be the first argument, you shouldn't be using that fact it's for more advanced designs involving superior and inferior classes).
Alexander James on 20 May 2020
Hi Guillaume, sorry I've been really busy the last two weeks but I'm back to work on it now, and with some success!
I've abandoned trying to clear trees by their proximity to the road by calling a method from road class as this did not make any sense. What I've done is create a static method in the Forest class and then passed the Forest obeject (containg the .forest list) to that argument.
Forest.clear_trees(Forest)
In the Forest class this looks like
methods(Static)
function clear_trees(this)
for i=1:70
end
this.forest(dist < 2) = [];
end
This succesfully removes trees, which I was so happy when I got it to finally work!
However, there's something wrong in the implimentation of distance calculation, I'm afraid I don't know why (I think its to do with my bad implmentation of the road) only trees at the top of my "plot" are removed not all the way along the road, if you get what I mean. I've included all of the .m's I've got up until this point, most of them are pretty bare currently.
Alexander James on 24 May 2020
I'ts now been solved