Skip to content

Saving progress for review of draft chapter still needs helper functions #83

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 250 additions & 0 deletions source/chx-recursion.ptx
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
<?xml version="1.0" encoding="UTF-8"?>
<chapter xml:id="recursion-in-java">
<title>Recursion in Java</title>
<introduction>
</introduction>
<section xml:id="basic-recursion">
<title>Basic Recursion</title>
<p>
In this chapter, we will explore how to translate your recursive logic from Python to Java. While the core concepts of recursion remain the same, the syntax and structure of your code will change somewhat.
</p>
<p><idx>recursion</idx>
As you may know from Python, <term>recursion</term> is a powerful problem-solving technique involving base cases and recursive steps in which a function or method calls itself. When moving to Java, the core logic you've learned remains identical. The challenge is adapting that logic to Java's statically-typed, class-based syntax.
</p>
<p>
Let's take the familiar factorial function (which calculates the factorial of a number, namely the product of all positive integers from 1 to n). The logical steps in the code are the same, but the implementation details change.
</p>
<p>
Here is the standard implementation in Python:
</p>
<program interactive="activecode" language="python">
<code>
class MathTools:
"""A utility class for mathematical operations."""
def factorial(n: int) -&gt; int:
"""Calculates the factorial of n using recursion."""
# A check for negative numbers is good practice.
if n &lt; 0:
raise ValueError("Factorial is not defined for negative numbers.") # Base Case: 0! or 1! is 1
if n &lt;= 1:
return 1 # Recursive Step: n * (n-1)!
# The call is now to the method within the class.
return n * MathTools.factorial(n - 1)# This block shows how to use the class method.
if __name__ == "__main__":
number = 5
result = MathTools.factorial(number) # Call the method on the class
print(f"{number}! is {result}")
</code>
</program>
<p>
The Java version follows the same recursive logic but requires three key syntax changes: the method must be inside a class, you must declare the parameter and return types (<c>int n</c> and <c>int return</c>), and you use <c>public static</c> to make it callable from <c>main</c>. The base case and recursive step remain conceptually identical.
</p>
<p>
Here is the equivalent Java code:
</p>
<program interactive="activecode" language="java">
<code>
public class MathTools { /**
* Calculates the factorial of n using recursion.
* This is a static method, like Python's @staticmethod.
* @param n The non-negative integer.
* @return The factorial of n as a long to prevent overflow for larger numbers.
*/
public static int factorial(int n) {
// A check for negative numbers is good practice.
if (n &lt; 0) {
throw new IllegalArgumentException("Factorial is not defined for negative numbers.");
} // Base Case: 0! or 1! is 1
if (n &lt;= 1) {
return 1;
} // Recursive Step: n * (n-1)!
return n * factorial(n - 1);
} /**
* The main entry point for the application.
* This is the Java equivalent of Python's 'if __name__ == "__main__":'
*/
public static void main(String[] args) {
int number = 5;
// The static method is called directly on the class.
long result = MathTools.factorial(number); System.out.println(number + "! is " + result);
}
}
</code>
</program>
<p>
Notice the key differences: instead of <c>def</c>, the method signature <c>public static int</c> declares its scope, that it belongs to the class rather than an object, and that it returns an <c>int</c>. All logic is contained within curly braces <c>{}</c>.
</p>
</section>
<section xml:id="java-patterns-for-recursion">
<title>Common Recursive Patterns</title>

<p>
In many recursive algorithms, the recursive calls need extra information that the original caller shouldn't have to provide. For example, to recursively process an array, you need to keep track of the current position (index). To traverse a tree, you need to know the current node. This extra information clutters the public-facing method signature.
</p>
<p>
A common pattern to solve this is using a private helper method. This pattern lets you create a clean, simple public method that users will call, while the private helper method handles the complex details of the recursion. The public method typically makes the initial call to the private helper, providing the necessary starting values for the extra parameters.
</p>
<p>
Let's see this pattern in action with an example that calculates the sum of all elements in an integer array. The public <c>sum</c> method only takes the array, but the private <c>sumHelper</c> method also takes an index to track its progress through the array.
</p>

<p>
You're likely familiar with how some recursive algorithms, like the naive Fibonacci implementation,
are elegant but inefficient, due to branching recursive calls filling the call stack. A common pattern to solve
this is using a private helper method.
</p>
<p>
The following example demonstrates this pattern. The public <c>fib</c> method provides a simple entry point, while the private <c>fibHelper</c> method performs the efficient recursion by carrying its state (the previous two numbers) in its parameters.
</p>
<p>
The following Java code demonstrates a similar pattern.
</p>

<program interactive="activecode" language="java">
<code>
public class FibonacciExample {
public int fib(int n) {
if (n &lt; 0) {
throw new IllegalArgumentException("Input cannot be negative.");
}
// Initial call to the recursive helper with depth 0.
return this._fibHelper(n, 0, 1, 0);
}
private int _fibHelper(int count, int a, int b, int depth) {
// Create an indent string based on the recursion depth.
String indent = " ".repeat(depth);
// Print when the method is entered (pushed onto the stack).
System.out.printf("%s[&gt;&gt;] ENTERING _fibHelper(count=%d, a=%d, b=%d)%n", indent, count, a, b);
// Base Case: When the count reaches 0, 'a' holds the result.
if (count == 0) {
System.out.printf("%s[&lt;&lt;] EXITING (Base Case) -&gt; returns %d%n", indent, a);
return a;
}
// Recursive Step.
int result = this._fibHelper(count - 1, b, a + b, depth + 1);
// Print when the method exits (popped from the stack).
System.out.printf("%s[&lt;&lt;] EXITING (Recursive Step) -&gt; passing %d%n", indent, result);
return result;
}
public static void main(String[] args) {
FibonacciExample calculator = new FibonacciExample();
int n = 4; // Let's calculate the 4th Fibonacci number.
System.out.printf("--- Calculating fib(%d) ---%n", n);
int result = calculator.fib(n);
System.out.println("--------------------------");
System.out.printf("The %dth Fibonacci number is: %d%n", n, result);
}
}
</code>
</program>
<p>
This helper method approach is significantly more efficient in terms of time than the classic branching recursion (where <c>fib(n)</c> calls <c>fib(n-1)</c> and <c>fib(n-2)</c>). The branching model has an exponential time complexity of roughly O(2^n) because it re-calculates the same values many times. In contrast, our helper method has a linear time complexity of O(n), as it avoids re-computation by carrying the previous two results (a and b) forward into the next call.
</p>
<p>
However, regarding memory efficiency, the comparison is different. The maximum depth of the call stack for both the naive and the helper method is proportional to n, giving them both a space complexity of O(n). This means that while the helper method is much faster, it is equally vulnerable to a <c>StackOverflowError</c> for very large values of n. Because Java does not perform tail-call optimization, any recursive solution that goes too deep will exhaust the stack memory, regardless of its time efficiency. For true memory efficiency (O(1) space), an iterative loop-based solution is superior.
</p>
<p>
The following Python code demonstrates the same pattern, using a public method to initiate the calculation and a private helper method to perform the recursion.
</p>
<program interactive="activecode" language="python">
<code>
class FibonacciExample:
def fib(self, n: int) -&gt; int:
"""
Public method to start the Fibonacci calculation.
"""
if n &lt; 0:
raise ValueError("Input cannot be negative.")
# Initial call to the recursive helper with depth 0.
return self._fib_helper(n, 0, 1, 0)

def _fib_helper(self, count: int, a: int, b: int, depth: int) -&gt; int:
"""
Private helper that performs the tail recursion to find the number.
"""
# Create an indent string based on the recursion depth.
indent = " " * depth
# Print when the method is entered (pushed onto the stack).
print(f"{indent}[&gt;&gt;] ENTERING _fib_helper(count={count}, a={a}, b={b})")

# Base Case: When the count reaches 0, 'a' holds the result.
if count == 0:
print(f"{indent}[&lt;&lt;] EXITING (Base Case) -&gt; returns {a}")
return a

# Recursive Step.
result = self._fib_helper(count - 1, b, a + b, depth + 1)
# Print when the method exits (popped from the stack).
print(f"{indent}[&lt;&lt;] EXITING (Recursive Step) -&gt; passing {result}")
return result

# The standard Python entry point, equivalent to Java's `main` method.
if __name__ == "__main__":
calculator = FibonacciExample()
n = 4 # Let's calculate the 4th Fibonacci number.
print(f"--- Calculating fib({n}) ---")
result = calculator.fib(n)
print("--------------------------")
print(f"The {n}th Fibonacci number is: {result}")
</code>
</program>
</section>
<section xml:id="recursion-limits-in-java">
<title>Recursion Limits: Python vs. Java</title>
<p>
The consequence of deep recursion, running out of stack space, is a concept you've already encountered in Python. Java handles this in a very similar way, throwing an error when the call stack depth is exceeded.
</p>
<p>
The key difference is the name of the error:
</p>
<ul>
<li>In Python, this raises a <c>RecursionError</c>.</li>
<li>In Java, this throws a <c>StackOverflowError</c>.</li>
</ul>
<p>
Neither language supports <idx> tail call optimization </idx><term>tail call optimization</term>, so the practical limits on recursion depth are a factor in both. If an algorithm requires thousands of recursive calls, an iterative (loop-based) approach is the preferred solution in both Python and Java.
</p>
<p>
The following Python code demonstrates a situation where a function calls itself indefinitely without a base case, leading to aRecursionError.
</p>
<program interactive="activecode" language="python">
<code>
def cause_recursion_error():
"""
This function calls itself without a base case, guaranteeing an error.
"""
cause_recursion_error()

# Standard Python entry point
if __name__ == "__main__":
print("Calling the recursive function... this will end in an error!")

# This line starts the infinite recursion.
# Python will stop it and raise a RecursionError automatically.
cause_recursion_error()
</code>
</program>

<p>
The following Java code demonstrates a similar situation, where a method calls itself indefinitely without a base case, leading to a StackOverflowError.
</p>
<program interactive="activecode" language="java">
<code>
public class Crash {
public static void causeStackOverflow() {
// This method calls itself endlessly without a stopping condition (a base case).
// Each call adds a new layer to the program's call stack.
// Eventually, the stack runs out of space, causing the error.
causeStackOverflow();
}
// A main method is required to run the program.
public static void main(String[] args) {
System.out.println("Calling the recursive method... this will end in an error!");
// This line starts the infinite recursion.
causeStackOverflow();
}
}
</code>
</program>
</section>
</chapter>
1 change: 1 addition & 0 deletions source/main.ptx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

<subtitle>The PreTeXt Interactive Edition</subtitle>
<xi:include href="meta_frontmatter.ptx" />

<xi:include href="ch1_overview.ptx" />
<xi:include href="ch3_firstjavaprogram.ptx" />
<xi:include href="ch4_javadatatypes.ptx" />
Expand Down