Thank you all I seem to have get it.
If you want to explore more complex recursion systems, for example what calls are made and in what order when calculating a specific value in the Fibonacci series, I would recommend you add another tool to your arsenal:
Graphviz. It is a set of programs that take textual descriptions of graphs and nets in the human-readable
DOT language, and draws the graph/net as an image, automatically placing nodes and edges according to tool-specific rules.
dot draws standard directed and undirected graphs and nets,
circo places nodes in a circle, and so on.
Each node does require an unique identifier, which can be text, number, or even a pointer (using format
\"%p\"). I recommend using a variable and a getter function to obtain a new unique id:
static int unique_ids = 0;
static inline int unique_id(void) {
return ++unique_id;
}
which simply increments the
unique_ids variable and returns the incremented value.
A program that calculates say the 6th number in the Fibonacci series printing the call graph would be
#include <stdlib.h>
#include <stdio.h>
static int unique_ids = 0;
static inline int unique_id(void) {
return ++unique_ids;
}
int fibonacci(FILE *dot, int n, int caller_id) {
/* Grab a new unique ID, identifying this particular call in the DOT graph */
int id = unique_id();
/* 'result' will eventually specify the calculated n'th Fibonacci number, F(n). */
int result;
/* F(n) = F(n-1) + F(n-2), but F(0) = 0, F(1) = 1. */
if (n < 2) {
result = n;
} else {
result = fibonacci(dot, n-1, id) + fibonacci(dot, n-2, id);
}
/* Describe this node in DOT, if 'dot' is non-NULL */
if (dot) {
/* First, describe this node */
fprintf(dot, " \"%d\" [ label=\"F(%d) = %d\" ];\n", id, n, result);
/* Then describe the edge to the parent (who called this function) */
fprintf(dot, " \"%d\" -> \"%d\";\n", id, caller_id);
}
/* As usual, this function returns the calculated n'th Fibonacci number. */
return result;
}
int main(void) {
/* Standard output consists of a directed graph in DOT language: */
fprintf(stdout, "digraph {\n");
/* Zero ID is the following fibonacci(stdout, 6, 0); call */
int result = fibonacci(stdout, 6, 0);
fprintf(stdout, " \"0\" [ label=\"fibonacci(stdout, 6, 0) = %d\" ];\n", result);
/* End of directed graph. */
fprintf(stdout, "}\n");
/* We also print the result to standard error. */
fprintf(stderr, "fibonacci(6) = %d, used %d recursive calls\n", result, unique_ids);
return EXIT_SUCCESS;
}
To standard error, this outputs
fibonacci(6) = 8, used 25 recursive callsand to standard output, the following DOT language directed graph:
digraph {
"6" [ label="F(1) = 1" ];
"6" -> "5";
"7" [ label="F(0) = 0" ];
"7" -> "5";
"5" [ label="F(2) = 1" ];
"5" -> "4";
"8" [ label="F(1) = 1" ];
"8" -> "4";
"4" [ label="F(3) = 2" ];
"4" -> "3";
"10" [ label="F(1) = 1" ];
"10" -> "9";
"11" [ label="F(0) = 0" ];
"11" -> "9";
"9" [ label="F(2) = 1" ];
"9" -> "3";
"3" [ label="F(4) = 3" ];
"3" -> "2";
"14" [ label="F(1) = 1" ];
"14" -> "13";
"15" [ label="F(0) = 0" ];
"15" -> "13";
"13" [ label="F(2) = 1" ];
"13" -> "12";
"16" [ label="F(1) = 1" ];
"16" -> "12";
"12" [ label="F(3) = 2" ];
"12" -> "2";
"2" [ label="F(5) = 5" ];
"2" -> "1";
"20" [ label="F(1) = 1" ];
"20" -> "19";
"21" [ label="F(0) = 0" ];
"21" -> "19";
"19" [ label="F(2) = 1" ];
"19" -> "18";
"22" [ label="F(1) = 1" ];
"22" -> "18";
"18" [ label="F(3) = 2" ];
"18" -> "17";
"24" [ label="F(1) = 1" ];
"24" -> "23";
"25" [ label="F(0) = 0" ];
"25" -> "23";
"23" [ label="F(2) = 1" ];
"23" -> "17";
"17" [ label="F(4) = 3" ];
"17" -> "1";
"1" [ label="F(6) = 8" ];
"1" -> "0";
"0" [ label="fibonacci(stdout, 6, 0) = 8" ];
}
If you pipe the output to
dot, or save it to a file and run
dot specifying the name of that file, you will see the graph itself,
(Click to embiggen)I personally have over three decades of paid software development in various programming languages, including a lot of C. Yet, I still prefer to use tools like Graphviz to verify my understanding of algorithms and even complex data structures (and how they refer to each other). Thus, I would suggest that learning how to augment your learning programs with DOT language output can help you produce better code, because you then understand the code better (as in how it works, how it computes the results, how many calls are needed, and how to make it much more efficient). For example, the above call graph shows that many Fibonacci numbers are calculated again and again, so
caching the already calculated results would make it much faster. In reality, there are better mathematical forms to use than the basic sum formula (Binet's formula being most powerful, but requiring arbitrary-precision numbers for larger
n), but I'm sure you see the usefulness of such graphs.
(I personally only use Linux for development. Development on Macs is very similar, but on Windows, there are quirks and exceptions, even when using WSL2. Because I do not know what operating system you use (or anyone else reading this post uses), I omitted the exact commands you use, because Graphviz is available for free and works in all operating systems. Whenever there is a graphical desktop available, it can display the graph directly, but it can also just generate the image file instead. Thus, this development pattern will work on any operating system, and even with any programming language. I've found it extremely powerful, and hope you find it useful too.)
An even more important thing, in my opinion, is writing useful comments. The comments should never describe what the code does, because we can read that from the code itself. The comments should always try to convey your intent and reasoning instead, so that those reading the code can compare what the comment says about the intent for the code and what the code actually does. I did not learn this early enough, and
still have trouble writing the kind of comments I would like to, so the earlier you learn to do this, and
always write such comments, the better a programmer you will become. I am literally limited by this, because the maintainability of the code I write suffers due to my comments not being as useful and to the point they could be! I hope you become better than I am, eventually.