import java.io.*; import java.util.*; // Name: Niklas Johnson // Date: 4-2-2024 // This class allows users to find and rate books within BOOK_DIRECTORY // containing certain terms. Also handles omitting items if the query is prefixed // with a '-' public class SearchClient { public static final String BOOK_DIRECTORY = "./books"; public static void main(String[] args) throws FileNotFoundException { Scanner console = new Scanner(System.in); List books = loadBooks(); List media = new ArrayList<>(books); Map> index = createIndex(media); System.out.println("Welcome to the CSE 123 Search Engine!"); String command = ""; while (!command.equalsIgnoreCase("quit")) { System.out.println("What would you like to do? [Search, Rate, Quit]"); System.out.print("> "); command = console.nextLine(); if (command.equalsIgnoreCase("search")) { searchQuery(console, index); } else if (command.equalsIgnoreCase("rate")) { addRating(console, media); } else if (!command.equalsIgnoreCase("quit")) { System.out.println("Invalid command, please try again."); } } System.out.println("See you next time!"); } // Produces and returns a "deep copy" of the parameter map, which has the same // structure and values as the parameter, but with all internal data structures // and values copied. After calling this method, modifying the parameter or // return value should NOT affect the other. // // Parameters: // inputMap - the map to duplicate // // Returns: // A deep copy of the parameter map. // TODO: Copy fixed version and update to use Media public static Map> deepCopy(Map> inputMap) { Map> deepCopy = new TreeMap<>(); for (String key : inputMap.keySet()) { Set inputSet = new HashSet<>(inputMap.get(key)); deepCopy.put(key, inputSet); } return deepCopy; } // creates a directory of words that appear in a given list of Strings // takes a list of strings to process // returns sorted map containing the index // no thrown exceptions public static Map> createIndex(List media) throws FileNotFoundException { Map> index = new TreeMap<>(); Set uniqueWords = getUniqueWords(media); for (String uniqueWord : uniqueWords) { index.put(uniqueWord.toLowerCase(), new HashSet()); } for (String word : index.keySet()) { for (int i = 0; i < media.size(); i++) { Scanner titleScanner = new Scanner(media.get(i).getTitle()); while (titleScanner.hasNext()) { if (titleScanner.next().equalsIgnoreCase(word)) { index.get(word).add(media.get(i)); } } titleScanner.close(); for (String artists : media.get(i).getArtists()) { Scanner artistScanner = new Scanner(artists); while (artistScanner.hasNext()) { if (artistScanner.next().equalsIgnoreCase(word)) { index.get(word).add(media.get(i)); } } artistScanner.close(); } } } return index; } // helper method for createIndex. looks through given list of strings, // keeping track of only unique words // takes list of words to prune // returns pruned set of words // no thrown exceptions public static Set getUniqueWords(List media) throws FileNotFoundException { Set uniqueWords = new HashSet<>(); for (Media thing : media) { Scanner mediaScanner = new Scanner(new File(BOOK_DIRECTORY + "/" + thing.getTitle() + ".txt")); while (mediaScanner.hasNext()) { uniqueWords.add(mediaScanner.next()); } mediaScanner.close(); } return uniqueWords; } // Allows the user to search a specific query using the provided 'index' to find appropraite // Media entries. // // Parameters: // console - the Scanner to get user input from // index - invertedIndex mapping terms to the Set of media containing those terms public static void searchQuery(Scanner console, Map> index) { System.out.println("Enter query:"); System.out.print("> "); String query = console.nextLine(); Optional> result = search(deepCopy(index), query); if (result.isEmpty()) { System.out.println("\tNo results!"); } else { for (Media m : result.get()) { System.out.println("\t" + m.toString()); } } } // Allows the user to add a rating to one of the options wthin 'media' // // Parameters: // console - the Scanner to get user input from // media - list of all media options loaded into the search engine public static void addRating(Scanner console, List media) { System.out.print("[" + media.get(0).getTitle()); for (int i = 1; i < media.size(); i++) { System.out.print(", " + media.get(i).getTitle()); } System.out.println("]"); System.out.println("What would you like to rate (enter index)?"); System.out.print("> "); int choice = Integer.parseInt(console.nextLine()); if (choice < 0 || choice >= media.size()) { System.out.println("Invalid choice"); } else { System.out.println("Rating [" + media.get(choice).getTitle() + "]"); System.out.println("What rating would you give?"); System.out.print("> "); int rating = Integer.parseInt(console.nextLine()); media.get(choice).addRating(rating); } } // Searches a specific query using the provided 'index' to find appropraite Media entries. // terms are determined by whitespace separation. If a term is proceeded by '-' any entry // containing that term will be removed from the result. // // Parameters: // index - invertedIndex mapping terms to the Set of media containing those terms // query - user's entered query string to use in searching // // Returns: // An optional set of all Media containing the requirested terms. If none, Optional.Empty() public static Optional> search(Map> index, String query) { Optional> ret = Optional.empty(); Scanner tokens = new Scanner(query); while (tokens.hasNext()) { boolean minus = false; String token = tokens.next().toLowerCase(); if (token.startsWith("-")) { minus = true; token = token.substring(1); } if (index.containsKey(token)) { if (ret.isEmpty() && !minus) { ret = Optional.of(index.get(token)); } else if (!ret.isEmpty() && minus) { ret.get().removeAll(index.get(token)); } else if (!ret.isEmpty() && !minus) { ret.get().retainAll(index.get(token)); } } } return ret; } // Loads all books from BOOK_DIRECTORY. Assumes that each book starts with two lines - // "Title: " which is followed by the book's title // "Author: " which is followed by the book's author // // Returns: // A list of all book objects corresponding to the ones located in BOOK_DIRECTORY public static List loadBooks() throws FileNotFoundException { List ret = new ArrayList<>(); File dir = new File(BOOK_DIRECTORY); for (File f : dir.listFiles()) { Scanner sc = new Scanner(f); String title = sc.nextLine().substring("Title: ".length()); String author = sc.nextLine().substring("Author: ".length()); ret.add(new Book(title, author)); } return ret; } }