views:

242

answers:

6

I have the following simple method for doing a complete search of a binary tree. I can definitely tell it can be simplified, but it's not coming to me.

bool Node::lookup(int d)
{
    if (data==d)
    {
        return true;
    }
    else
    {
        if (left != NULL && right != NULL)
        {
            return left->lookup(d) && right->lookup(d);
        }
        else if (left != NULL)
        {
            return left->lookup(d);
        }
        else if (right != NULL)
        {
            return right->lookup(d);
        }
        else
        {
            return false;
        }
    }
}
+10  A: 
bool Node::lookup(int d)
{
    return (data==d)
        || ((NULL != left) && left->lookup(d))
        || ((NULL != right) && right->lookup(d));
}

Takes advantage of short-circuit behaviour of && and ||.

Note that this isn't exactly equivalent to your code, but I imagine that this is probably the behaviour you actually want. (Your original code returns false is both children exist and d isn't in both of them.)

Oli Charlesworth
That's definitely what I was thinking of. Thanks!
btree
Actually, I'm not sure this is correct. Hold on...
Oli Charlesworth
I think it's fixed now.
Oli Charlesworth
@Oli, technically, that's not the same code. But it's probably what the OP actually wanted anyway.
MSN
I recommend renaming the member function from `lookup` to `contains`, which sounds like something that you'd answer "true" or "false" to, unless this happens to be a tree of booleans keyed by integers.
Jeremy W. Sherman
Why the Yoda conditions... :P
dreamlax
@MSN: indeed...
Oli Charlesworth
@dreamlax: force of habit
Oli Charlesworth
Is this any faster? I would prefer clear code to hard to understand at first glance fast code
gbrandt
@gbrandt: Probably not, but the OP didn't ask for "faster". I agree with you, but on this occasion, I'd argue that the succinct version is actually easier to read than the original (with its extraneous conditions and function calls).
Oli Charlesworth
as others mentioned, this isn't equivalent to the original code (it should return false if both left and right exist, and d is not in BOTH of them)
Will
This is definitely a situation where I'd prefer `left` to `NULL != left`, (`left != 0` ranks between the two).
Steve Jessop
A: 

It cannot be made significantly faster. However, I'd write it as

bool Node::lookup(int d){
    if (!this) // a little bit unorthodox, but much more elegant that testing before call
        return false;
    return (data == d) || left->lookup(d) || right->lookup(d);
}

EDIT: It seems that no one likes this approach. But why? Except for the case of virtual functions it provides better encapsulation than the standard one:

Node* tree; 
if (tree->lookup(i)) // we encapsulate in the Node class the information that an empty tree must return false
    printf("found!\n");

Node* tree; 
if (tree && tree->lookup(i)) // we have to know in the outside that a NULL tree shall return false
    printf("found!\n");

The only way it could produce UB is when the compiler will consider that this cannot be NULL and will skip the test. This will not happen because there is quite enough code in the wild that use this kind of checking. Also, in plain C it's common practice.

ruslik
This is more than unorthodox; this in undefined behaviour. What if these are virtual functions?
Oli Charlesworth
-1 not a great pattern to de reference null to a specific type and use it as an object.
Greg Domjan
-1 That's completely broken.
meagar
@Ruslik: It's not a matter of *sometimes* producing UB; this *is* UB. The compiler doesn't have to guarantee anything about dereferencing NULL pointers, much less invoking member functions on them, and certainly not virtual functions on them. This is a very bad idea...
Oli Charlesworth
A: 

You could get rid of the nested ifs and else ifs

bool Node::lookup(int d)
{
    if (data==d)
        return true;
    if (left != NULL && right != NULL)
        return left->lookup(d) && right->lookup(d);
    if (left != NULL)
        return left->lookup(d);
    if (right != NULL)
        return right->lookup(d);
    return false;
}
Sebastian N.
+1  A: 

If you want a terse version of the code in the question, you can do this:

bool Node::lookup(int d) 
{ 
    return 
        (data==d) || 
        (
            (right || left) &&
            (!left || left->lookup(d)) &&
            (!right || right->lookup(d))
        ); 
} 

But I suspect you want lookup to return true if d matches in either left or right, not if it matches in both left and right

MSN
Greg Domjan
@Greg right you are! Fixed.
MSN
A: 

Ternary operator shortens things nicely:

bool Node::lookup(int d)
{
  if (data==d)
    return true;

  if (left == right)
    return left == null ? false : left->lookup(d) && right->lookup(d);   
  return left == null ? right->lookup(d) : left->lookup(d);
}
meagar
A: 
bool Node::lookup(int d)
{
    return (data == d)
       || (left && left->lookup(d))
       || (right && right->lookup(d));    
}

I'm making an assumption that you have a logic error in your originial, since it requires that if the left and right child are non-null that the target be found in both branches.

JohnMcG