/* sbernstein.c: Construct a bijection from injections using a constructive
proof of the Schroder-Bernstein Theorem.
By Terry R. McConnell
Compile: cc -o sbernstein sbernstein.c
For linking only the sbernstein subroutine, add -DNO_MAIN
The Schroder-Bernstein Theorem says that if A and B are sets and
if one-one maps F:X -> Y and G:Y -> X exist, then there is a one-one and
onto map (bijection) T: X -> Y, hence X and Y have the same cardinality. The
purpose of this program is to illustrate one particular constructive proof
of the SB theorem. This proof is due to M. Hellman ( Amer. Math. Monthly,
68(1961), p. 770.) Here is a sketch with hopefully enough detail to make
the programming clear:
F
X | -----------------------------> | Y
| |
G(Y) + |
+ |
. *
A . * F(X)
. *
+ *
+ |
| |
| |
<-------------------------------
G
Let f denote the composition G inverse followed by F inverse, which maps
the "inner" region, A = F(G(X)), of X (marked with ...) to X. One iterates this
map until the resulting point leaves A (if it ever does.) Let z denote the
first point outside of A so obtained. Then T(x) is defined to be G-inverse
of x if z lands in the range of G (marked with +++). Otherwise (and this
includes the case that the iteration never leaves A) T(x) = x.
The theorem and the construction are not at all interesting for
finite sets, so we consider for our illustration only functions from the whole
numbers (natural numbers + 0) to the whole numbers. For external linkage,
the main routine of this file is called sbernstein. (See declaration below.)
It returns a pointer to a function of int returning int. You supply it with
pointers to implementations of F, G and their inverses. These must be functions
taking a single integer argument and returning an int. In addition, the
inverses must return -1 when given an argument not in the range of the original
functions. A small main routine for testing is included below, so this
file can be compiled and run as is, with F(x) = 2x and G(y) = 3y.
Note that the above sketched construction is not necessarily
EFFECTIVE, i.e, it is possible that it may not reach a decision in finite
time (e.g, if the iteration never terminates.) Accordingly, this program
really only constructs an approximation to the desired bijection, although
it most cases it will be exact. The defined LOOP_LIMIT below
indicates how many times to iterate f before deciding that the sequence will
stay forever in A. (With the default test functions x = 0 is a fixed point
of the iteration, so LOOP_LIMIT will be exceeded when computing the
bijection for x = 0. A notice of this fact is printed on the error stream.)
Open Question (well, open for me): is there a proof of SB that yields an
effective procedure in our context?
*/
#include
#include
#define LOOP_LIMIT 100
/* sbernstein:
Returns a pointer to a bijective function from the whole numbers to
themselves. The functions f and g are one to one functions on the whole
numbers with whole number values, but are not necessarily onto. The
function returned is built using an algorithm based upon a proof of
the Schroder-Bernstein theorem. One must pass also pointers to
implementations of the inverses of f and g. These are required to return
-1 when evaluated at a number which does not belong to their domains,
i.e., to the ranges of f and g respectively
The value returned is actually just a pointer to the_function, which
does the real work using stored pointers to the passed in functions with
the integer arg it is passed. All sbernstein does is set up these
static pointers.
*/
static int (*F)(int);
static int (*G)(int);
static int (*FINV)(int);
static int (*GINV)(int);
static int the_function(int);
int(*sbernstein(int (*f)(int), int (*g)(int), int (*f_inv)(int),
int (*g_inv)(int)))(int)
{
/* store pointers to implementations for use by the_function */
F = f;
G = g;
FINV = f_inv;
GINV = g_inv;
return &the_function;
}
/* As explained above, this actually implements the algorithm to compute
the bijection for the user-supplied arg. It uses stored pointers to
implementations of the functions F, G and their inverses. */
static int the_function(int x)
{
int n,m,i;
m = n = x;
i = 0;
while((n=FINV(GINV(n)))!=-1){ /* Loop until we leave A. See above */
m = n;
if(i++ >= LOOP_LIMIT){
fprintf(stderr,"Loop limit exceeded on arg %d. Function be only approximate.\n",x);
break;
}
}
if(i >= LOOP_LIMIT)return F(x); /* Never left A */
if(GINV(m)==-1)return F(x); /* Left A outside range of G */
return GINV(x); /* Left A inside range of G */
}
/* test platform */
static int test_f(int);
static int test_g(int);
static int test_finv(int);
static int test_ginv(int);
/* This platform prints a table of values with this many rows: */
#define ROWS 20
#ifndef NO_MAIN
int main()
{
int (*T)(int);
int i=0;
T = sbernstein(&test_f,&test_f,&test_finv,&test_ginv);
printf("%s\t%s\t%s\t%s\n","n","f","g","bijection");
printf("---------------------------------\n");
while(i