A recursive example of dashes
¶
Review iterative dashes
¶
You may recall a method we wrote called dashes
which took a String
parameter and returned a new String
that was equivalent to the given parameter with dashes inserted between every character. For example, a call to dashes("pupper")
would return the String
"p-u-p-p-e-r"
public static String dashes(String s) {
if (s.isEmpty()) {
return s;
} else {
String result = "" + s.charAt(0);
for (int i = 1; i < s.length(); i++) {
result += "-" + s.charAt(i);
}
return result;
}
}
Reviewing the components of recursive methods¶
Before we dive into the details of solving dashes
recursively, we should first remind ourselves the components of a recursive solution. We can recall that a recursive method deals with only one small part of a problem before passing on the rest of the problem to other method calls. We also can recall that recursive solutions are made up of a combination of base cases and recursive cases. It may help to review this brief outline of things needed to wrtie recursive methods:
Outline for writing recursive methods:
- Figure out the base case(s). These tend to be the simplest, most basic cases of the problem we can solve immediately. It may help to think of them as the “bottom” or “end” of the recursive call chain (hence the name “base case”)
- Figure out what is the smallest “part” or “section” of the problem we should break off in the recursive case
- Deal with the recursive case(s). This usually involves solving the small part of the problem that we broke off in the previous step and combining it with the result of a recursive call to the same method on the smaller sub-problem
Implementing dashes
recursively¶
Now that we’ve reviewed the components of recursive methods, let’s keep them in mind as we implement dashes
recursively. To start, we will write the method stub
// Returns a String which is the result of adding dashes
// in between the characters of the given String
public static String dashes(String s) {
//TODO: implement this method
}
We can begin by figuring out what our base case should be. What’s the simplest problem we can solve immediately? The case where we are passed the empty String
, in which we want to return an empty String
(we can’t insert dashes in-between an empty String
). Let’s add our base case
// Returns a String which is the result of adding dashes
// in between the characters of the given String
public static String dashes(String s) {
if (s.isEmpty()) {
return "";
} else {
//TODO: implement the recursive case
}
}
What do we want to do if our String
is not empty? This is where we begin to write the recursive case. Remember, our recursive case should just do the smallest amount of work to solve part of the problem before passing off the rest of the work to other recursive calls (we don’t want to do too much in each method call!). We need to somehow add dashes to our String
, so a small part of the problem we could do is break off one character, add one dash, and then combine that with a dashed version of the rest of the String
. Wouldn’t getting a dashed version of the rest of the String
be easy if we had a method to add dashes to a String
? Thankfully, we are writing one so we can call it recursively to perform this last task! Below is a good example of a recursive case
// Returns a String which is the result of adding dashes
// in between the characters of the given String
public static String dashes(String s) {
if (s.isEmpty()) {
return "";
} else {
return s.charAt(0) + "-" + dashes(s.substring(1));
}
}
s.charAt(0)
breaks off the first character and then the + "-"
adds a dash. Then we need to figure out how to add dashes in between the characters of the rest of the given String
(s.substring(1)
). If we struggle to figure out exactly how to use our recursive method, it can sometimes help to look at the method comment in order to find the sub-problem to call it with. Our method comment states Returns a String which is the result of adding dashes
in between the characters of the given String
s.substring(1)
!!! Rather than iteratively implementing all of that functionality in our recursive case, we can use the recursive call dashes(s.substring(1))
to achieve the desired result. A reminder of the importance of testing code¶
Now that we have an attempt at a solution, we should test it on some sample inputs to make sure it is working as expected. In reality, we want to test on a wide variety of cases, such as
- The empty
String
- A one character
String
- An even-length
String
- An odd-length
String
- Any other cases specific/special to our problem’s functionality
For now, let’s just test our solution to dashes
on the empty String
, a String
with one character "P"
, and a slightly longer example "pup"
.
For the empty String
our solution would enter the base case and correctly return the empty String
.
dashes("");
is empty, so return ""
For our one character String
, we would want to return exactly that character (you can’t have dashes between one character). Let’s trace through what would happen
dashes("P")
is not empty, so execute recursive case
compute dashes("")
| is empty, so return ""
return firstChar + "-" + rest ('P' + "-" + "" = "P-")
String
with one character our output has an extra dash added at the end. We can see why, as a String
with one character isn’t the empty String
, so we enter our recursive case and add a dash followed by a call to dashes("")
. How can we restructure our code to account for this case? It seems we need a new case for a one-character String
such as the following public static String dashes(String s) {
if (s.isEmpty()) {
return "";
} else if (s.length() == 1) {
return s;
} else {
return s.charAt(0) + "-" + dashes(s.substring(1));
}
}
dashes("");
is empty, so return ""
String
still works! dashes("P")
is not empty but length is 1 so return same String ("P")
String
. Let’s go ahead and try our last test case. dashes("pup")
not empty and length > 1 so execute last case
compute dashes("up")
| not empty and length > 1 so execute last case
| compute dashes("p")
| | not empty but length == 1 so return same String
| return firstChar + "-" + rest ('u' + "-" + "p" = "u-p")
return firstChar + "-" + rest ('p' + "-" + "u-p" = "p-u-p")
Redundant cases¶
If we look at the first two cases, however, we can’t quite start celebrating our working solution. If we are given the empty String
, we end up returning the empty String
, and if we are given a one-character String
, we return that same one-character String
. In both cases, we end up returning the same String
we were passed. When writing methods, it is poor code quality to write code with redundant/duplicate cases such as the method written above. Rather than having the cases be separate, let’s go ahead and combine the two cases in order to produce a stylistically-better version of our solution
public static String dashes(String s) {
if (s.length() <= 1) {
return s;
} else {
return s.charAt(0) + "-" + dashes(s.substring(1));
}
}
Recap¶
As a recap, some important takeaways for writing recursive solutions are:
- Figuring out the base case, which is often the simplest case of our problem that we can solve immediately.
- Find the small part of the problem we will break off and solve in the recursive case. Solve this small problem and then use a recursive call to solve the rest of the problem.
- Test recursive solutions just like we would test any other code!!! Testing is super important in programming, as we can often catch small errors that we overlook when writing methods. Often writing an incorrect solution is part of the problem solving process, and there is no harm in writing a wrong solution the first time around as long as we test and fix it!
Practice
Try out this problem and this other problem for more practice with recursion.