Using cell fun to extract data from a cell containing objects
You are now following this question
- You will see updates in your followed content feed.
- You may receive emails, depending on your communication preferences.
An Error Occurred
Unable to complete the action because of changes made to the page. Reload the page to see its updated state.
Show older comments
0 votes
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
2 Comments
"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
Accepted Answer
"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.
10 Comments
Alexander James
on 1 May 2020
Hi Gaillaume. Thanks very much for your answer. You're correct, both classes were created under a super class< matlab.mixin.heterogeneous called Tree.
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
This was a very good way of getting around the problem, thank you! Not understanding the difference between between the cell array Forest of scalar objects and the non-scalar heterogeneous object shoot in the end I ended up doing in a nested for loop within the while loop (I also made some other modifications). See below:
Forest={};
i=1;
while numel(Forest)<Size_of_intialforest
if rand>0.5 % ramdomly create either an Oak or Birch object
shoot(i) = Oak(rand*68, rand*78,0.25);
else
shoot(i) = Birch(rand*68, rand*78,0.2);
end
Forest{i}= shoot(i);
% Location check
if numel(Forest)>1 % if there's more than one tree in forest
for j = 1:numel(Forest)
if sqrt((Forest{i}.x - Forest{j}.x)^2 + (Forest{i}.y - Forest{j}.y)^2) < 0.5 && j~=i
Forest{i} = {}; % Tree planted too close to the last and is removed by the local community
break
end
end
if j==numel(Forest)
i=i+1;
end
else
i=i+1;
end
end
I realise that this method is slower than your suggestion but since that doesnt matter for this application, I shall keep it as written for the time being. However I think I will make use of the knowledge that the non-scalar heterogeneous object shoot can be used this is this way in the future of this project, my thanks!
Guillaume
on 4 May 2020
Note that as I've shown:
sqrt((Forest{i}.x - Forest{j}.x)^2 + (Forest{i}.y - Forest{j}.y)^2)
could be written:
hypot(Forest{i}.x - Forest{j}.x, Forest{i].y - Forest{j}.y})
it doesn't save you much typing, but it is supposed to be more robust.
And as I've shown, if you use shoot instead of Forest the loop can be vectorised.
I don't particularly understand why you're still using Forest. It contains exactly the same as shoot, that is for all i, Forest{i} == shoot(i), so you're just storing the same information twice, with the cell array being less practical and using more memory.
Guillaume
on 4 May 2020
Alexander James' question posted as an answer moved to a comment here:
I'm not sure if I should ask this here or make a new question but;
I'm not sure why the outcomes of class methods are not being applied to global variables:
When I enter
clear(Forest_Road,shoot)
in the main script I do not recive any errors but I don't have any trees being removed, I think this is by design. I tried making the input arguement a global variable but it didn't affect the outcome, what am I missing.
classdef Road
% Creation of the class road
%
properties
x;
y;
c;
end
methods
% Constructer
function obj = Road(x,y,c)
% Construct an instance of road class
% Assign input properties
obj.x = x;
obj.y = y;
obj.c = c;
end
% function clear_trees = clear(obj,shoot)
% %METHOD1 Summary of this method goes here
% % Detailed explanation goes here
% for i=1:numel(shoot)
% for j=1:80
% dist = hypot([shoot(i).x] - obj.x(j), [shoot(i).y] - [obj.y(j) + obj.c]);
% if any(dist < 2);
% global shoot(i)=[];
% end
% end
% end
% end
function chop_trees = clear(obj,shoot)
i=1;
j=1;
while j~=80
for j=1:80
dist = hypot([shoot(i).x] - obj.x(j), [shoot(i).y] - [obj.y(j) + obj.c]);
if any(dist < 2);
global shoot
shoot(i)=[];
end
end
i=i+1;
end
end
end
end
Guillaume
on 4 May 2020
Yes, it probably would have been better to ask a new question. Certainly don't post it as an answer.
Several things
- calling a function clear is a bad idea. Readers of your code may think you're trying to call the built-in clear (which is overrused by beginners).
- global variables are extremely discouraged in standard non-OOP programming. in OOP programming, there's absolutely no need for them. Use class properties.
In this particular case, I don't even understand what you're trying to do. shoot is an input of the function, so it can't be global. Doesn't matlab complain about that code.
- deleting elements of an array while you're looping over its elements is going to fail. Imagine you have the array [1 2 4 6 8] and you'd like to delete all even values with a loop as you've done:
shoot = [1 2 4 6 8]; %for demo purpose
for j = 1:5
if mod(shoot(j), 2) == 0 %if number is even
shoot(j) = [];
end
end
The loop goes as follow:
- j = 1, shoot(1) is 1. It's odd, the if is false. proceed to next step.
- j = 2, shoot(2) is 2. It's even, the if is true so shoot(j) == shoot(2) gets deleted. shoot is now [1, 4 6 8]. Proceed to next step.
- j = 3, as noted above, shoot is now [1, 4, 6, 8], so shoot(3) is 6. It's even, the if is true so shoot(j) == shoot(3) gets deleted. shoot is now [1, 4, 8]. Notice that you completely skipped over 4 since it got moved to position 2 by the previous deletion. Proceed to next step since we stop at j = 5.
- j = 4. Oops, shoot only has 3 elements and matlab errors when you ask for shoot(j).
Not only does the code errors, but it's skipped over some elements. If you're going to use a loop you need to keep track of the elements to delete in the loop (with a logical) then proceed to the deletion after the loop:
todelete = false(size(shoot)); %array keeping track of which element to delete
for j = 1:numel(shoot)
if sometest
todelete(j) = true; %this element must be deleted
end
end
shoot(todelete) = []; %proceed with deletion after the loop.
- I don't understand why you've got a for loop wrapped in a while loop.
- In my answer, the reason I use brackets in [shoot(1:i-1).x] in the hypot call is because shoot(1:i-1) is an array. The [] converts the comma separated list returned by shoot(1:-1).x back into an array. See Stephen's comment for more links on comma separated lists. However, if you do shoot(i).x, shoot(i) is a scalar and there's no point in wrapping that in [].
Alexander James
on 4 May 2020
Edited: Alexander James
on 4 May 2020
Thank you once again for your answer, so lots of issues..
I've made some changes in my code, I renamed the function to c_trees and removed global, instead I've added Forest (renamed from shoot) to my Road properties, i've also made road < handle.
- I don't understand why you've got a for loop wrapped in a while loop.
My reasoning was that while I wasn't sure about the size of the forest due to me deleting objects, I wanted to check the size of size of my Forest in each iteration. You're right of course about deleting objects ater the loop completes due to the issues raised, I've included this below.
classdef Road < handle
% Creation of the class road
properties
x;
y;
c;
Forest;
end
methods
% Constructer
function obj = Road(x,y,c)
% Construct an instance of road class
% Assign input properties
obj.x = x;
obj.y = y;
obj.c = c;
end
function clear_trees = c_trees(obj)
i=1;
j=1;
while i~=numel(Forest.forest)
for j=1:80
dist(i) = hypot(Forest.forest(i).x - obj.x(j), Forest.forest(i).y - obj.y(j) + obj.c)
end
i=i+1;
end
Forest.forest(dist < 2)=[];
end
end
end
Two quesions, when I create my road object I don't get the Forest object added the road variables, instead I get an empty array in road.Forest, not sure why this happens.
I've created a Forest class from which I can carry out methods on the forest as a whole, thus the new Forest list can be found in Forest.forest. Mabye it would be best to move the method to this class?
Guillaume
on 4 May 2020
First, practical considerations considering your code:
"when I create my road object I don't get the Forest object added the road variables, instead I get an empty array in road.Forest, not sure why this happens."
From what I can see, you never assign anything to obj.Forest, hence it will always be empty. As the property is public, it's possible that the assignment is done outside the class (which would be a bad design) but it's certainly not done inside the class.
There are still lots of problem with your c_trees method. I'm a bit unclear on the design of the Forest class. I think what you're trying to do is:
function clear_trees(this)
%I prefer to name the object _this_ rather than _obj_. Call it whatever you want
distances = hypot([this.Forest.x] - this.x, [this.Forest.y] - this.y);
this.Forest(distances < 2) = [];
end
Secondly, on a higher level, design considerations:
When working with OOP, one thing that is important to get right (as this greatly affects the design and flexibility of the code) is the relationship between classes. Here you have a road which has as a member a forest. Does a forest belong to a road? That doesn't sound right to me, so I'm not sure that Forest should be a member of road. I could understand that you'd want to store all the forest a road goes through in which case you could have an array of forests (which could be empty) as a member of a road object, or you could store all the roads that go through a forest in which case you could have an array of roads (which could be empty) as a member of a forest objects. However since the relationship is many to many, the best storage would be a separate class/array that stores the relationship.
Alexander James
on 5 May 2020
Thank you once again Guillaume:
I ammended the class contructer the assign the obj.Forest to the object Road. 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 then eventually remove the Forest from the road propeties. In fact I have the road as property of my Forest class along with forest and geometry.
Two questions:
One: When I try and run the c_trees method as you recommended there was no loop to go over all of the objects in Forest.forest however I know that the method did go through all of the objects because I ommited the ; at the end of the distances calculation and saw the process. In this case however because in comparing the presence of a trees compared to a road which was created as:
road = Road(1:80,1:80,-12);
(There is only one road so I allowed myself to name the object after the class.) Would I not have to loop over 1:80 in the comparison script?
As I did before but not including the j (indexing the forest objects) since I clearly don't need to.
dist(i) = hypot(([this.Forest.forest.x] - this(i).x, [this.Forest.forest.y] - this(i).y + this.c))
This end result was that still no Forest.forest objects were removed.
If stating to think that I went about this completely the wrong way, see question 2.
Two: When I copy the method from my road class to my forest class (%'ing out the first) I tried to run the method from my main script but MATLAB complained that Road has no method c_trees. If confused. Does that mean that I cannot run a method on other objects, does what I enter into the argument have to be of the class as the method I'm attempting to run on it. That would mean that my method is doomed to failure (attempting to run call a method in the main script to accomplish the clearing of trees from my around my road).
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
dist = hypot([this.forest.x] - this.road.x(i), [this.forest.y] - (this.road.y(i) + this.road.c))
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
Edited: Alexander James
on 24 May 2020
I'ts now been solved
More Answers (0)
Categories
Find more on Data Type Identification in Help Center and File Exchange
See Also
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!Select a Web Site
Choose a web site to get translated content where available and see local events and offers. Based on your location, we recommend that you select: .
You can also select a web site from the following list
How to Get Best Site Performance
Select the China site (in Chinese or English) for best site performance. Other MathWorks country sites are not optimized for visits from your location.
Americas
- América Latina (Español)
- Canada (English)
- United States (English)
Europe
- Belgium (English)
- Denmark (English)
- Deutschland (Deutsch)
- España (Español)
- Finland (English)
- France (Français)
- Ireland (English)
- Italia (Italiano)
- Luxembourg (English)
- Netherlands (English)
- Norway (English)
- Österreich (Deutsch)
- Portugal (English)
- Sweden (English)
- Switzerland
- United Kingdom (English)