Thread: Database in C and thoughts about input.

  1. #1
    Registered User
    Join Date
    Sep 2011
    Location
    Athens , Greece
    Posts
    357

    Database in C and thoughts about input.

    I am reading from King's book on Page 394-395

    There is a C database program which it implemented with arrays of structures

    iventory.c

    Code:
    #include<stdio.h>
    #include "readline.h"
    
    #define NAME_LEN 25
    #define MAX_PARTS 100
    
    struct part {
    	int number;
    	char name[NAME_LEN+1];
    	int on_hand;
    } inventory[MAX_PARTS];
    
    int num_parts = 0 ;
    
    int find_part(int number);
    void insert(void);
    void search(void);
    void update(void);
    void print(void);
    
    int main(void)
    {
    	char code;
    	
    	for(;;) {
    		printf("Enter operation code: ");
    		scanf(" %c" , &code);
    		while( getchar() !='\n')
    		;
    		
    		switch(code) {
    			case 'i': insert();
    					  break;
    		    case 's': search();
    					  break;
    			case 'u': update();
    			          break;
    		    case 'p': print();
    			          break;
    			case 'q': return 0;
    			default:  printf("Illegal code\n");
    		}
    		printf("\n");
    	}
    }
    
    int find_part(int number)
    {
    	int i;
    	
    	for(i=0; i<num_parts; i++)
    		if( inventory[i].number == number) 
    			return i;
    	
    	return -1;
    }
    void insert(void)
    {
    	int part_number;
    	
    	if (num_parts == MAX_PARTS) {
    		printf("Database is full; Can't add more parts. \n");
    		return;
    	}
    	
    	printf("Enter part number: ");
    	scanf("%d" , &part_number);
    	
    	if( find_part(part_number) >=0 ) {
    		printf("Part already exists. \n");
    		return;
    	}
    	
    	inventory[num_parts].number = part_number;
    	printf("Enter part name: ");
    	read_line(inventory[num_parts].name , NAME_LEN);
    	puts(inventory[num_parts].name);
    	printf("Enter quantity on hand: ");
    	scanf("%d" , &inventory[num_parts].on_hand);
    	
    	num_parts++;
    	
    }
    	
    void search(void)
    {
    	int i , number;
    	
    	printf("Enter part number: ");
    	scanf("%d" , &number);
    	
    	i = find_part(number);
    	
    	if( i >=0 ){
    		printf(" Part name : %s \n " , inventory[i].name);
    		printf(" Quantity on hand: %d \n" , inventory[i].on_hand);
    	} else
    		printf("Part not found. \n ");
    	}
    	
    void update(void) 
    {
    	int i , number , change;
    	
    	printf(" Enter part number: ");
    	scanf("%d" , &number);
    	
    	i= find_part(number);
    	if(i>=0) {
    		printf(" Enter change in quantity on hand: ");
    		scanf("%d" , &change);
    		inventory[i].on_hand += change;
    	} else
    		printf("Part not found");
    	}
    	
    void print(void)
    {
    	int i;
    	
    	printf(" Part Number  Part Name              " 
    	"Quantity on Hand\n");
    	for(i=0; i < num_parts; i++)
    		printf("%7d        %-25s%11d\n" , inventory[i].number , inventory[i].name , inventory[i].on_hand);
    	}
    readline.h

    Code:
    #ifndef READLINE_H
    #define READLINE_H
    
    int read_line(char str[] , int n);
    
    #endif
    and

    readline.c

    Code:
    #include<ctype.h>
    #include<stdio.h>
    #include "readline.h"
    
    int read_line( char str[] , int n)
    {
    	int ch , i=0;
    	
    	while( isspace( ch = getchar() ))
    	;
    	
    	while( ch!= '\n' && ch != EOF) {
    	
    	if( i < n)
    	str[i++] = ch;
    	ch = getchar();
    	
    	}
    	
    	str[i] ='\0';
    	return i;
    }
    I can understood this program I think is very easy but I have some queries about the read_line function.... King writes that we must use this edition of read_line instead of this :

    Code:
    /*#include<stdio.h>
    #include "readline.h"
    
    int read_char(void) 
    {
    	int ch = getchar();
    	
    	if( ch == '\n' || ch == '\t' )
    		return ' ';
    
          return ch;
    }
    
    void read_line(char *word , int len)
    {
    	int ch , pos = 0; 
    	while( (ch = read_char() ) == ' ') 
           ; 
    
    	while( ch != ' ' && ch != EOF ) {
    	if(pos < len)
    		word[pos++] = ch;  
            ch = read_char();
    	}
    	word[pos] = '\0';  // Το κάνει string.
    }*/
    Why? Because if we have an input like

    Code:
    Enter part number: 528
    Enter part name: Disk Drive
    If we try to read the part name using the previous read_line we will encounter the '\n' character immediately and stop reading.This problem is common when numerical input is followed by character input.Our solution will be to write a version of read_line that skips white space characters before it begins storing characters.Not only will this solve the new-line problem but it also allows us to avoid storing any blanks that precede the part name

    I didn't see any problem with '\n' since the previous edition of read_line converts it to space character ... What is stopped? I only see a problem when Disk Drive string encountered the space character (between Disk and Drive). I can't understand why King explains it with this way.... I have traced the function read_line ...

    English is not my native language and sometimes I face difficulties with the English books.
    Last edited by Mr.Lnx; 05-28-2013 at 02:14 PM.

  2. #2
    SAMARAS std10093's Avatar
    Join Date
    Jan 2011
    Location
    Nice, France
    Posts
    2,694
    Input My name is Samaras. and see what happens.

    As for the Greek comment in line 26, this does not convert it to string, but it terminates the string with the proper string terminator

    Tip:
    Focus on this piece of code
    Code:
    while( ch != ' ' && ch != EOF ) {
        if(pos < len)
            word[pos++] = ch;  
            ch = read_char();
        }
    from the method you are not suggested to use. What does the conditions of the while are?
    Last edited by std10093; 05-28-2013 at 02:45 PM.
    Code - functions and small libraries I use


    It’s 2014 and I still use printf() for debugging.


    "Programs must be written for people to read, and only incidentally for machines to execute. " —Harold Abelson

  3. #3
    Registered User
    Join Date
    May 2012
    Posts
    505
    Quote Originally Posted by Mr.Lnx View Post
    I am reading from King's book on Page 394-395

    There is a C database program which it implemented with arrays of structures

    I didn't see any problem with '\n' since the previous edition of read_line converts it to space character ... What is stopped? I only see a problem when Disk Drive string encountered the space character (between Disk and Drive). I can't understand why King explains it with this way.... I have traced the function read_line ...
    I don't understand it either.
    stdin is line buffered, which means that when you call a reading function, the program will hang until the user enters a whole line. This often creates problems.
    But both functions are skipping preceding whitespace. So the first version should be fine.
    I'm the author of MiniBasic: How to write a script interpreter and Basic Algorithms
    Visit my website for lots of associated C programming resources.
    https://github.com/MalcolmMcLean


  4. #4
    SAMARAS std10093's Avatar
    Join Date
    Jan 2011
    Location
    Nice, France
    Posts
    2,694
    @Malcolm, read again Mr.Lnx's post
    Code - functions and small libraries I use


    It’s 2014 and I still use printf() for debugging.


    "Programs must be written for people to read, and only incidentally for machines to execute. " —Harold Abelson

  5. #5
    Registered User migf1's Avatar
    Join Date
    May 2013
    Location
    Athens, Greece
    Posts
    385
    Quote Originally Posted by Mr.Lnx View Post
    ...
    I didn't see any problem with '\n' since the previous edition of read_line converts it to space character ... What is stopped? I only see a problem when Disk Drive string encountered the space character (between Disk and Drive). I can't understand why King explains it with this way.... I have traced the function read_line ...
    I don't see the problem with '\n' in this particular case either. There is indeed a problem with the space between "Disk" and "Drive" where (if I glanced it correctly) the space is consumed as a sentinel but "Drive" remains in the buffer, thus feeding the next scanf() that tries to read the "quantity on hand" and fails.

    The improved version of get_line() happily accepts intermediate spaces, so it works correctly.

    But yes, I don't see King's point either the way he puts it for the faulty version of get_line(). Perhaps it's corrected in the book's errata.
    Last edited by migf1; 05-28-2013 at 03:07 PM.

  6. #6
    Registered User
    Join Date
    Sep 2011
    Location
    Athens , Greece
    Posts
    357
    Yes that is right migf1.... And there is no any correction in the book's erata as I looked before.

    I deeply believe there is no any problem with '\n' as very clearly writes... I read again and again but I can't understand why ...

    @std10093

    I meant that makes the string word a valid C-string since it assigns the null-terminated at the current last position.

    EDIT: OOps sorry... the function read_line is this

    Code:
    int read_line( char str[] , int n )
    {
             int ch , i = 0;
    
             while( (ch = getchar() ) !='\n')
             if( i < n)
                str[i++] = ch;  
    	str[i]='\0';
    	return i;
    }
    I looked at 13.3 section few minutes ago .... it uses this as example because it has int return type. I had in my mind the function which comes from 15 chapter (Writing large programs)
    I am really sorry for the confusion. Next time I will be more careful.
    Last edited by Mr.Lnx; 05-28-2013 at 04:02 PM.

  7. #7
    Registered User migf1's Avatar
    Join Date
    May 2013
    Location
    Athens, Greece
    Posts
    385
    Quote Originally Posted by Mr.Lnx View Post
    ..
    EDIT: OOps sorry... the function read_line is this ...
    ...
    So that's the faulty version. Now King makes perfect sense

  8. #8
    Registered User
    Join Date
    May 2012
    Posts
    1,066
    [Edit]OP did find out himself that he looked at the wrong code[/Edit]
    Last edited by AndiPersti; 05-28-2013 at 04:21 PM.

  9. #9
    Registered User
    Join Date
    Sep 2011
    Location
    Athens , Greece
    Posts
    357
    @Andi in Greece we say "It occurs and to the best families...when we face a strange fault or a fault comes from stupidity"

    Why does King make read_line to have return type??? Where the number of characters name is valuable?
    Last edited by Mr.Lnx; 05-29-2013 at 01:04 PM.

  10. #10
    Registered User
    Join Date
    May 2012
    Posts
    1,066
    Quote Originally Posted by Mr.Lnx View Post
    Why does King make read_line to have return type??? Where the number of characters name is valuable?
    Why not?
    It doesn't cost anything to return the length of the string and in some situations you may need it.

    Bye, Andreas

  11. #11
    Registered User
    Join Date
    Sep 2011
    Location
    Athens , Greece
    Posts
    357
    Hello again. I am writing here again because I want to do some exercises related with this code (Database code) ...

    The first exercise asks make inventory and num_parts variables which are globals local to the main function of the program.

    I have done this and I want to discuss it ... My way is to pass the array of structures and num_parts variables in each function and I will increase num_parts variable each time I would call the function insert for example :

    Ideone.com | Online C Compiler & Debugging Tool

    another solution is to pass array of structures and num_parts variables in each function but this time you will pass the address of num_parts and we will manipulate it as a pointer in insert function so the changes will be stay for example

    Ideone.com | Online C Compiler & Debugging Tool

    My simple question is which of two is better.... is there any fault in my first way with the num_parts++; inside i label of switch?

    and do you have any idea how can I make the elements sorted by a part number??? I want to sort the elements in inv array by a part number .... I think I can use bubblesort but I must save the median element ...
    Last edited by Mr.Lnx; 06-21-2013 at 05:44 AM.

  12. #12
    Registered User migf1's Avatar
    Join Date
    May 2013
    Location
    Athens, Greece
    Posts
    385
    Either way is fine, imo. I prefer the 2nd one though (passing the address of num_parts to functions that modify it) because it kinda hides away the internals from the main().

    Even better, I would use a struct for inventory (instead of an array) part of which would be the array of parts and the num_parts counter. This way I would pass just the address of the inventory into the functions.

  13. #13
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by migf1 View Post
    Even better, I would use a struct for inventory (instead of an array) part of which would be the array of parts and the num_parts counter. This way I would pass just the address of the inventory into the functions.
    I concur. If you make it
    Code:
    typedef struct {
        size_t namelen;
        char  *name;
        int    number;
        int    on_hand;
    } part_t;
    
    typedef struct {
        size_t   parts_max; /* Allocated parts */
        size_t   parts;     /* Parts in inventory */
        int      next;      /* Next free number */
        part_t **part;      /* Array of pointers, pointer per part */
    } inventory_t;
    
    #define INVENTORY_INITIALIZER { 0, 0, 1, NULL }
    you can easily grow the inventory dynamically when necessary.

    For example, to add a new part to inventory, automatically numbering new parts consecutively:
    Code:
    part_t *add_inventory(inventory_t *const inventory, const char *const name, const int on_hand)
    {
        const size_t namelen = (name) ? strlen(name) : 0;
        part_t *part;
    
        /* Neither pointer can be NULL, and name cannot be empty. */
        if (!inventory || namelen < 1) {
            errno = EINVAL;
            return NULL;
        }
    
        /* Is the inventory full? */
        if (inventory->parts >= inventory->parts_max) {
            size_t   parts_max;
            part_t **list;
    
            /* Grow parts_max by a bit more, so we don't reallocate so often. */
            if (inventory->parts < 16)
                parts_max = 16;
            else if (inventory->parts < 1048576)
                parts_max = (inventory->parts * 5) / 4;
            else
                parts_max = (inventory->parts | 131071) + 131073;
    
            list = realloc(inventory->part, parts_max * sizeof *inventory->part);
            if (!list) {
                errno = ENOMEM;
                return NULL;
            }
    
            inventory->parts_max = parts_max;
            inventory->part = list;
        }
    
        /* Allocate memory for the new part.
         * We include the name in the same area,
         * so if you need to reallocate the name string,
         * you need to reallocate the entire structure. */
        part = malloc((sizeof *part) + namelen + 1);
        if (!part) {
            errno = ENOMEM;
            return NULL;
        }
    
        /* The name immediately follows the structure itself. */
        part->name = ((char *)part) + sizeof *part;
        part->namelen = namelen;
        part->number = inventory->next++;
        part->on_hand = on_hand;
    
        /* Copy the name, including the '\0' following it. */
        memcpy(part->name, name, namelen + 1);
    
        /* Add to inventory. */
        inventory->part[inventory->parts++] = part;
    
        /* Success! */
        errno = 0;
        return part;
    }
    Locating a part based on its name or number is also very easy:
    Code:
    part_t *part_by_number(inventory_t *const inventory, const int number)
    {
        size_t i;
    
        if (!inventory) {
            errno = EINVAL;
            return NULL;
        }
    
        i = inventory->parts;
        while (i-->0)
            if (inventory->part[i]->number == number)
                return inventory->part[i];
    
        errno = ENOENT;
        return NULL;
    }
    
    part_t *part_by_name(inventory_t *const inventory, const char *const name)
    {
        const size_t namelen = (name) ? strlen(name) : 0;
        size_t i;
    
        if (!inventory || namelen < 1) {
            errno = EINVAL;
            return NULL;
        }
    
        i = inventory->parts;
        while (i-->0)
            if (inventory->part[i]->namelen == namelen &&
                !strcmp(inventory->part[i]->name, name))
                return inventory->part[i];
    
        errno = ENOENT;
        return NULL;
    }
    All you need to do is initialize the inventory to INVENTORY_INITIALIZER (so the parts and parts_max are zero and part NULL). (realloc(NULL, size) is the same as malloc(size), so there is no need to treat the first "allocation" any different than any other reallocations.)

    For example,
    Code:
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <errno.h>
    
    /* The three above code snippets */
    
    int main(void)
    {
        inventory_t inventory = INVENTORY_INITIALIZER;
        part_t *part;
    
        /* Add a wrench to the inventory. We are only interested in the result,
         * not the wrench part itself. */
        if (!add_inventory(&inventory, "wrench", 1)) {
            fprintf(stderr, "Failed to add a wrench to inventory: %s.\n", strerror(errno));
            return 1;
        }
    
        /* Add five sprockets to inventory. */
        part = add_inventory(&inventory, "sprocket", 5);
        if (!part) {
            fprintf(stderr, "Failed to add sprockets to inventory: %s.\n", strerror(errno));
            return 1;
        }
    
        /* Since we saved the return pointer, we already have a handle
         * to the sprocket part we added to the inventory. */
        printf("Sprocket name is '%s', part number %d, and there are %d on hand.\n",
               part->name, part->number, part->on_hand);
    
        /* Look up the wrench. */
        part = part_by_name(&inventory, "wrench");
        if (part)
            printf("Wrench name is '%s', part number %d, and there are %d on hand.\n",
                   part->name, part->number, part->on_hand);
    
        printf("Inventory contains %lu parts.\n", (unsigned long)inventory.parts);
    
        return 0;
    }
    Efficiency-wise, there are a large number of ways to make this more efficient.

    Perhaps the simplest one would be to have two arrays of part pointers in the inventory instead of one, with parts arranged by name in one, and by number in the other. Then you could use binary search to look for the correct part. To insert, you'd first find the correct position (binary search), then move the pointers one step up, and set the new pointer there. The trick is that you need to do this for both arrays, not just one; the index where the new part is inserted to is usually different in the two arrays.

    Even when you read data from disk, it's usually the fastest wall-clock-time wise to do it that way, instead of first reading and then sorting. This is because the I/O is usually the bottleneck, and if you do the "insertion work" while also doing I/O, you basically spend CPU time when otherwise your code would be waiting for reads ti complete. (In other words, you do spend more CPU time, but your task completes in less wall clock time.)

    In any case, for up to several hundred thousand parts or more, users won't notice the "slowness", since it'll be instantaneous in human terms on any 32-bit processor with enough memory. So, for an exercise, such optimizations are not needed.

    However, those kinds of algorithmic optimizations are quite important in real life -- at least if you care about your user experience --, and not at all difficult to implement when you get more proficient in C. So, I think this is a good thing to revisit at some point in the future.

    (I for one don't bother to "optimize code", except for certain math stuff when it happens to be the bottleneck. For best results, optimize your algorithms instead of optimizing your code!)

    Hope you find this interesting.

  14. #14
    Registered User
    Join Date
    Sep 2011
    Location
    Athens , Greece
    Posts
    357
    @Nominal thank you very much I will stick this information and I will come later ... because the next chapter is related with double pointers and dynamic memory allocation ...
    Last edited by Mr.Lnx; 06-21-2013 at 10:55 AM.

  15. #15
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by Mr.Lnx View Post
    @Nominal thank you very much I will stick this information and I will come later ... because the next chapter is related with double pointers and dynamic memory allocation ...
    Oops, I rushed a bit, then

    Anyway, I've found it very informative to compare different solutions, to see why things are done the way they are. Many examples, especially in books, are not as careful as they should be, so if there is something you wonder about in my code, feel free to ask, and I (and hopefully others!) shall try to clarify.

    There are two things I wish C programming courses would say about pointers:
    • Arrays are often referenced using pointers.
      For actual arrays, the compiler will treat the reference to the array as if it was a pointer to the first element of the array, automatically when needed.
      Because of this, when reading or writing code, a pointer can be a pointer to an item, or a pointer to an array of items. There is no difference in the declaration; only the way the code uses the pointer is different.
      In everyday speech, it is common to use pointer p and array p interchangeably, with the listener expected to deduce the correct interpretation from the way the code uses p.
    • When you read or write code, read the specifiers separated by asterisks (*) from right to left, starting at the variable name. In other words,
      Code:
      char *r;
      const char *s;
      char *const t;
      const char *const q;
      char **const a;
      const char **b;
      char *const *const c;
      read in English as:

      r is a pointer to a char, or a pointer to a char array. (If the data ends with a NUL byte, '\0', the char array contains a string: r is a pointer to a string.)
      s is a pointer to constant char(s). That is, you can modify s, but not the data it points to.
      t is a constant pointer to char(s). That is, you cannot modify t, but you can modify the data it points to.
      q is a constant pointer to constant char(s). You cannot modify q nor the data it points to.
      a is a constant pointer to a pointer to char(s), or an array of pointers to char(s) (== an array of char pointers). You cannot modify a, but you can modify the pointers, and the data they point to.
      b is a pointer to a pointer to constant char(s), or an array of pointers to constant char(s). You can modify b, and you can modify the pointer(s), but you cannot modify the data they point to.
      c is a constant pointer to constant pointer(s) to char(s), or an array of constant pointers to char(s). You cannot modify c or the pointers, but you can modify the data they point to.
      (Literal strings, "this", are logically the same type as q, but the compiler manages their storage.)

      Sprinkling const in the variable declarations -- especially in function definitions -- is useful, because it allows the compiler to generate better code (it does not need to guess whetjher the data might be modified or not), and it also tells other programmers the original intent, on how that parameter or variable is intended to be used.

      The same right-to-left order applies to all specifiers, not just const. For function return types, too.
    Last edited by Nominal Animal; 06-21-2013 at 12:04 PM.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. thoughts
    By laasunde in forum C++ Programming
    Replies: 5
    Last Post: 06-26-2003, 08:38 AM
  2. UML and C++: Your Thoughts
    By Mister C in forum C++ Programming
    Replies: 5
    Last Post: 03-16-2003, 12:56 PM
  3. What are you thoughts?
    By hermit in forum A Brief History of Cprogramming.com
    Replies: 2
    Last Post: 10-06-2002, 06:10 PM
  4. more thoughts....
    By DavidP in forum A Brief History of Cprogramming.com
    Replies: 11
    Last Post: 05-07-2002, 02:34 AM
  5. thoughts
    By DavidP in forum A Brief History of Cprogramming.com
    Replies: 27
    Last Post: 04-29-2002, 10:00 PM