Reputation: 435
I'm still a newbie to C++ and I've been trying to modularize some spaghetti code that was given to me. So far (apart from learning how to use git and installing the rarray library to replace the automatic arrays with them) I've been sort of stumped as to how to modularize things and then compile it via make.
I understand that I must create prototypes in a header, create my object files from my functions, and then compile it all with a 'driver' code. Running/writing a make file is not my concern, but it's how to begin modularizing code like this; I'm not sure how to make functions that modify arrays!
Any pointers in the right direction would be amazing. I can clarify more if necessary.
#include <cmath>
#include <iostream>
#include <rarray> // Including the rarray library.
#include <rarrayio> // rarray input/output, if necessary. Probably not.
int main()
{
// ants walk on a table
rarray<float,2> number_of_ants(356,356);
rarray<float,2> new_number_of_ants(356,356);
rarray<float,2> velocity_of_ants(356,356);
const int total_ants = 1010; // initial number of ants
// initialize
for (int i=0;i<356;i++) {
for (int j=0;j<356;j++) {
velocity_of_ants[i][j] = M_PI*(sin((2*M_PI*(i+j))/3560)+1);
}
}
int n = 0;
float z = 0;
for (int i=0;i<356;i++) {
for (int j=0;j<356;j++) {
number_of_ants[i][j] = 0.0;
}
}
while (n < total_ants) {
for (int i=0;i<356;i++) {
for (int j=0;j<356;j++) {
z += sin(0.3*(i+j));
if (z>1 and n!=total_ants) {
number_of_ants[i][j] += 1;
n += 1;
}
}
}
}
// run simulation
for (int t = 0; t < 40; t++) {
float totants = 0.0;
for (int i=0;i<356;i++) {
for (int j=0;j<356;j++) {
totants += number_of_ants[i][j];
}
}
std::cout << t<< " " << totants << std::endl;
for (int i=0;i<356;i++) {
for (int j=0;j<356;j++) {
new_number_of_ants[i][j] = 0.0;
}
}
for (int i=0;i<356;i++) {
for (int j=0;j<356;j++) {
int di = 1.9*sin(velocity_of_ants[i][j]);
int dj = 1.9*cos(velocity_of_ants[i][j]);
int i2 = i + di;
int j2 = j + dj;
// some ants do not walk
new_number_of_ants[i][j]+=0.8*number_of_ants[i][j];
// the rest of the ants walk, but some fall of the table
if (i2>0 and i2>=356 and j2<0 and j2>=356) {
new_number_of_ants[i2][j2]+=0.2*number_of_ants[i][j];
}
}
}
for (int i=0;i<356;i++) {
for (int j=0;j<356;j++) {
number_of_ants[i][j] = new_number_of_ants[i][j];
totants += number_of_ants[i][j];
}
}
}
return 0;
}
Upvotes: 1
Views: 914
Reputation: 36597
This is not spaghetti code at all. The control structure is actually quite straight-forward (a series of loops, sometimes nested). From the manner csome constructs are being used, it has been translated from some other programming language to C++ without much effort to turn it from the original language to "effective C++" (i.e. it is C++ written with techniques from another language). But my guess is that the original language was somewhat different from C++ - or that the original code did not make a lot of use of that language's features.
If you want to modularise it, consider breaking some things into separately, appropriately named, functions.
Get rid of the magic values (like 356
, 3560
, 0.3
, 40
, 1.9
, etc). Turn them into named constants (if they are to be fixed at compile time) or named variables (if there is a reasonable chance you may wish them to be inputs to the code at some time in the future). Bear in mind that M_PI
is not actually standard in C or C++ (it is common to a number of C and C++ implementations, but is not standard so is not guaranteed to work with all compilers).
Work out what rarray
is, and work out how to replace it with a standard C++ container. My guess, from the usage, is that rarray<float, 2> number_if_ants(356,356)
represents a two-dimensional array of floats, with both dimensions equal to 356
. As such, it might be appropriate to use a std::vector<std::vector<float> >
(any version of C++) or (in C++11) std::array<std::array<float, dimension>, dimension>
(where dimension
is my arbitrary name to replace your magic value of 356
). That may look a bit more complicated, but can be made much simpler with the help of a couple of tyepdef
s. In the long run, C++ developers will understand the code better than they will if you insist on using rarray
.
Look carefully at operations that work on the C++ standard containers. For example, construction and resizing of a std::vector
- by default - initialises elements to zero in many circumstances. You might be able replace some of sets of nested loops with a single statement.
Also, dig into the standard algorithms (in header algorithm
). They can act on a range of elements in any std::vector
- via iterators - and possibly do other things directly that this code needs nested loops for.
Upvotes: 0
Reputation: 80
Except for replacing the magic numbers with constants in the beginning, there is not much that can be done to improve scientific code as barely anything is reusable.
The only part that is repeated is:
for (int i=0;i<356;i++) {
for (int j=0;j<356;j++) {
new_number_of_ants[i][j] = 0.0;
}
}
Which you can extract as a function (I have not replaced the magic numbers, you should do that first and give them as parameters):
void zeroRarray(rarray<float, 2> number_of_ants) {
for (int i = 0; i < 356; i++) {
for (int j = 0; j < 356; j++) {
number_of_ants[i][j] = 0.0;
}
}
}
And call like:
zeroRarray(number_of_ants); // Btw the name of this rarray is misleading!
Also, replace the mathematical expressions with function calls:
velocity_of_ants[i][j] = M_PI* (sin((2 * M_PI * (i + j)) / 3560) + 1);
with:
velocity_of_ants[i][j] = calculateSomething(i, j);
where the function looks something like:
double calculateSomethingHere(int i, int j) {
return M_PI * (sin((2 * M_PI * (i + j)) / 3560) + 1);
}
so you can give these long and insightful names and focus on what each part of your code does and not what it looks like.
Most IDE's have a refactoring functionality built-in where you highlight part of code that you want to extract and right-click and select Extract function from Refactor (or something similar).
If your code is short (e.g under 200 lines) there is not much you can do except extracting parts of your code that are very abstract. The next step is to write a class for ants and what ever these ants are doing, but there is little benefit for this unless you have more code.
Upvotes: 1
Reputation: 6752
I've been sort of stumped as to how to modularize things and then compile it via make.
That might be in part due to the code you are trying to modularize. Modularization is an idiom that is often used to help separate problem domains so that if one area of code has an issue, it won't necessarily* affect another area, and is especially useful when building larger applications; modularization is also one of the key points to classes in object oriented design.
*necessarily with regards to "spaghettification", that is, if the code really is "spaghetti code", often modifying or fixing one area of code most certainly affects other areas of code with unintended or unforeseen consequences, in other words, not modular.
The code you've posted is 63 lines (the main function), and doesn't really require any modularization. Though if you wanted to, you'd want to look at what could be modularized and what should be, but again, there isn't really much in the way to separate out, aside from making separate functions (which would just add to the code bulk). And since you asked specifically
I'm not sure how to make functions that modify arrays!
That can be done with the following:
// to pass a variable by reference (so as to avoid making copies), just give the type with the & symbol
void run_simulation(rarray<float,2>& noa, rarray<float,2>& new_noa, rarray<float,2>& voa)
{
// do something with the arrays
}
int main()
{
// ants walk on a table
rarray<float,2> number_of_ants(356,356);
rarray<float,2> new_number_of_ants(356,356);
rarray<float,2> velocity_of_ants(356,356);
...
run_simulation(number_of_ants, new_number_of_ants, velocity_of_ants);
...
}
Also, it should be noted there's a potential bug in your code; under the run simulation
loop, you declare float totants = 0.0;
then act on that variable until the end of the loop, at which point you still modify it with totants += number_of_ants[i][j];
. If this variable is to be used to keep a 'running' total without being reset, you'd need to move the totants
declaration outside of the for
loop, otherwise, strictly speaking, that last totants +=
statement is not necessary.
Hope that can help add some clarity.
Upvotes: 1