简体   繁体   中英

Quit recursive function when a dynamic condition is met

Using the function from Generate all sequences of bits within Hamming distance t :

void magic(char* str, int i, int changesLeft) {
        if (changesLeft == 0) {
                printf("%s\n", str);
                return;
        }
        if (i < 0) return;
        // flip current bit
        str[i] = str[i] == '0' ? '1' : '0';
        magic(str, i-1, changesLeft-1);
        // or don't flip it (flip it again to undo)
        str[i] = str[i] == '0' ? '1' : '0';
        magic(str, i-1, changesLeft);
}

I would like to quit the recursive function and return to the caller function when a certain condition occurs (if it does). So it's like my recursive function is hearing voices that might tell her to quit!

It only happens after str is printed, here:

if (changesLeft == 0) {
    printf("%s\n", str);
    int quit_now = voices(str);
    return;
}

How to do this (stop unfolding the recursion and return to the function caller)?


Attempt:

if (i < 0 || quit_now == 1) return;

just seems to block the execution and never end!

PS - I am interested even in old methodologies.

A simple solution, given your function currently has no return value, is to use it to indicate whether that terminating condition was met. Then you can use it to immediately exit all recursive calls if the result becomes true.

Not sure if I'm capturing your expected logic correctly here, but the intuitive approach would be something like this:

int magic(char* str, int i, int changesLeft) {
    int result;
    if (changesLeft == 0) {
            printf("%s\n", str);
            return voices(str);
    }
    if (i < 0) return 0;

    // flip current bit
    str[i] = str[i] == '0' ? '1' : '0';
    result = magic(str, i-1, changesLeft-1);

    if( !result ) {
        // or don't flip it (flip it again to undo)
        str[i] = str[i] == '0' ? '1' : '0';
        result = magic(str, i-1, changesLeft);
    }

    return result;
}

The magic function calls itself recursively in two places. So in each of those places, you need to check your exit condition. The answer given by paddy details this.

An alternative for immediate unwinding of the stack is to use setjmp and longjmp which can function as a non-local goto .

jmp_buf magic_buf;

void magic_int(char* str, int i, int changesLeft) {
        if (changesLeft == 0) {
                printf("%s\n", str);
                if (voices(str)) {
                    longjmp(magic_buf, 1);
                }
                return;
        }
        if (i < 0) return;
        // flip current bit
        str[i] = str[i] == '0' ? '1' : '0';
        magic_int(str, i-1, changesLeft-1);
        // or don't flip it (flip it again to undo)
        str[i] = str[i] == '0' ? '1' : '0';
        magic_int(str, i-1, changesLeft);
}

void magic(char* str, int i, int changesLeft) {
    if (!setjmp(magic_buf)) {
        magic(str, i, changesLeft);
    }
}

The setjmp function returns 0 when called directly. When longjmp is called, it is the setjmp function that actually returns, and the return value is the second parameter given to longjmp .

Here, we have a wrapper function which calls setjmp . This sets the jump point for when longjmp is called. Then the recursive function is called. Later, when the recursive function "hears voices" telling it to quit now , it calls longjmp which immediately goes directly to the corresponding setjmp call.

These functions are specified in C99 as well as POSIX, so a POSIX conforming system (ie Linux) should still have these available in C89 mode.

If you were to do this in C++, the preferred method would be to throw an exception in the recursive function and catch it in the wrapper function.

struct magic_voices {
    int rval;
};

void magic_int(char* str, int i, int changesLeft) {
        if (changesLeft == 0) {
                printf("%s\n", str);
                int rval = voices(str);
                if (rval) {
                    struct magic_voices ex;
                    ex.rval = rval;
                    throw ex;
                }
                return;
        }
        if (i < 0) return;
        // flip current bit
        str[i] = str[i] == '0' ? '1' : '0';
        magic_int(str, i-1, changesLeft-1);
        // or don't flip it (flip it again to undo)
        str[i] = str[i] == '0' ? '1' : '0';
        magic_int(str, i-1, changesLeft);
}

void magic(char* str, int i, int changesLeft) {
    try {
        magic(str, i, changesLeft);
    } catch (struct magic_voices ex) {
        printf("rval=%d\n", ex.rval);
    }
}

To put it in the simplest form, you could do something like this:

void foo(bool & ret) {
  // doStuff...
  if (ret) return;
  foo(ret);
  // doStuff...
  if (ret) return;
  foo(ret);
}

Then you initiate the recursion:

bool ret = false;
foo(ret);

In your case you can interupt the recursion by

if (!changesLeft) {
  printf("%s\n", str);
  ret = true;
  return;
}

Setting to true will get you out of the entire call tree.

You can do it in C as well, just use a pointer rather than a reference.

This is a non-recursive variant. Basically, it generates all increasing sequences 0 <= a[0] < ... < a[dist-1] < strlen(num) , and reverts bits at corresponding indices.

void print(const char* num, const vector<int>& a) {
    size_t k = 0, n = strlen(num);
    for (size_t i = 0; i < n; ++i)
        if (k < a.size() && a[k] == i) {
            cout << (num[i] == '0') ? '1' : '0';
            ++k;
        }
        else
            cout << num[i];
    cout << endl;
}

void hamming(const char* num, size_t dist) {
    assert(dist > 0);
    vector<int> a(dist);
    size_t k = 0, n = strlen(num);
    a[k] = -1;
    while (true)
        if (++a[k] > n - dist + k)
            if (k == 0)
                return;
            else {
                --k;
                continue;
            }
        else
            if (k == dist - 1) {
                print(num, a);
                // conditional return here
            }
            else {
                a[k+1] = a[k];
                ++k;
            }
}

Which can be used like this:

int main()
{
    hamming("0000", 2);
    /* output:
       1100
       1010
       1001
       0110
       0101
       0011
    */
}

PS Thanks to @ruakh for mentioning a missing optimization in the while - if condition.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM