Library Management System - Catalog Operations
Implement search, display, and calculation features using loops, arrays, and recursion
Objectives
By the end of this practical work, you will be able to:
- Use different types of loops (for, while, do-while, foreach)
- Work with arrays to store and manipulate collections of data
- Implement search algorithms to find books
- Use recursion for hierarchical calculations
- Display formatted catalog listings
Prerequisites
- Completed Practical Works 3 and 4
- Book, Member, BookStatus, BookCategory classes
- Basic console menu from PW4
Part 1: Managing Book Catalog with Arrays
Step 1.1: Create BookCatalog Class
Create a dedicated class to manage the book collection:
package com.library.service;
import com.library.model.Book;
import com.library.model.BookCategory;
import com.library.model.BookStatus;
public class BookCatalog {
private Book[] books;
private int size;
private static final int INITIAL_CAPACITY = 10;
public BookCatalog() {
books = new Book[INITIAL_CAPACITY];
size = 0;
}
// Add a book to the catalog
public void addBook(Book book) {
if (size >= books.length) {
expandCapacity(); // (#1:Dynamic array expansion)
}
books[size++] = book;
}
// Expand array when full
private void expandCapacity() {
Book[] newArray = new Book[books.length * 2];
for (int i = 0; i < books.length; i++) { // (#2:Manual array copy)
newArray[i] = books[i];
}
books = newArray;
System.out.println("Catalog capacity expanded to " + books.length);
}
// Get book by index
public Book getBook(int index) {
if (index >= 0 && index < size) {
return books[index];
}
return null;
}
public int getSize() {
return size;
}
public Book[] getAllBooks() {
Book[] result = new Book[size];
for (int i = 0; i < size; i++) {
result[i] = books[i];
}
return result;
}
}
Step 1.2: Display All Books with Different Loops
Add display methods using different loop types:
// Using traditional for loop
public void displayAllBooksFor() {
System.out.println("\n=== Catalog (for loop) ===");
for (int i = 0; i < size; i++) {
System.out.printf("%d. %s by %s [%s]%n",
i + 1,
books[i].getTitle(),
books[i].getAuthor(),
books[i].getStatus());
}
}
// Using while loop
public void displayAllBooksWhile() {
System.out.println("\n=== Catalog (while loop) ===");
int i = 0;
while (i < size) {
System.out.printf("%d. %s (%d)%n",
i + 1,
books[i].getTitle(),
books[i].getPublicationYear());
i++;
}
}
// Using enhanced for loop (foreach)
public void displayAllBooksForeach() {
System.out.println("\n=== Catalog (foreach) ===");
int count = 1;
for (Book book : getAllBooks()) { // (#1:Enhanced for loop)
System.out.printf("%d. %s - %s%n",
count++,
book.getTitle(),
book.getCategory());
}
}
Part 2: Search Functionality
Step 2.1: Search by Title
// Search by title (returns array of matching books)
public Book[] searchByTitle(String searchTerm) {
// First pass: count matches
int matchCount = 0;
for (int i = 0; i < size; i++) {
if (books[i].getTitle().toLowerCase()
.contains(searchTerm.toLowerCase())) {
matchCount++;
}
}
// Second pass: collect matches
Book[] results = new Book[matchCount];
int resultIndex = 0;
for (int i = 0; i < size; i++) {
if (books[i].getTitle().toLowerCase()
.contains(searchTerm.toLowerCase())) {
results[resultIndex++] = books[i];
}
}
return results;
}
// Search by author
public Book[] searchByAuthor(String authorName) {
int matchCount = 0;
for (Book book : getAllBooks()) {
if (book.getAuthor().toLowerCase()
.contains(authorName.toLowerCase())) {
matchCount++;
}
}
Book[] results = new Book[matchCount];
int index = 0;
for (Book book : getAllBooks()) {
if (book.getAuthor().toLowerCase()
.contains(authorName.toLowerCase())) {
results[index++] = book;
}
}
return results;
}
Step 2.2: Filter by Category and Status
// Filter by category
public Book[] filterByCategory(BookCategory category) {
int count = 0;
for (int i = 0; i < size; i++) {
if (books[i].getCategory() == category) {
count++;
}
}
Book[] filtered = new Book[count];
int index = 0;
for (int i = 0; i < size; i++) {
if (books[i].getCategory() == category) {
filtered[index++] = books[i];
}
}
return filtered;
}
// Filter by status
public Book[] filterByStatus(BookStatus status) {
int count = 0;
int i = 0;
while (i < size) { // Using while loop
if (books[i].getStatus() == status) {
count++;
}
i++;
}
Book[] filtered = new Book[count];
int index = 0;
i = 0;
while (i < size) {
if (books[i].getStatus() == status) {
filtered[index++] = books[i];
}
i++;
}
return filtered;
}
// Get available books only
public Book[] getAvailableBooks() {
return filterByStatus(BookStatus.AVAILABLE);
}
Step 2.3: Find Book by ISBN
// Find single book by ISBN (unique identifier)
public Book findByIsbn(String isbn) {
for (int i = 0; i < size; i++) {
if (books[i].getIsbn().equals(isbn)) {
return books[i]; // Found!
}
}
return null; // Not found
}
// Check if ISBN exists
public boolean isbnExists(String isbn) {
return findByIsbn(isbn) != null;
}
Part 3: Catalog Statistics
Step 3.1: Count and Summary Methods
// Count books by status
public int countByStatus(BookStatus status) {
int count = 0;
for (int i = 0; i < size; i++) {
if (books[i].getStatus() == status) {
count++;
}
}
return count;
}
// Count books by category
public int countByCategory(BookCategory category) {
int count = 0;
for (Book book : getAllBooks()) {
if (book.getCategory() == category) {
count++;
}
}
return count;
}
// Display statistics
public void displayStatistics() {
System.out.println("\n===== Catalog Statistics =====");
System.out.println("Total books: " + size);
System.out.println("\nBy Status:");
for (BookStatus status : BookStatus.values()) {
int count = countByStatus(status);
if (count > 0) {
System.out.printf(" %s: %d%n", status, count);
}
}
System.out.println("\nBy Category:");
for (BookCategory category : BookCategory.values()) {
int count = countByCategory(category);
if (count > 0) {
System.out.printf(" %s: %d%n", category, count);
}
}
System.out.println("==============================");
}
Step 3.2: Find Oldest and Newest Books
// Find oldest book
public Book findOldestBook() {
if (size == 0) return null;
Book oldest = books[0];
for (int i = 1; i < size; i++) {
if (books[i].getPublicationYear() < oldest.getPublicationYear()) {
oldest = books[i];
}
}
return oldest;
}
// Find newest book
public Book findNewestBook() {
if (size == 0) return null;
Book newest = books[0];
int i = 1;
while (i < size) {
if (books[i].getPublicationYear() > newest.getPublicationYear()) {
newest = books[i];
}
i++;
}
return newest;
}
// Calculate average publication year
public double getAveragePublicationYear() {
if (size == 0) return 0;
int sum = 0;
for (Book book : getAllBooks()) {
sum += book.getPublicationYear();
}
return (double) sum / size;
}
Part 4: Recursion
Step 4.1: Late Fee Calculator with Recursion
Create a recursive late fee calculator with compound interest:
package com.library.service;
public class LateFeeCalculator {
private static final double DAILY_RATE = 0.50;
private static final double MAX_FEE = 25.0;
// Iterative version (for comparison)
public static double calculateFeeIterative(int daysLate) {
if (daysLate <= 0) return 0;
double fee = 0;
for (int day = 1; day <= daysLate; day++) {
fee += DAILY_RATE;
if (fee >= MAX_FEE) {
return MAX_FEE;
}
}
return fee;
}
// Recursive version
public static double calculateFeeRecursive(int daysLate) {
// Base cases
if (daysLate <= 0) return 0; // (#1:Base case - no late days)
// Calculate fee for remaining days recursively
double previousFee = calculateFeeRecursive(daysLate - 1); // (#2:Recursive call)
// Add today's fee
double totalFee = previousFee + DAILY_RATE;
// Apply maximum cap
return Math.min(totalFee, MAX_FEE); // (#3:Return with cap)
}
// Recursive with compound interest (fee increases each week)
public static double calculateCompoundFee(int daysLate, int currentDay) {
if (currentDay > daysLate) {
return 0; // Base case
}
// Week 1: 0.50, Week 2: 0.75, Week 3+: 1.00
double dailyRate;
if (currentDay <= 7) {
dailyRate = 0.50;
} else if (currentDay <= 14) {
dailyRate = 0.75;
} else {
dailyRate = 1.00;
}
return dailyRate + calculateCompoundFee(daysLate, currentDay + 1);
}
// Helper method for compound fee
public static double calculateCompoundFee(int daysLate) {
double fee = calculateCompoundFee(daysLate, 1);
return Math.min(fee, MAX_FEE);
}
}
Step 4.2: Recursive Search in Nested Categories
Imagine categories can have sub-categories. Here's a recursive approach:
// Simplified category tree for demonstration
public class CategoryNode {
private String name;
private CategoryNode[] subCategories;
private int bookCount;
public CategoryNode(String name) {
this.name = name;
this.subCategories = new CategoryNode[10];
this.bookCount = 0;
}
// Recursive count of all books in this category and sub-categories
public int getTotalBookCount() {
int total = bookCount; // Books directly in this category
for (CategoryNode sub : subCategories) {
if (sub != null) {
total += sub.getTotalBookCount(); // Recursive call
}
}
return total;
}
// Recursive print of category tree
public void printTree(int indent) {
// Print current category with indentation
for (int i = 0; i < indent; i++) {
System.out.print(" ");
}
System.out.println("- " + name + " (" + bookCount + " books)");
// Recursively print sub-categories
for (CategoryNode sub : subCategories) {
if (sub != null) {
sub.printTree(indent + 1);
}
}
}
}
Step 4.3: Fibonacci for Member Loyalty Points
public class LoyaltyCalculator {
// Loyalty points follow Fibonacci-like growth
// Year 1: 1 point, Year 2: 1 point, Year 3: 2 points, etc.
public static int calculateLoyaltyPoints(int yearsAsMember) {
// Base cases
if (yearsAsMember <= 0) return 0;
if (yearsAsMember == 1) return 1;
if (yearsAsMember == 2) return 1;
// Recursive case: sum of previous two years
return calculateLoyaltyPoints(yearsAsMember - 1)
+ calculateLoyaltyPoints(yearsAsMember - 2);
}
// More efficient iterative version
public static int calculateLoyaltyPointsIterative(int years) {
if (years <= 0) return 0;
if (years <= 2) return 1;
int prev2 = 1;
int prev1 = 1;
int current = 0;
for (int i = 3; i <= years; i++) {
current = prev1 + prev2;
prev2 = prev1;
prev1 = current;
}
return current;
}
// Display loyalty points table
public static void displayLoyaltyTable(int maxYears) {
System.out.println("\n=== Loyalty Points Table ===");
System.out.println("Years | Points | Discount");
System.out.println("------|--------|----------");
for (int year = 1; year <= maxYears; year++) {
int points = calculateLoyaltyPointsIterative(year);
String discount = getDiscountTier(points);
System.out.printf(" %2d | %4d | %s%n", year, points, discount);
}
}
private static String getDiscountTier(int points) {
if (points >= 21) return "15% off";
if (points >= 8) return "10% off";
if (points >= 3) return "5% off";
return "None";
}
}
Part 5: Update Console Menu
Step 5.1: Integrate BookCatalog
Update your LibraryConsole to use the new BookCatalog:
// Replace array with BookCatalog
private BookCatalog catalog;
public LibraryConsole() {
scanner = new Scanner(System.in);
catalog = new BookCatalog();
initializeSampleBooks();
}
private void initializeSampleBooks() {
catalog.addBook(new Book("978-0-13-468599-1", "Effective Java",
"Joshua Bloch", 2018, BookCategory.TECHNOLOGY));
catalog.addBook(new Book("978-0-06-112008-4", "To Kill a Mockingbird",
"Harper Lee", 1960, BookCategory.FICTION));
// Add more sample books...
}
// Update menu with new options
private void displayMenu() {
System.out.println("\n====== Library Management System ======");
System.out.println("1. List all books");
System.out.println("2. Search by title");
System.out.println("3. Search by author");
System.out.println("4. Filter by category");
System.out.println("5. Filter by status");
System.out.println("6. Display statistics");
System.out.println("7. Calculate late fee");
System.out.println("8. View loyalty points");
System.out.println("0. Exit");
System.out.println("========================================");
}
Exercises
Exercise 1: Sort Books
Implement sorting methods in BookCatalog:
sortByTitle()- Alphabetically by titlesortByYear()- By publication year (newest first)sortByAuthor()- Alphabetically by author
Use nested loops (bubble sort or selection sort).
Exercise 2: Remove Book
Implement removeBook(String isbn) that:
- Finds the book by ISBN
- Shifts remaining elements to fill the gap
- Decrements size
- Returns true if successful, false if not found
Exercise 3: Recursive Book Count by Year Range
// Implement recursively
public int countBooksInYearRange(int startYear, int endYear, int currentIndex) {
// Count books published between startYear and endYear
// Use recursion instead of loops
}
Exercise 4: Member Borrowing History
Create a BorrowingHistory class that:
- Stores an array of borrowed book ISBNs
- Tracks borrow dates as strings (for now)
- Has methods:
addBorrowing(),displayHistory(),countOverdue()
Deliverables
- Source Code: Updated project with BookCatalog and all search/filter methods
- Console Output: Screenshots showing all menu features working
- Recursion Comparison: Output comparing iterative vs recursive late fee calculations
Bonus Challenges
- Challenge 1: Implement binary search for sorted arrays
- Challenge 2: Create a recursive method to generate book recommendations based on category
- Challenge 3: Implement pagination (display 10 books per page)