← Back to Lecture
Practical Work 5

Library Management System - Catalog Operations

Implement search, display, and calculation features using loops, arrays, and recursion

Duration 3 hours
Difficulty Intermediate
Session 5 - 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 title
  • sortByYear() - 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)

Resources