Question about a solution to excercise 4-13 in K & R

C

Chad

The problem is:
Write a recursive version of the function reverse(s), which reverses
the string s in place.

In "The C Answer Book", Second Edition, near the bottom of page 95, the
authors say
"This is not a good application of recursion". Just for the record, I
did make an attempt at the solution before I broke down and looked at
the solution in the book.

I'm sort of mystified why reversing a string in place recursively is
not a good application of recurson.

Chad
 
R

Richard Bos

Chad said:
The problem is:
Write a recursive version of the function reverse(s), which reverses
the string s in place.

In "The C Answer Book", Second Edition, near the bottom of page 95, the
authors say
"This is not a good application of recursion". Just for the record, I
did make an attempt at the solution before I broke down and looked at
the solution in the book.

I'm sort of mystified why reversing a string in place recursively is
not a good application of recurson.

Because it can easily be done using simple iteration, so recursion is
overkill and could (though it's unlikely) fail due to lack of stack
space where an iterative solution wouldn't.

Richard
 
J

Julian V. Noble

Chad said:
The problem is:
Write a recursive version of the function reverse(s), which reverses
the string s in place.

In "The C Answer Book", Second Edition, near the bottom of page 95, the
authors say
"This is not a good application of recursion". Just for the record, I
did make an attempt at the solution before I broke down and looked at
the solution in the book.

I'm sort of mystified why reversing a string in place recursively is
not a good application of recurson.

Chad

Recursion is bad for string reversal because it is increases the
stack depth to K*n, where n is the length of the string. A much
better method is described in Bentley's "Programming Pearls",
where you set pointers beg, end to the ends of the string, swap
those characters, increment beg and decrement end, then repeat.
You also test whether beg>=end and in that case do not swap but
exit. This algorithm uses n/2 swaps. The loop is iterative and
does not increase the stack.
 
C

Chad

Julian said:
Recursion is bad for string reversal because it is increases the
stack depth to K*n, where n is the length of the string. A much
better method is described in Bentley's "Programming Pearls",
where you set pointers beg, end to the ends of the string, swap
those characters, increment beg and decrement end, then repeat.
You also test whether beg>=end and in that case do not swap but
exit. This algorithm uses n/2 swaps. The loop is iterative and
does not increase the stack.

Don't we also increase the stack depth on a Binary Search Tree
everytime we add a node to the tree? If I remember correctly, we use
recursion to add a node to a Binary Search Tree. How would this be much
more different than using a recursive version of reversing a string in
place?

Chad
 
R

Richard Heathfield

Chad said:

Don't we also increase the stack depth on a Binary Search Tree
everytime we add a node to the tree?

Yes, but.
If I remember correctly, we use
recursion to add a node to a Binary Search Tree.

Yes, but.
How would this be much
more different than using a recursive version of reversing a string in
place?

If you naively add nodes to a binary search tree without taking due account
of the "shape" of that tree, and if you use recursion to add or search for
nodes (which is not a given - IIRC Ben Pfaff's libavl does not use
recursion), then you do run the risk of exceeding the machine's ability to
cope with that kind of call depth. But of course you can be mildly clever,
and balance the tree as you go. Also, if you do choose to use recursion,
you gain in expressive power - it's easy to write a set of recursive
functions to handle a binary tree, but rather harder to write an iterative
set.

Here's a string reversing routine that uses iteration:

void reverse(char *s)
{
if(s && *s)
{
size_t len = strlen(s) - 1;
char *p = s;
char *q = p + len;
char tmp = *p;
while(p < q)
{
*p++ = *q;
*q-- = tmp;
tmp = *p;
}
}
}

Here's a version that uses recursion:

void recurse(char *p, char *q)
{
if(p < q)
{
char tmp = *p;
*p = *q;
*q = tmp;
recurse(p + 1, q - 1);
}
}
void recerse(char *s)
{
if(s && *s)
{
size_t len = strlen(s) - 1;
char *p = s;
char *q = p + len;
recurse(p, q);
}
}

As you can see, I've wrapped the recursive function inside another function
that calculates the length of the string. I'm sure it's possible to avoid
calling strlen over and over again without writing a wrapper, but this was
the quickest, most obvious way I could find. As you can see, the recursive
version is slightly longer and rather less obvious than the iterative
version. And if the string is, say, a description of a DNA strand 5,000,000
bases long, then you're looking at a call depth of 2.5 million - whereas
the iterative version will have a call depth of just 1. And it won't need
the overhead of 2.5 million function calls, either.
 
A

Ark

Richard said:
Chad said:



Yes, but.


Yes, but.


If you naively add nodes to a binary search tree without taking due account
of the "shape" of that tree, and if you use recursion to add or search for
nodes (which is not a given - IIRC Ben Pfaff's libavl does not use
recursion), then you do run the risk of exceeding the machine's ability to
cope with that kind of call depth. But of course you can be mildly clever,
and balance the tree as you go. Also, if you do choose to use recursion,
you gain in expressive power - it's easy to write a set of recursive
functions to handle a binary tree, but rather harder to write an iterative
set.

Here's a string reversing routine that uses iteration:

void reverse(char *s)
{
if(s && *s)
{
size_t len = strlen(s) - 1;
char *p = s;
char *q = p + len;
char tmp = *p;
while(p < q)
{
*p++ = *q;
*q-- = tmp;
tmp = *p;
}
}
}

Here's a version that uses recursion:

void recurse(char *p, char *q)
{
if(p < q)
{
char tmp = *p;
*p = *q;
*q = tmp;
recurse(p + 1, q - 1);
}
}
void recerse(char *s)
{
if(s && *s)
{
size_t len = strlen(s) - 1;
char *p = s;
char *q = p + len;
recurse(p, q);
}
}

As you can see, I've wrapped the recursive function inside another function
that calculates the length of the string. I'm sure it's possible to avoid
calling strlen over and over again without writing a wrapper, but this was
the quickest, most obvious way I could find. As you can see, the recursive
version is slightly longer and rather less obvious than the iterative
version. And if the string is, say, a description of a DNA strand 5,000,000
bases long, then you're looking at a call depth of 2.5 million - whereas
the iterative version will have a call depth of just 1. And it won't need
the overhead of 2.5 million function calls, either.

Isn't it true though that a respectable compiler would optimize the tail
recursion out of existence?
 
R

Richard Heathfield

Ark said:

Isn't it true though that a respectable compiler would optimize the tail
recursion out of existence?

It certainly could, yes. It is not required to by the language spec,
however.
 
E

ena8t8si

Chad said:
The problem is:
Write a recursive version of the function reverse(s), which reverses
the string s in place.

In "The C Answer Book", Second Edition, near the bottom of page 95, the
authors say
"This is not a good application of recursion". Just for the record, I
did make an attempt at the solution before I broke down and looked at
the solution in the book.

I'm sort of mystified why reversing a string in place recursively is
not a good application of recurson.

There's no inherent reason why recursion isn't a good
way to reverse a string. It can be just as easy to
understand, just as easy to program, and perform as
well as an iterative solution.

If there is a reason not to use a recursive solution,
it is that using recursion here will surprise many
programmers who have done most of their programming
in C or C-like languages and don't have much experience
doing functional programming. A recursive solution
isn't a good match to current C culture. That may
be changing, but it is changing only slowly.
 
E

ena8t8si

Julian said:
Recursion is bad for string reversal because it is increases the
stack depth to K*n, where n is the length of the string. A much
better method is described in Bentley's "Programming Pearls",
where you set pointers beg, end to the ends of the string, swap
those characters, increment beg and decrement end, then repeat.
You also test whether beg>=end and in that case do not swap but
exit. This algorithm uses n/2 swaps. The loop is iterative and
does not increase the stack.

Any decent compiler will optimize a tail-recursive
call so a recursive solution can take a constant
amount of stack space just like an iterative solution.
 
E

ena8t8si

Richard said:
Chad said:



Yes, but.


Yes, but.


If you naively add nodes to a binary search tree without taking due account
of the "shape" of that tree, and if you use recursion to add or search for
nodes (which is not a given - IIRC Ben Pfaff's libavl does not use
recursion), then you do run the risk of exceeding the machine's ability to
cope with that kind of call depth. But of course you can be mildly clever,
and balance the tree as you go. Also, if you do choose to use recursion,
you gain in expressive power - it's easy to write a set of recursive
functions to handle a binary tree, but rather harder to write an iterative
set.

Here's a string reversing routine that uses iteration:

void reverse(char *s)
{
if(s && *s)
{
size_t len = strlen(s) - 1;
char *p = s;
char *q = p + len;
char tmp = *p;
while(p < q)
{
*p++ = *q;
*q-- = tmp;
tmp = *p;
}
}
}

Here's a version that uses recursion:

void recurse(char *p, char *q)
{
if(p < q)
{
char tmp = *p;
*p = *q;
*q = tmp;
recurse(p + 1, q - 1);
}
}
void recerse(char *s)
{
if(s && *s)
{
size_t len = strlen(s) - 1;
char *p = s;
char *q = p + len;
recurse(p, q);
}
}

As you can see, I've wrapped the recursive function inside another function
that calculates the length of the string. I'm sure it's possible to avoid
calling strlen over and over again without writing a wrapper, but this was
the quickest, most obvious way I could find. As you can see, the recursive
version is slightly longer and rather less obvious than the iterative
version.

Except you've written the recursive version incompetently.
The temporary variables p and q are necessary in the
iterative version, but most competent functional
programmers wouldn't use them in recerse(). A more
appropriate comparison would look like this:

void reverse(char *s)
{
if(s && *s){
size_t len = strlen(s) - 1;
char *p = s;
char *q = p + len;
char tmp;
while(p < q) tmp = *p, *p++ = *q, *q-- = tmp;
}
}

void recurse(char *p, char *q)
{
char tmp;
if(p < q) tmp = *p, *p = *q, *p = tmp, recurse(p+1,q-1);
}
void recerse(char *s)
{
if(s && *s) recurse(s, s+strlen(s)-1);
}
And if the string is, say, a description of a DNA strand 5,000,000
bases long, then you're looking at a call depth of 2.5 million - whereas
the iterative version will have a call depth of just 1. And it won't need
the overhead of 2.5 million function calls, either.

I'm not looking at a call depth of 2.5 million;
the compilers I use will optimize the tail recursive
call. And, I suspect, so will compilers that you use.
 
E

ena8t8si

Richard said:
Ark said:



It certainly could, yes. It is not required to by the language spec,
however.

That's true, but neither does the language spec require
that a while loop be compiled as a jump rather than
a recursive call.
 
R

Richard Heathfield

(e-mail address removed) said:
Richard Heathfield wrote:


Except you've written the recursive version incompetently.

No, I've written it clearly. Had I written it less clearly, it would have
indeed have been shorter, but it would also have been even less obvious.
 
A

Andrew Poelstra

That's true, but neither does the language spec require
that a while loop be compiled as a jump rather than
a recursive call.

Clever argument, but it /does/ require that code behave as it (meaning
both the Standard and the code in question) say it does, and the simplest
solution is for the compiler to compile loops as loops, and recursive
functions as recursive functions.

Still not guaranteed, but a lot more likely than it is for a given
compiler to optimize tail recursion.
 
E

ena8t8si

Richard said:
(e-mail address removed) said:


No, I've written it clearly. Had I written it less clearly, it would have
indeed have been shorter, but it would also have been even less obvious.

You're entitled to your opinion of what's clear, just
as other people are entitled to their opinion of what's
competent. I expressed an opinion that I believe is
representative of the opinion of practitioners in the
functional programming community.
 
E

ena8t8si

Andrew said:
Clever argument, but it /does/ require that code behave as it (meaning
both the Standard and the code in question) say it does, and the simplest
solution is for the compiler to compile loops as loops, and recursive
functions as recursive functions.

It wasn't an argument, just an observation.
Still not guaranteed, but a lot more likely than it is for a given
compiler to optimize tail recursion.

Yes, and that comment is on point. I think it's better
to take issues straight on, rather than arguing by innuendo.
 
R

Richard Heathfield

(e-mail address removed) said:
You're entitled to your opinion of what's clear, just
as other people are entitled to their opinion of what's
competent.

Quite so. Incidentally, I don't doubt /your/ competence - but you could use
a class in diplomacy. (Probably so could I.)
I expressed an opinion that I believe is
representative of the opinion of practitioners in the
functional programming community.

C is not a functional programming language. (I can imagine a few people
making hay with that statement, but I know /you/ know what I mean by
"functional" in this context.)
 
K

Keith Thompson

Richard Heathfield said:
(e-mail address removed) said: [...]
You're entitled to your opinion of what's clear, just
as other people are entitled to their opinion of what's
competent.

Quite so. Incidentally, I don't doubt /your/ competence - but you could use
a class in diplomacy. (Probably so could I.)

But who around here could teach it? :cool:}
 
R

Richard Heathfield

Keith Thompson said:
Richard Heathfield said:
(e-mail address removed) said: [...]
You're entitled to your opinion of what's clear, just
as other people are entitled to their opinion of what's
competent.

Quite so. Incidentally, I don't doubt /your/ competence - but you could
use a class in diplomacy. (Probably so could I.)

But who around here could teach it? :cool:}

Chris Torek as head of department, Steve Summit as principal lecturer, and
Stefan Wilms (if he ever manages to find his way back here after his
somewhat extended lunch break) as TA.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,534
Members
45,008
Latest member
Rahul737

Latest Threads

Top