summaryrefslogtreecommitdiff
path: root/search-engine/SearchClient.java
diff options
context:
space:
mode:
authorpants <niklasjo2004@gmail.com>2026-09-21 14:16:34 -0700
committerpants <niklasjo2004@gmail.com>2026-09-21 14:16:34 -0700
commit81ba9eb66f8284d8760012973f84cdbe617ceb94 (patch)
tree466432644e9e3fd66ee183ec53a1f10abb1b7faa /search-engine/SearchClient.java
Diffstat (limited to 'search-engine/SearchClient.java')
-rw-r--r--search-engine/SearchClient.java213
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;
+ }
+}