/* * CS 1723 Section 1T, Summer 1998 * Lecture 9: Trees in Disks * * This program implements an abstract data type that is a little different * from the others we have seen. It resides on disk, and persists even when * the program has terminated and the computer is turned off. */ #include #include #include /* since 0 is a valid block address, use -1 for the "null" block pointer */ #define NULL_BLOCK (-1) /* single node on the disk. this node is bigger depending on what * kind of data is stored */ typedef struct _disknode { int left, right; /* left and right disk blocks */ char data[0]; /* data for this disk block */ } disknode; /* holds data for the ADT */ typedef struct _disktree { char fname[100]; /* file name */ FILE *fptr; /* file pointer */ int datasize, /* size of data in bytes */ blocksize, /* size of data block, including pointers */ file_length; /* number of records in file */ /* this is a function pointer to compare two data blocks. * it should compare the keys of the data, returning < 0 if * the first is less than the second, 0 if they are equal, * > 0 if the second is less than the first */ int (*compar)(void *, void*); disknode *tmpblock, /* holds a disk block */ *tmpblock2; /* holds another disk block */ } disktree; /* open a disk tree */ int open_disktree ( disktree *t, /* reference parameter for ADT */ char fname[], /* name of file */ int datasize, /* size of data item */ /* comparison function */ int (*compar)(void *, void *)) { /* remember the name, datasize and comparison function */ strcpy (t->fname, fname); t->datasize = datasize; t->compar = compar; /* compute size of a block */ t->blocksize = datasize + sizeof (disknode); /* open the file for read and write */ t->fptr = fopen (fname, "r+"); /* error? */ if (!(t->fptr)) { perror (fname); return 1; } /* go to end of file */ fseek (t->fptr, 0L, SEEK_END); /* find out the length of the file */ t->file_length = ftell (t->fptr) / t->blocksize; /* allocate memory for the temp block. * we don't allocate sizeof (disknode) bytes, * we allocate enough for a disknode AND the data */ t->tmpblock = malloc (t->blocksize); t->tmpblock2 = malloc (t->blocksize); return 0; } /* create and open a disk tree */ int create_disktree ( disktree *t, /* reference parameter for ADT */ char fname[], /* name of file */ int datasize, /* size of data item */ /* comparison function */ int (*compar)(void *, void *)) { /* make a file with name fname */ t->fptr = fopen (fname, "w"); if (!(t->fptr)) { perror (fname); return 1; } fclose (t->fptr); /* open the file */ return open_disktree (t, fname, datasize, compar); } /* allocate a new disk block */ int allocate_diskblock (disktree *t) { int p; /* go to the end of the file */ p = t->file_length; fseek (t->fptr, p * t->blocksize, SEEK_SET); /* write a new block, making it a leaf */ t->tmpblock->left = NULL_BLOCK; t->tmpblock->right = NULL_BLOCK; fwrite (t->tmpblock, t->blocksize, 1, t->fptr); t->file_length++; return p; } /* insert data into a disktree */ void insert_disktree (disktree *t, void *k) { int p, q; int c, is_left; /* if this is the first block in file, create a root node */ if (t->file_length == 0) { /* one record in file */ t->file_length = 1; /* copy the data to a temp block */ memcpy (t->tmpblock->data, k, t->datasize); t->tmpblock->left = NULL_BLOCK; t->tmpblock->right = NULL_BLOCK; /* write the block to disk as first in file */ fseek (t->fptr, 0L, SEEK_SET); fwrite (t->tmpblock, t->blocksize, 1, t->fptr); fflush (t->fptr); } else { /* start at the root */ q = 0; while (q != NULL_BLOCK) { /* bring node q into tmpblock2 */ fseek (t->fptr, q * t->blocksize, SEEK_SET); fread (t->tmpblock2, t->blocksize, 1, t->fptr); c = t->compar (k, t->tmpblock2->data); /* remember the parent node */ p = q; if (c <= 0) { /* go left */ q = t->tmpblock2->left; is_left = 1; } else { q = t->tmpblock2->right; is_left = 0; } } /* now, p will be the parent of the new node */ q = allocate_diskblock (t); /* now, tmpblock has the child, tmpblock2 the parent */ /* update the parent and sync with file */ if (is_left) t->tmpblock2->left = q; else t->tmpblock2->right = q; fseek (t->fptr, p * t->blocksize, SEEK_SET); fwrite (t->tmpblock2, t->blocksize, 1, t->fptr); /* make the child, writing it to disk */ memcpy (t->tmpblock->data, k, t->datasize); fseek (t->fptr, q * t->blocksize, SEEK_SET); fwrite (t->tmpblock, t->blocksize, 1, t->fptr); fflush (t->fptr); } } /* traverse the disk tree, calling 'visit()' on each node */ void traverse_disktree (disktree *t, int p, void (*visit)(void *)) { /* if empty tree, return */ if (p == NULL_BLOCK || t->file_length == 0) return; /* fetch node p */ fseek (t->fptr, p * t->blocksize, SEEK_SET); fread (t->tmpblock, t->blocksize, 1, t->fptr); /* go left */ traverse_disktree (t, t->tmpblock->left, visit); /* bring the block back into tmpblock */ fseek (t->fptr, p * t->blocksize, SEEK_SET); fread (t->tmpblock, t->blocksize, 1, t->fptr); /* visit the data */ visit (t->tmpblock->data); /* go right */ traverse_disktree (t, t->tmpblock->right, visit); } /* close a disktree file */ void close_disktree (disktree *t) { fclose (t->fptr); } /* comparison function for integers */ int intcmp (int *a, int *b) { return *a - *b; } /* visit function for integers */ int intvisit (int *a) { printf ("%i\n", *a); } /* little driver program, inserting random numbers into the file */ int main (void) { disktree t; int i, r; open_disktree (&t, "foo", sizeof (int), (int (*)(void*,void*))intcmp); for (i=0; i<1000; i++) { r = rand () % 1000; printf ("%i\n", r); insert_disktree (&t, &r); } traverse_disktree (&t, 0, (void (*)(void*))intvisit); close_disktree (&t); exit (0); }