Skip to content

Commit 7411f65

Browse files
authored
Merge pull request #120 from RunestoneInteractive/newrecursioneg
Improve Recursion chapter
2 parents 5a5573d + 5cde97e commit 7411f65

3 files changed

Lines changed: 337 additions & 279 deletions

File tree

source/ch7_recursion.ptx

Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<chapter xml:id="recursion-in-java">
3+
<title>Recursion in Java</title>
4+
<introduction>
5+
</introduction>
6+
7+
<section xml:id="basic-recursion">
8+
<title>Basic Recursion</title>
9+
<p>
10+
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.
11+
</p>
12+
<p><idx>recursion</idx>
13+
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.
14+
</p>
15+
16+
<p>
17+
Let's take the familiar factorial function, which calculates <m>n!</m> (read as "n factorial"), so for example 5! = 5 × 4 × 3 × 2 × 1 = 120. Factorial is a classic example of recursion, where the function calls itself with a smaller value until it reaches a base case.
18+
In general, <m>n! = n \times (n-1) \times (n-2) \times \cdots \times 2 \times 1</m>,
19+
or recursively defined as <m>n! = n \times (n-1)!</m> with base cases <m>0! = 1</m> and <m>1! = 1</m>.
20+
</p>
21+
<p>
22+
You may recall mathematical notation using the symbol <m>\sum</m> (Greek letter sigma)
23+
to represent "sum." For example, when we sum all elements in an array, we write
24+
<m>\sum_{i=0}^{n-1} a_i</m>, where <m>i=0</m> below the symbol indicates we start at index 0,
25+
<m>n-1</m> above it means we end at index <m>n-1</m>, and <m>a_i</m> represents the array
26+
element at each index <m>i</m>. Similarly, <m>\sum_{i=1}^{n} i</m> means "sum all integers
27+
<m>i</m> from 1 to <m>n</m>."
28+
</p>
29+
<p>
30+
Factorial involves multiplication rather than addition, so we use the product symbol
31+
<m>\prod</m> (Greek letter pi): <m>n! = \prod_{i=1}^{n} i</m>, which means "multiply
32+
all integers <m>i</m> from 1 to <m>n</m>." Both summation and factorial can be expressed
33+
recursively—summation as the first element plus the sum of remaining elements, and factorial
34+
as <m>n \times (n-1)!</m>.
35+
</p>
36+
<p>
37+
Here is a Python implementation of factorial using functions:
38+
</p>
39+
<program xml:id="factorial-python-function" interactive="activecode" language="python">
40+
<code>
41+
def factorial(n):
42+
# Check for negative numbers
43+
if n &lt; 0:
44+
print("Factorials are only defined on non-negative integers.")
45+
return
46+
# Base Case: 0! or 1! is 1
47+
if n &lt;= 1:
48+
return 1
49+
# Recursive Step: n * (n-1)!
50+
return n * factorial(n - 1)
51+
52+
def main():
53+
number = 5
54+
print(str(number) + "! is " + str(factorial(number)))
55+
56+
main()
57+
</code>
58+
</program>
59+
60+
<p>
61+
Many Python programs organize related functions into classes. The same factorial function can be placed inside a class as a method. Then you need to create an instance of the class to call the method. There we create the class <c>MathTools</c> with a method <c>factorial</c>, and we call it from the <c>main</c> function.
62+
</p>
63+
<program xml:id="factorial-python-class" interactive="activecode" language="python">
64+
<code>
65+
class MathTools:
66+
def factorial(self, n):
67+
# Check for negative numbers
68+
if n &lt; 0:
69+
print("Factorials are only defined on non-negative integers.")
70+
return
71+
# Base Case: 0! or 1! is 1
72+
if n &lt;= 1:
73+
return 1
74+
# Recursive Step: n * (n-1)!
75+
return n * self.factorial(n - 1)
76+
77+
def main():
78+
# Create an instance of the class and call the method
79+
math_tools = MathTools()
80+
number = 5
81+
print(str(number) + "! is " + str(math_tools.factorial(number)))
82+
83+
main()
84+
</code>
85+
</program>
86+
87+
<p>
88+
See if you can spot the differences in the Java version below.
89+
</p>
90+
<p>
91+
Here is the equivalent Java code:
92+
</p>
93+
<program xml:id="factorial-java-class" interactive="activecode" language="java">
94+
<code>
95+
public class MathTools {
96+
public static int factorial(int n) {
97+
// Check for negative numbers
98+
if (n &lt; 0) {
99+
System.out.println("Factorials are only defined on non-negative integers.");
100+
return -1; // Return -1 to indicate error
101+
}
102+
// Base Case: 0! or 1! is 1
103+
if (n &lt;= 1) {
104+
return 1;
105+
}
106+
// Recursive Step: n * (n-1)!
107+
return n * factorial(n - 1);
108+
}
109+
110+
public static void main(String[] args) {
111+
int number = 5;
112+
System.out.println(number + "! is " + factorial(number));
113+
}
114+
}
115+
</code>
116+
</program>
117+
<p>
118+
Notice the key differences from Python: instead of <c>def factorial(n):</c>, Java uses <c>public static int factorial(int n)</c> which declares the method's visibility as <c>public</c>, that it belongs to the class rather than an instance (hence, <c>static</c>), the return type as integer, and the parameter type also as integer. The recursive logic—base case and recursive step—remains identical to Python, but all code blocks use curly braces <c>{}</c> instead of indentation.
119+
</p>
120+
</section>
121+
122+
<section xml:id="using-helper-methods">
123+
<title>Using Helper Methods</title>
124+
125+
<p>
126+
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 index of the current position. This extra information clutters the public-facing signature by forcing users to provide implementation details they shouldn't actually need to know about.
127+
</p>
128+
<p><idx>helper method pattern in recursion</idx>
129+
A common pattern to solve this problem is by using a <term>helper method</term>. This pattern lets you create a clean, simple function or public method that users can call, while the private helper function or method handles the complex details of the recursion. The function or public method typically makes an initial call to the private helper method or function, providing the necessary starting values for the extra parameters.
130+
</p>
131+
132+
133+
<p>
134+
First, let's see what happens if we try to write a recursive array sum function <em>without</em> using a helper method. In this approach, the user must provide the starting index, which is awkward and exposes implementation details:
135+
</p>
136+
<program xml:id="array-sum-python-no-helper" interactive="activecode" language="python">
137+
<code>
138+
class ArrayProcessor:
139+
def sum_array(self, arr, index):
140+
"""
141+
This version forces users to provide the index parameter.
142+
This is inconvenient and exposes implementation details.
143+
"""
144+
# Base case: we've processed all elements
145+
if index &gt;= len(arr):
146+
return 0
147+
148+
# Recursive step: current element + sum of remaining elements
149+
return arr[index] + self.sum_array(arr, index + 1)
150+
151+
def main():
152+
processor = ArrayProcessor()
153+
numbers = [1, 2, 3, 4, 5]
154+
# Users must remember to start at index 0 - this is confusing!
155+
result = processor.sum_array(numbers, 0)
156+
print("The sum of " + str(numbers) + " is " + str(result))
157+
158+
main()
159+
</code>
160+
</program>
161+
162+
<p>
163+
This approach has a significant problem, namely that users must remember to start with index 0. Hence, the method signature is cluttered with an implementation detail, and it's easy to make a mistake by passing the wrong starting index. The same awkward pattern appears in Java:
164+
</p>
165+
<program xml:id="array-sum-java-no-helper" interactive="activecode" language="java">
166+
<code>
167+
public class ArrayProcessor {
168+
public static int sumArray(int[] arr, int index) {
169+
// Base case: we've processed all elements
170+
if (index &gt;= arr.length) {
171+
return 0;
172+
}
173+
174+
// Recursive step: current element + sum of remaining elements
175+
return arr[index] + sumArray(arr, index + 1);
176+
}
177+
178+
public static void main(String[] args) {
179+
int[] numbers = {1, 2, 3, 4, 5};
180+
// Users must remember to start at index 0 - this is confusing!
181+
int result = sumArray(numbers, 0);
182+
System.out.println("The sum of [1, 2, 3, 4, 5] is " + result);
183+
}
184+
}
185+
</code>
186+
</program>
187+
188+
<p>
189+
Both versions force users to understand and provide implementation details they shouldn't need to know about. Now let's see how helper methods solve this problem by providing a clean, user-friendly interface. Notice how the public method only requires the array itself, and the hidden recursive logic tracks the current index position.
190+
</p>
191+
<p>
192+
Here's the improved Python version using a helper method:
193+
</p>
194+
<program xml:id="array-sum-python-with-helper" interactive="activecode" language="python">
195+
<code>
196+
class ArrayProcessor:
197+
def sum_array(self, arr):
198+
"""
199+
Public method that provides a clean interface for summing array elements.
200+
Users only need to provide the array - no implementation details required.
201+
"""
202+
if not arr: # Handle empty array
203+
return 0
204+
# Start the recursion at index 0
205+
return self._sum_helper(arr, 0)
206+
207+
def _sum_helper(self, arr, index):
208+
"""
209+
Private helper method that does the actual recursive work.
210+
Tracks the current index position through the array.
211+
"""
212+
# Base case: we've processed all elements
213+
if index &gt;= len(arr):
214+
return 0
215+
216+
# Recursive step: current element + sum of remaining elements
217+
return arr[index] + self._sum_helper(arr, index + 1)
218+
219+
def main():
220+
processor = ArrayProcessor()
221+
numbers = [1, 2, 3, 4, 5]
222+
result = processor.sum_array(numbers)
223+
print("The sum of " + str(numbers) + " is " + str(result))
224+
225+
main()
226+
</code>
227+
</program>
228+
229+
<p><idx>separation of concerns</idx>
230+
The key insight here is called the <term>separation of concerns</term>. The public <c>sum_array</c> method provides a user-friendly interface—callers just pass an array and get the sum. Users don't need to know about indexes or how the recursion works internally. The private <c>_sum_helper</c> method handles the recursive logic with the extra parameter needed to track progress through the array.
231+
</p>
232+
233+
<p>
234+
Now let's see the improved Java version using a helper method:
235+
</p>
236+
<program xml:id="array-sum-java-with-helper" interactive="activecode" language="java">
237+
<code>
238+
public class ArrayProcessor {
239+
public static int sumArray(int[] arr) {
240+
// Handle empty array
241+
if (arr.length == 0) {
242+
return 0;
243+
}
244+
// Start the recursion at index 0
245+
return sumHelper(arr, 0);
246+
}
247+
248+
private static int sumHelper(int[] arr, int index) {
249+
// Base case: we've processed all elements
250+
if (index &gt;= arr.length) {
251+
return 0;
252+
}
253+
254+
// Recursive step: current element + sum of remaining elements
255+
return arr[index] + sumHelper(arr, index + 1);
256+
}
257+
258+
public static void main(String[] args) {
259+
int[] numbers = {1, 2, 3, 4, 5};
260+
int result = sumArray(numbers);
261+
System.out.println("The sum of [1, 2, 3, 4, 5] is " + result);
262+
}
263+
}
264+
</code>
265+
</program>
266+
267+
<p>
268+
Compare these improved versions with the earlier problematic ones. Notice how much cleaner the method calls become: <c>processor.sum_array(numbers)</c> in Python and <c>sumArray(numbers)</c> in Java. Users no longer need to worry about providing the correct starting index or understanding the internal mechanics of the recursion. The helper method pattern creates a clear separation between what users need to know (just pass an array) and the implementation details (tracking the index through recursion).
269+
</p>
270+
271+
<p>
272+
This helper method pattern is essential when your recursive algorithm needs to track additional state (like array positions, accumulated values, or depth counters) that the original caller shouldn't need to provide or care about. It's a fundamental pattern and technique you'll likely use frequently in recursive problem solving.
273+
</p>
274+
</section>
275+
276+
<section xml:id="recursion-limits-in-java">
277+
<title>Recursion Limits: Python vs. Java</title>
278+
<p>
279+
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.
280+
</p>
281+
<p>
282+
The key difference is the name of the error:
283+
</p>
284+
<ul>
285+
<li>In Python, this raises a <c>RecursionError</c>.</li>
286+
<li>In Java, this throws a <c>StackOverflowError</c>.</li>
287+
</ul>
288+
<p>
289+
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.
290+
</p>
291+
<p>
292+
The following Python code demonstrates a situation where a function calls itself indefinitely without a base case, leading to aRecursionError.
293+
</p>
294+
<program xml:id="python-recursion-error" interactive="activecode" language="python">
295+
<code>
296+
def cause_recursion_error():
297+
"""
298+
This function calls itself without a base case, guaranteeing an error.
299+
"""
300+
cause_recursion_error()
301+
302+
# Standard Python entry point
303+
if __name__ == "__main__":
304+
print("Calling the recursive function... this will end in an error!")
305+
306+
# This line starts the infinite recursion.
307+
# Python will stop it and raise a RecursionError automatically.
308+
cause_recursion_error()
309+
</code>
310+
</program>
311+
312+
<p>
313+
The following Java code demonstrates a similar situation, where a method calls itself indefinitely without a base case, leading to a StackOverflowError.
314+
</p>
315+
<program xml:id="java-stack-overflow" interactive="activecode" language="java">
316+
<code>
317+
public class Crash {
318+
public static void causeStackOverflow() {
319+
// This method calls itself endlessly without a stopping condition (a base case).
320+
// Each call adds a new layer to the program's call stack.
321+
// Eventually, the stack runs out of space, causing the error.
322+
causeStackOverflow();
323+
}
324+
// A main method is required to run the program.
325+
public static void main(String[] args) {
326+
System.out.println("Calling the recursive method... this will end in an error!");
327+
// This line starts the infinite recursion.
328+
causeStackOverflow();
329+
}
330+
}
331+
</code>
332+
</program>
333+
</section>
334+
</chapter>

0 commit comments

Comments
 (0)