This repository has been archived on 2026-03-18. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
CSE-123/search-engine/SearchClient.java
2026-03-18 00:39:35 -07:00

214 lines
8.3 KiB
Java

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<Book> books = loadBooks();
List<Media> media = new ArrayList<>(books);
Map<String, Set<Media>> 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<String, Set<Media>> deepCopy(Map<String, Set<Media>> inputMap) {
Map<String, Set<Media>> deepCopy = new TreeMap<>();
for (String key : inputMap.keySet()) {
Set<Media> 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<String, Set<Media>> createIndex(List<Media> media) throws FileNotFoundException {
Map<String, Set<Media>> index = new TreeMap<>();
Set<String> uniqueWords = getUniqueWords(media);
for (String uniqueWord : uniqueWords) {
index.put(uniqueWord.toLowerCase(), new HashSet<Media>());
}
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<String> getUniqueWords(List<Media> media) throws FileNotFoundException {
Set<String> 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<String, Set<Media>> index) {
System.out.println("Enter query:");
System.out.print("> ");
String query = console.nextLine();
Optional<Set<Media>> 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> 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<Set<Media>> search(Map<String, Set<Media>> index, String query) {
Optional<Set<Media>> 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<Book> loadBooks() throws FileNotFoundException {
List<Book> 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;
}
}