forked from nus-cs2103-AY1920S1/addressbook-level3
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
653 additions
and
105 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
112 changes: 112 additions & 0 deletions
112
src/main/java/seedu/address/logic/search/AutoComplete.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
package seedu.address.logic.search; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.InputStream; | ||
import java.io.InputStreamReader; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
|
||
import seedu.address.MainApp; | ||
import seedu.address.model.autocomplete.AutoCompleteModel; | ||
import seedu.address.model.autocomplete.Word; | ||
|
||
/** | ||
* Main controller class to execute the searching logic | ||
*/ | ||
public class AutoComplete { | ||
|
||
private static final int numOfSug = 10; | ||
|
||
private static AutoCompleteModel acModel; | ||
private static Word[] matchedResults; | ||
private static boolean displayWeights; | ||
|
||
public AutoComplete() { | ||
matchedResults = new Word[numOfSug]; | ||
displayWeights = false; | ||
} | ||
|
||
/** | ||
* Read word storage from txt | ||
* | ||
* @param is path for the library of words | ||
* @return the suggestions to be shown in textField | ||
*/ | ||
private static Word[] readWordsFromFile(InputStream is) { | ||
Word[] queries = null; | ||
try { | ||
is.mark(0); | ||
BufferedReader reader = new BufferedReader(new InputStreamReader(is)); | ||
int lines = 0; | ||
while (reader.readLine() != null) { | ||
lines++; | ||
} | ||
is.reset(); | ||
queries = new Word[lines]; | ||
for (int i = 0; i < lines; i++) { | ||
String line = reader.readLine(); | ||
if (line == null) { | ||
System.err.println("Could not read line " + (i + 1)); | ||
System.exit(1); | ||
} | ||
int tab = line.indexOf('\t'); | ||
if (tab == -1) { | ||
System.err.println("No tab character in line " + (i + 1)); | ||
System.exit(1); | ||
} | ||
long weight = Long.parseLong(line.substring(0, tab).trim()); | ||
String query = line.substring(tab + 1); | ||
queries[i] = new Word(query, weight); | ||
} | ||
reader.close(); | ||
} catch (Exception e) { | ||
System.err.println("Could not read or parse input file "); | ||
e.printStackTrace(); | ||
System.exit(1); | ||
} | ||
|
||
return queries; | ||
} | ||
|
||
/** | ||
* Initialize an autocomplete model | ||
*/ | ||
public static void initAc() { | ||
InputStream in = MainApp.class.getResourceAsStream("/data/vocabulary.txt"); | ||
Word[] data = readWordsFromFile(in); | ||
acModel = new AutoCompleteModel(data); | ||
} | ||
|
||
/** | ||
* Makes a call to the implementation of AutoCompleteModel to get | ||
* suggestions for the currently entered text. | ||
* | ||
* @param text string to search for | ||
*/ | ||
public static List<String> getSuggestions(String text) { | ||
|
||
List<String> suggestions = new ArrayList<>(); | ||
// don't search for suggestions if there is no input | ||
if (text.length() > 0) { | ||
// get all matching words | ||
Word[] allResults = acModel.allMatches(text); | ||
if (allResults == null) { | ||
throw new NullPointerException("allMatches() is null"); | ||
} | ||
|
||
if (allResults.length > numOfSug) { | ||
matchedResults = Arrays.copyOfRange(allResults, 0, numOfSug); | ||
} else { | ||
matchedResults = allResults; | ||
} | ||
|
||
if (!displayWeights) { | ||
for (Word matchedResult : matchedResults) { | ||
suggestions.add(matchedResult.getQuery()); | ||
} | ||
} | ||
} | ||
return suggestions; | ||
} | ||
} |
99 changes: 99 additions & 0 deletions
99
src/main/java/seedu/address/logic/search/BinarySearch.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package seedu.address.logic.search; | ||
|
||
import java.util.Comparator; | ||
|
||
/** | ||
* Utility class to find the first and the last index of key using Binary Search. | ||
*/ | ||
public class BinarySearch { | ||
|
||
/** | ||
* Searches the specified array for the specified value using modification of binary | ||
* search algorithm and returns the index of the first key in list[] that equals the search key, | ||
* or -1 if no such key were found. | ||
* | ||
* @param list - the array of keys to be searched | ||
* @param key - the value to be searched for | ||
* @param comparator - the comparator by which array is ordered | ||
* @return - the index of the last key in list that equals the search key, -1 if not found | ||
* @throws NullPointerException - if list is null | ||
* @throws NullPointerException - if key is null | ||
* @throws NullPointerException - if comparator is null | ||
*/ | ||
public static <T> int firstIndexOf(T[] list, T key, Comparator<T> comparator) { | ||
if (list == null || key == null || comparator == null) { | ||
throw new IllegalArgumentException(); | ||
} | ||
|
||
if (list.length == 0) { | ||
return -1; | ||
} | ||
if (comparator.compare(list[0], key) == 0) { | ||
return 0; | ||
} | ||
|
||
int lo = 0; | ||
int hi = list.length - 1; | ||
|
||
while (lo <= hi) { | ||
int mid = lo + (hi - lo) / 2; | ||
int cmp = comparator.compare(list[mid], key); | ||
|
||
if (cmp >= 1) { | ||
hi = mid - 1; | ||
} else if (cmp <= -1) { | ||
lo = mid + 1; | ||
} else if (comparator.compare(list[mid - 1], list[mid]) == 0) { | ||
hi = mid - 1; | ||
} else { | ||
return mid; | ||
} | ||
} | ||
return -1; | ||
} | ||
|
||
/** | ||
* Searches the specified array for the specified value using modification of binary | ||
* search algorithm and returns the index of the last key in list[] that equals the search key, | ||
* or -1 if no such key were found. | ||
* | ||
* @param list - the array of keys to be searched | ||
* @param key - the value to be searched for | ||
* @param comparator - the comparator by which array is ordered | ||
* @return - the index of the last key in list that equals the search key, -1 if not found | ||
* @throws NullPointerException - if list is null | ||
* @throws NullPointerException - if key is null | ||
* @throws NullPointerException - if comparator is null | ||
*/ | ||
public static <T> int lastIndexOf(T[] list, T key, Comparator<T> comparator) { | ||
if (list == null || key == null || comparator == null) { | ||
throw new IllegalArgumentException(); | ||
} | ||
|
||
if (list.length == 0) { | ||
return -1; | ||
} | ||
if (comparator.compare(list[list.length - 1], key) == 0) { | ||
return list.length - 1; | ||
} | ||
|
||
int lo = 0; | ||
int hi = list.length - 1; | ||
|
||
while (lo <= hi) { | ||
int mid = lo + (hi - lo) / 2; | ||
int cmp = comparator.compare(list[mid], key); | ||
|
||
if (cmp >= 1) { | ||
hi = mid - 1; | ||
} else if (cmp <= -1) { | ||
lo = mid + 1; | ||
} else if (comparator.compare(list[mid + 1], list[mid]) == 0) { | ||
lo = mid + 1; | ||
} else { | ||
return mid; | ||
} | ||
} | ||
return -1; | ||
} | ||
} |
59 changes: 59 additions & 0 deletions
59
src/main/java/seedu/address/model/autocomplete/AutoCompleteModel.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package seedu.address.model.autocomplete; | ||
|
||
import java.util.Arrays; | ||
|
||
import seedu.address.logic.search.BinarySearch; | ||
|
||
/** | ||
* Given the data and the query, this class is for searching for words in the data | ||
* starting with the given query and returns the array in descending order w.r.t weight. | ||
*/ | ||
public class AutoCompleteModel { | ||
|
||
private final Word[] dataCopy; | ||
|
||
/** | ||
* Initialize an AutoCompleteModel using array of words. | ||
* | ||
* @param data - the array of queries | ||
* @throws NullPointerException - if queries == null | ||
*/ | ||
public AutoCompleteModel(Word[] data) { | ||
if (data == null) { | ||
throw new NullPointerException("Data cannot be null"); | ||
} | ||
dataCopy = Arrays.copyOf(data, data.length); | ||
Arrays.sort(dataCopy); | ||
} | ||
|
||
/** | ||
* Return all words that start with the given query, in descending order of weight. | ||
* Query should be non-null. | ||
* | ||
* @param query - query to be searched for | ||
* @return - array of words that match the given query in descending order | ||
* @throws NullPointerException - if query == null | ||
*/ | ||
public Word[] allMatches(String query) { | ||
if (query == null) { | ||
throw new NullPointerException("Query cannot be null"); | ||
} | ||
Word queryWord = new Word(query, 100); | ||
int firstIndex = BinarySearch.firstIndexOf(dataCopy, queryWord, Word.compareCharSeq(query.length())); | ||
|
||
// NOT FOUND | ||
if (firstIndex == -1) { | ||
return new Word[0]; | ||
} | ||
int lastIndex = BinarySearch.lastIndexOf(dataCopy, queryWord, Word.compareCharSeq(query.length())); | ||
|
||
int range = lastIndex - firstIndex + 1; | ||
Word[] allMatches = new Word[range]; | ||
int j = 0; | ||
for (int i = firstIndex; i <= lastIndex; i++) { | ||
allMatches[j++] = dataCopy[i]; | ||
} | ||
Arrays.sort(allMatches, Word.compareWeightDescending()); | ||
return allMatches; | ||
} | ||
} |
114 changes: 114 additions & 0 deletions
114
src/main/java/seedu/address/model/autocomplete/Word.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
package seedu.address.model.autocomplete; | ||
|
||
import java.util.Comparator; | ||
|
||
/** | ||
* Word is an immutable data type that represents an autocomplete object. | ||
* A query string and an associated integer weight(optional) | ||
* Represents single search word with the query and the weight. | ||
*/ | ||
public class Word implements Comparable<Word> { | ||
|
||
private final String query; | ||
private final long weight; | ||
|
||
public Word() { | ||
this.query = null; | ||
this.weight = -1; | ||
} | ||
|
||
/** | ||
* Initializes a word with the specified query and the weight. | ||
* Word should be non-null and weight must be non-negative. | ||
* | ||
* @param query - the query to be searched for | ||
* @param weight - the corresponding weight of the query | ||
* @throws NullPointerException - if query == null | ||
* @throws IllegalArgumentException - if weight < 0 | ||
*/ | ||
public Word(String query, long weight) { | ||
if (query == null) { | ||
throw new NullPointerException("Word can't be null"); | ||
} | ||
if (weight < 0) { | ||
throw new IllegalArgumentException("Weight must be non-negative"); | ||
} | ||
|
||
this.query = query; | ||
this.weight = weight; | ||
} | ||
|
||
/** | ||
* Returns comparator to compare words in lexicographic order using | ||
* only the first r characters of each query. Parameter r should be non-negative. | ||
* | ||
* @return -1 if first r characters of this are less than the first r characters of that | ||
* 0 if first r characters of this are equal to the first r characters of that | ||
* 1 if first r characters of this are larger than to the first r characters of that | ||
*/ | ||
public static Comparator<Word> compareCharSeq(int len) { | ||
if (len < 0) { | ||
throw new IllegalArgumentException("length must be non-negative, but was " + len); | ||
} | ||
return (t1, t2) -> { | ||
String q1 = truncateTarget(t1.query, len); | ||
String q2 = truncateTarget(t2.query, len); | ||
return Integer.compare(q1.compareToIgnoreCase(q2), 0); | ||
}; | ||
} | ||
|
||
/** | ||
* For comparing first R chars | ||
* | ||
* @param targetString Desc string to compare | ||
* @param len | ||
* @return | ||
*/ | ||
private static String truncateTarget(String targetString, int len) { | ||
final int endIndex = Math.min(targetString.length(), len); | ||
return targetString.substring(0, endIndex); | ||
} | ||
|
||
/** | ||
* Returns comparator for comparing words using their corresponding weights. | ||
*/ | ||
public static Comparator<Word> compareWeightDescending() { | ||
return Comparator.comparingLong(Word::getWeight).reversed(); | ||
} | ||
|
||
public long getWeight() { | ||
return weight; | ||
} | ||
|
||
public String getQuery() { | ||
return query; | ||
} | ||
|
||
/** | ||
* Returns a string representation of this word in the following format: | ||
* the weight, followed by a tab, followed by the query. | ||
*/ | ||
@Override | ||
public String toString() { | ||
return weight + "\t" + query; | ||
} | ||
|
||
/** | ||
* Compares two words in lexicographic order. | ||
* | ||
* @return -1 if this is (less than) that | ||
* 0 if this (is the same as) that | ||
* 1 if this (is larger than) that | ||
*/ | ||
@Override | ||
public int compareTo(Word word) { | ||
int cmp = this.query.toLowerCase().compareTo(word.query.toLowerCase()); | ||
if (cmp <= -1) { | ||
return -1; | ||
} else if (cmp >= 1) { | ||
return 1; | ||
} else { | ||
return 0; | ||
} | ||
} | ||
} |
Oops, something went wrong.