diff options
| author | pants <niklasjo2004@gmail.com> | 2026-09-21 14:16:34 -0700 |
|---|---|---|
| committer | pants <niklasjo2004@gmail.com> | 2026-09-21 14:16:34 -0700 |
| commit | 81ba9eb66f8284d8760012973f84cdbe617ceb94 (patch) | |
| tree | 466432644e9e3fd66ee183ec53a1f10abb1b7faa /search-engine/SearchClient.java | |
Diffstat (limited to 'search-engine/SearchClient.java')
| -rw-r--r-- | search-engine/SearchClient.java | 213 |
1 files changed, 213 insertions, 0 deletions
diff --git a/search-engine/SearchClient.java b/search-engine/SearchClient.java new file mode 100644 index 0000000..20dbe6d --- /dev/null +++ b/search-engine/SearchClient.java @@ -0,0 +1,213 @@ +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; + } +} |
