This repository contains the source code for the semester 5 project assigned to us for the course CS3101: Programming and Data Structures I
To compile the source, first download it, by either cloning it, or downloading it as a zip from github. Then, run make
. That should create an executable called lms
in your PWD
.
An example has been provided below (for cloning the repo using git-cli and then compiling it).
git clone https://github.com/TheSillyCoder/library-system.git
cd library-system
make
./lms
This application has ~ 3000 books in the database, all of which are stored in the books-clean.csv
file. This application let's a user (student/faculty) issue or return books,
while the admins can add, delete, and edit books. It also has a search functionality, including a free-text/fuzzy search, an field-wise/advanced search, and a search by ID. The users
are classified using a user priviledge parameter, and the users are split into 3 seperate classes, "admins", "student", and "faculty". It also has a chatbot Clippy
, which can answer your questions.
The admin can change a user's priviledge, by demoting or promoting them.
We have talked about these features in a little more detail below.
When the application is opened it prompts the user for their username and password. If the username is new, the application asks the user if they want to sign up, and if confirmed, its signs the user up, and logs them in. Or otherwise, if the username and password match one record in the database, it logs that user in. Otherwise it quits the application. Saying "Login Failed!!!"
The functions being called in this process are as follows:
int login(int* priv, char** uname)
: It prompts for a login and returnsLOGIN_FAILIURE
, if the login fails, andLOGIN_SUCCESS
otherwise.User* fetchUsers(char* filename)
: This reads the user data from the user and stores them in an array ofUser
structs.void registerUser(char* username, char* password)
: In case its a new user, this function writes the new user into the users database.
The return values of login
are here
#define LOGIN_SUCCESS 0
#define LOGIN_FAILURE -1
The priviledges have the following values:
enum PRIV {
ADMIN=1,
FACULTY=2,
STUDENT=4
};
The forementioned User
struct has been defined as follows.
typedef struct {
char* username;
char* password;
int priv;
} User;
This chatbot uses a combination of natural language processing techniques and a weighted scoring mechanism to determine appropriate responses to user input. The core functionality includes tokenizing input, calculating weights for matching contexts, and selecting an answer based on statistical measures like mean and standard deviation.
The chatbot leverages pre-stored context information and a trie-based search system to efficiently find relevant responses.
Tokenizes a user input string into sanitized tokens.
char* input
: The input string provided by the user.int* count
: Pointer to an integer to store the number of tokens generated.
- A dynamically allocated array of sanitized tokens.
- Splits the input string into tokens based on spaces.
- Sanitizes each token using the
sanitize
function. - Returns the sanitized token list and updates
count
with the number of tokens.
Calculates the mean of a list of floating-point numbers.
float* l
: Array of float numbers.int le
: Number of elements in the array.
- The mean value of the array.
Computes the standard deviation of a list of floating-point numbers.
float* l
: Array of float numbers.int le
: Number of elements in the array.
- The standard deviation of the array.
- Calculates the mean of the array.
- Computes the squared differences from the mean.
- Returns the square root of the average squared difference.
Generates a response to the user's input.
char* input
: The input query provided by the user.
- A dynamically allocated string containing the chatbot's response.
-
Context and Trie Initialization:
- Reads context data from a binary file (
context-test.bin
). - Loads a trie structure from another binary file (
trie-test.bin
).
- Reads context data from a binary file (
-
Tokenization:
- Tokenizes and sanitizes the input using
returnTokenList
.
- Tokenizes and sanitizes the input using
-
Token Weight Calculation:
- Searches the trie for each token and retrieves corresponding weights.
-
Weight Aggregation:
- Aggregates weights for each potential response.
-
Normalization and Sorting:
- Normalizes the aggregated weights and sorts them in descending order using
bubbleSortDescending
.
- Normalizes the aggregated weights and sorts them in descending order using
-
Answer Selection:
- Determines the top response based on statistical analysis of weights.
- If the difference between the top weight and the mean is less than half the standard deviation or the weight is invalid (NaN), a fallback response is given.
-
Memory Management:
- Frees dynamically allocated memory and returns the response.
-
Setup:
- Ensure the necessary context and trie binary files (
context-test.bin
andtrie-test.bin
) are generated and accessible.
- Ensure the necessary context and trie binary files (
-
Integrate with User Input:
- Use
generateAnswer
to process user queries. - Pass the user's query string to
generateAnswer
and display the returned response.
- Use
-
Error Handling:
- Fallback response: If the chatbot cannot determine an answer, it responds with a default message: "I'm sorry, but I do not know how to answer that!"
- Trie-based Search: Efficiently finds matching tokens in the context database.
- Context Data: Stores potential answers and their associated weights.
- Utility Functions:
str_split
: Splits a string by a delimiter.sanitize
: Prepares tokens for comparison by removing unwanted characters.bubbleSortDescending
: Sorts arrays in descending order.inPlaceNormalize
: Normalizes weights.
-
Memory Management:
- Add checks to ensure all dynamically allocated memory is freed.
- Use modern memory allocation patterns to avoid leaks.
-
Error Handling:
- Validate input and files to handle missing or corrupted data gracefully.
-
Optimization:
- Replace
bubbleSortDescending
with a more efficient sorting algorithm for larger datasets.
- Replace
-
Customizability:
- Allow dynamic updates to the context database without recompiling or replacing binary files.
#include <stdio.h>
#include "chatbot.h"
int main() {
char query[256];
printf("Ask me a question: ");
fgets(query, 256, stdin);
// Generate a response
char* response = generateAnswer(query);
printf("Chatbot: %s\n", response);
free(response); // Free allocated response memory
return 0;
}
This code integrates the chatbot into a simple terminal-based interface, allowing users to ask questions and receive answers.
This documentation provides an explanation of the C code implementing a fuzzy search algorithm. The code performs advanced, flexible searches in a dictionary database using various techniques like the Damerau-Levenshtein metric, Soundex hashing, and synonym matching.
The code consists of the following primary components:
advanced_search()
: The main entry function for performing advanced searches. It allows searching by title, author, or publisher using fuzzy matching.fuzzy_search()
: A helper function that conducts the fuzzy search for a specific category (title, author, or publisher).- Utility Functions:
- Damerau-Levenshtein Metric: Used to compute the edit distance between strings, allowing correction of typographical errors.
- Soundex: Identifies phonetically similar words.
- Synonyms Lookup: Matches words with their synonyms using a thesaurus.
- Sorting Functionality: Results are scored and sorted by relevance.
This function takes the following arguments:
title
: The search term for the book title.author
: The search term for the book author.pub
: The search term for the publisher.dict_file
: Path to the dictionary file containing indexed data.
-
Separate Searches:
- If a search term is provided for a category (e.g., title), it performs a fuzzy search using
fuzzy_search()
. - Results for each category are stored in arrays (
res_title
,res_author
, andres_pub
).
- If a search term is provided for a category (e.g., title), it performs a fuzzy search using
-
Combine Results:
- Combines results from all search categories by matching common indices.
- Calculates scores based on the relevance of results and their position in each category's list.
-
Special Cases:
- If only one category has results (e.g., only the title was searched), returns the results for that category directly.
- If no results are found for all categories, returns an empty array.
-
Sorting:
- Scores are used to sort the combined results in descending order of relevance.
-
Return:
- Returns an array of indices corresponding to the most relevant matches.
This function performs a detailed fuzzy search for a given query and category.
query
: The search term.cat
: The category to search in (1
for title,2
for author,3
for publisher).dict_file
: The dictionary file.
-
Sanitization:
- Prepares the query by stripping extra spaces, splitting it into terms, and sanitizing them to handle special characters.
- Generates Soundex hashes for terms to handle phonetically similar words.
-
Synonym Expansion:
- Uses a thesaurus (
thesaurus.csv
) to expand the search query with synonyms.
- Uses a thesaurus (
-
Dictionary Lookup:
- Iterates through each row of the dictionary.
- For each term:
- Computes the Damerau-Levenshtein Distance for fuzzy matching.
- Compares Soundex hashes for phonetically similar matches.
- Matches terms with their synonyms.
-
Scoring:
- Assigns scores to results based on:
- Edit distance (smaller distance → higher score).
- Soundex match quality.
- Synonym penalty for less direct matches.
- Combines results from all terms and calculates cumulative scores.
- Assigns scores to results based on:
-
Sorting:
- Results are sorted by scores in descending order for relevance.
-
Return:
- Returns an array of indices representing matches, sorted by score.
-
Damerau-Levenshtein Distance:
- Measures the edit distance between two strings, accounting for operations like:
- Insertion
- Deletion
- Substitution
- Transposition
- Allows matching terms even with typographical errors.
- Measures the edit distance between two strings, accounting for operations like:
-
Soundex Hashing:
- Encodes words into a phonetic representation.
- Matches words that sound similar but may have different spellings.
-
Synonyms Expansion:
- Uses a thesaurus to find synonyms for search terms.
- Broadens the search to include related terms.
-
Sorting by Scores:
- Combines scores from all matching methods (Damerau-Levenshtein, Soundex, and synonyms).
- Higher relevance results are ranked at the top.
-
A dictionary file (
dict_file
):- Contains indexed data structured as:
Example:
<token>,<type>,<indices>,<Soundex_hash>
"programming","title","1-2-3","P625" "science","author","4-5","S520"
- Contains indexed data structured as:
-
A thesaurus file (
thesaurus.csv
):- Lists synonyms in a comma-separated format.
Example:
"book,volume,tome" "author,writer,novelist"
- Lists synonyms in a comma-separated format.
Example:
-
Include required headers and ensure linked implementations for:
- Damerau-Levenshtein
- Soundex
- Thesaurus lookups
-
Call
advanced_search()
with appropriate parameters:char* title = "science"; char* author = ""; char* publisher = "oxford"; char* dict_file = "books-clean.csv"; int* results = advanced_search(title, author, publisher, dict_file); for (int i = 0; results[i] != -1; ++i) { printf("Matched index: %d\n", results[i]); } free(results);
- Memory Usage:
- Uses dynamically allocated memory for result arrays. Ensure all memory is freed after use.
- Time Complexity:
- Depends on the size of the dictionary and the complexity of the search terms.
- Sorting operations and multiple scans through the dictionary can be costly for large datasets.
- Dictionary Structure:
- The dictionary file must follow the specified format strictly.
- Synonym Coverage:
- Effectiveness of synonyms depends on the quality of the thesaurus file.
- Optimization:
- Sorting and multiple list intersections could be optimized for performance.
This code provides a robust solution for implementing a fuzzy search with support for errors, synonyms, and phonetic matching. It is particularly suited for searching in large text databases like books, documents, or metadata collections.
Search by ID: To do this, we have implemented a binary search, in the int idToIdx(int id)
function in ui.c
. This returns the index of the book which has a matching ID.
The users (faculty/student) can issue books from, or return their issued books to the library.
When you trigger the command to issue a book, the function calls that are executed have been explained below.
void issuePrompt(int i)
: This function takes a variableint i
as one of the parameters, which corresponds to the index of the the book in the globalE.Books
array. This then is passed to the next function.void issueBook(char* username, int bookID, int nweeks)
: This function is defined inlibrary.c
it adds a record in thetransanctions.csv
. The structure of this database has been mentioned below.
Note: If a book has less than 3 copies, only faculty can issue the book. Also, there's a upper bound on the number of books that can be issued by the users, namely STUDILIM
and FACILIM
. Also
the default number of weeks for which a book can be issued is defined as STUDLIM
and FACLIM
.
Here are the definitions for these limits defined in ui.c
.
#define STUDLIM 8
#define FACLIM 16
#define STUDILIM 5
#define FACILIM 10
If the number of copies of the book being issued is less than 5, the following formula is used to calculate the number of weeks for which the book will be issued.
nWeeks = nWeeks - (5 - copies);
To return a book, one can go to the DUES
page, and see their issued books. Once they, trigger the return a book, the following functions are called in sequence.
void returnPrompt(int i)
: This function takes the index of the issued book entry in the globalDues
array, and then deletes it from the database.void loadDues()
: This loads the issued books into the global books array. This has been explained later in the UI section.
Any admin who has admin priviledges can add, delete or edit books. Whenever these are triggered, the following functions are called respectively.
void addPrompt()
: This prompts the admin, to enter the details of the new book to be added to the data base. And then it callsupdateBooks()
to update the database.void deletePrompt(int i)
: This prompts the admin for confirmation whether they really want to delete the record. If confirmed, it deletes the book from the global books array and callsupdateBooks()
to update the database.void editPrompt(int i)
: This prompts the admin to enter what field they want to edit. And then it edits that book in the global books array and then callsupdateBooks()
to update the database.
All of these functions call the function updateBooks()
. This function has been explained below.
void updateBooks(Book* books, int nbooks)
: This function takes an array of books and the number of books in that array as parameters and writes it to the database.
The Book
struct was defined like this:
typedef struct {
int id;
char* title;
char* authors;
char* publisher;
char* pubDate;
int pages;
int qty;
} Book;
All the databases used in this application are in CSV format.
The books are stored in books-clean.csv
file. This dataset was downloaded from kaggle. It has the following fields:
- book ID
- Book title
- Book authors
- Number of Pages in the book
- Publication date
- Publisher
- Copies Available
The users are stored in users.csv
file. This file has the following fields:
- Username
- Password
- Userfield
The issued books are stored in transanctions.csv
file. This file has the following fields:
- Username
- Book ID
- Issue Date
- Due Date
The User Interface for this library management system has been written in the ui.c
. It provides functionality for rendering different pages, managing user inputs, and interacting with backend modules.
-
Multi-page UI with views for book search, user management, dues, and chat functionalities.
-
Command handling for efficient navigation and operations.
-
Integration with other system components, such as RSA encryption and chatbots.
The following libraries are used:
-
Standard Libraries:
stdio.h
,stdlib.h
,termios.h
, etc., for basic I/O and terminal interaction. -
Custom Modules: Headers such as
rsa.h
,library.h
, andchatbot.h
enable modular functionality.
Key macros defined in the file include:
-
MSGTIMEOUT
: Timeout for displaying messages. -
MAXCHARLIM
: Maximum character limit for command buffers. -
CTRL_Key()
: This is a macro that gives us the ASCII value of the character when a key is pressed along with theCtrl
Key. -
PAGES
: This enum stores the values for rendering different pages in the application, like the homepage, search results, book details, chat bot etc. -
cursorKeys
: This is an enum storing the keycodes for different characters, which are then returned by thereadKeyPress
function.
The definitions for these macros is shown below.
#define MSGTIMEOUT 3
#define MAXCHARLIM 5000
#define CTRL_Key(x) (x & 31)
enum cursorKeys {
TAB_KEY = 9,
BACKSPACE = 127,
ARROW_LEFT = 1000,
ARROW_RIGHT,
ARROW_UP,
ARROW_DOWN,
PAGE_UP,
PAGE_DOWN,
HOME_KEY,
END_KEY,
DEL_KEY
};
enum PAGES {
NORMAL=0,
BOOK_VIEW,
SEARCH,
DUES,
DUE_VIEW,
USERS,
CHAT
};
-
Due
: Tracks book issuance details, such as username, book ID, issue date, and due date. -
Userd
: Represents user information, including username and privilege level. -
Chat
: Stores chatbot interactions (questions and answers). -
state
: Maintains the current state of the UI, including cursor position, active page, and user information etc.
The definitions for these Structs are shown below:
typedef struct {
char* uname;
int bookID;
time_t issueDate;
time_t dueDate;
} Due;
typedef struct {
char* uname;
int priv;
} Userd;
typedef struct {
char* question;
char* answer;
} Chat;
struct state {
int cx, cy, screenrows, screencols, rowoff, numResults;
struct termios orig_term;
Book* books;
int nbooks;
char* username;
int userPriv;
int page;
char commandBuf[MAXCHARLIM];
time_t commandTime;
int* sIdx;
Due* dues;
int nDues;
Userd* users;
int nUsers;
Chat chat;
};
- This function turns off canonical mode in the terminal and turns it into raw mode.
- Kill the application if it throws an error and quit with an error message.
- Reset the terminal.
- Read key presses from the terminal stdin.
- Free the global books array.
- Free the global dues array.
- Function to run when the user sends the quit signal. Frees the global arrays and resets the terminal.
- A function to search the book database by ID.
- A binary search function that returns the index of the book when the ID of the book is passed into it.
- Move the cursor when the arrow keys are pressed.
- Loads the books when the app is started.
- Loads the due books when the app is started.
- Load the users list for the admin.
- Draw the users table.
- Change the user type.
- This function lets the user return a book.
- Draws the bottom status bar.
- Draw the top bar.
- Draw the table of books on the homepage.
- Prompt the user to ask questions to the chatbot.
- Render the chatbot page.
- Render the dues table.
- Render the details of a particular issued book record.
- Render the search results.
- Draw the command bar.
- Set the text in the command bar.
- Render the help bar.
- Draw the quit instruction.
- Prompt the user for an input. Takes a FORMAT string as an input and returns a string (
char *
).
- Prompt the user to delete a book and ask for confirmation.
- Prompt the user to add details about a new book.
- Edit book details.
- Prompt the user to issue a book.
- Prompt the user for a search.
- Advanced search functionality.
- Scrolls the page by setting the global
E.rowoff
variable to set a row offset.
- Sends the cursor to the x-th column and the y-th row.
- Resets the screen, clears it, and returns the cursor to
(0, 0)
.
- Refreshes the screen and draws everything to the screen.
- Initializes the UI and logs the user in. Called at the beginning of the main function.
- Gets dimensions of the terminal window in rows and columns and writes to the pointers passed as parameters.
- Gets the cursor position into the pointers passed as parameters.
- Terminal support for ANSI escape codes (for UI rendering).
This program implements basic RSA encryption and decryption, providing functionality to encrypt and decrypt files. It also generates RSA key pairs using random prime numbers and performs modular arithmetic for encryption and decryption operations.
-
encryptFile(char* file, char* out_file, int public_key, unsigned long prime_product)
- Encrypts a text file character by character using the provided public key and prime product (
n
). - Inputs:
file
: Path to the input file (plaintext).out_file
: Path to the output file (encrypted).public_key
: RSA public key (e
).prime_product
: Product of two primes (n
).
- Encrypts a text file character by character using the provided public key and prime product (
-
decryptFile(char* infile, char* outfile, int private_key, unsigned long prime_product)
- Decrypts an encrypted file using the provided private key and prime product (
n
). - Inputs:
infile
: Path to the input file (encrypted).outfile
: Path to the output file (decrypted plaintext).private_key
: RSA private key (d
).prime_product
: Product of two primes (n
).
- Decrypts an encrypted file using the provided private key and prime product (
-
encrypt(unsigned long int msg, int public_key, unsigned long prime_product)
- Encrypts a single integer (
msg
) using modular exponentiation:
[ \text{encrypted_msg} = \text{msg}^e \mod n ]
- Encrypts a single integer (
-
decrypt(unsigned long int enc, int private_key, unsigned long prime_product)
- Decrypts a single encrypted integer (
enc
) using modular exponentiation:
[ \text{decrypted_msg} = \text{enc}^d \mod n ]
- Decrypts a single encrypted integer (
setkeys()
- Generates an RSA key pair (public key, private key) and the prime product (
n
). - Returns:
An array containing:- Public key (
e
) - Private key (
d
) - Prime product (
n
)
- Public key (
- Generates an RSA key pair (public key, private key) and the prime product (
-
returnRandom(unsigned long int min, unsigned long int max)
- Generates a random integer in the range
[min, max]
.
- Generates a random integer in the range
-
diff(unsigned long int x, unsigned long int y)
- Returns the absolute difference between two integers.
-
gcd(unsigned long int x, unsigned long int y)
- Calculates the greatest common divisor (GCD) using the Euclidean algorithm.
- Sieve of Eratosthenes
- Used within
setkeys()
to identify prime numbers up toMAXPRIME
. - Two random primes are selected for key generation.
- Used within
MAXPRIME
- The upper limit for prime numbers used in the key generation process. Defaults to 1000.
-
Key Generation:
- Call
setkeys()
to generate the public/private key pair andn
.
- Call
-
Encryption:
- Use
encryptFile()
to encrypt a plaintext file with the public key (e
) andn
.
- Use
-
Decryption:
- Use
decryptFile()
to decrypt the file using the private key (d
) andn
.
- Use
- Generate keys:
unsigned long* keys = setkeys(); int public_key = keys[0]; int private_key = keys[1]; unsigned long prime_product = keys[2];
- Encrypt a file:
encryptFile("plaintext.txt", "encrypted.txt", public_key, prime_product);
- Decrypt the file:
decryptFile("encrypted.txt", "decrypted.txt", private_key, prime_product);
- The code assumes small keys for simplicity. In real-world scenarios, use larger primes and more secure key lengths.
- Proper memory management (e.g., freeing allocated memory) is essential to avoid memory leaks.
Debayan Sarkar (@TheSilyCoder) | Diptanuj Sarkar (@CasioWave) |
---|---|
User Interface | Chatbot |
Database CRUD operations | Search |
Authentication | RSA |