Skip to content

Commit

Permalink
Merge af33fdf into b92c5c3
Browse files Browse the repository at this point in the history
  • Loading branch information
Cary-Xx committed Oct 22, 2019
2 parents b92c5c3 + af33fdf commit b88edb5
Show file tree
Hide file tree
Showing 17 changed files with 653 additions and 105 deletions.
2 changes: 1 addition & 1 deletion src/main/java/seedu/address/MainApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
*/
public class MainApp extends Application {

public static final Version VERSION = new Version(0, 6, 0, true);
public static final Version VERSION = new Version(1, 0, 1, false);

private static final Logger logger = LogsCenter.getLogger(MainApp.class);

Expand Down
112 changes: 112 additions & 0 deletions src/main/java/seedu/address/logic/search/AutoComplete.java
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 src/main/java/seedu/address/logic/search/BinarySearch.java
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;
}
}
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 src/main/java/seedu/address/model/autocomplete/Word.java
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;
}
}
}

0 comments on commit b88edb5

Please sign in to comment.