Skip to content

Commit

Permalink
Merge pull request #72 from AY1920S1-CS2103-T14-4/Autocomplete_ZH
Browse files Browse the repository at this point in the history
AutoComplete V1
  • Loading branch information
Cary-Xx committed Oct 23, 2019
2 parents 9026bab + f44de59 commit 0c00d99
Show file tree
Hide file tree
Showing 23 changed files with 787 additions and 108 deletions.
36 changes: 35 additions & 1 deletion docs/DeveloperGuide.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,41 @@ image::CommitActivityDiagram.png[]
** Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as `HistoryManager` now needs to do two different things.
// end::undoredo[]

// end::dataencryption[]
=== AutoComplete Feature
==== Implementation
Autocomplete is facilitated by several parts.
The logic part is implemented through `java.seedu.address.logic.search` package which contains `AutoComplete` and
`BinarySearch`.

The model is constructed through `java.seedu.address.model.autocomplete` which contains `AutoCompleteModel` and `Word`.

The Ui part is implemented through `java.seedu.address.ui.QueryCard`on top of `CommandBox`.

Given below is an example usage scenario and how the autocomplete mechanism behaves at each step. (p.s. details are
omitted)

Step 1. The user launch MYMorise and the user will be prompted to enter a command as shown in the command box.

Step 2. User enter `a` and the listener is triggered. Then `AutoComplete#initAc()` and
`AutoComplete#getSuggestions()` is invoked.

Step 3. `initAc()` calls `AutoComplete#readWordsFromFile()` which reads the vocabulary from our local dictionary to get
the "database and then construct an `AutocompleteModel` with the vocabulary read"

Step 4. `getSuggestions(input)` calls `AutocompleteModel#allMatches()` which utilises the improved version of binary
search algorithm `BinarySearch`. The algorithm will return the first and last index of potential matched results.
Since the result is based on a pre-order for sorting, all the words inside this range will be the qualify ones.

Step 5. The listview of `QueryCard` will be updated based on the words and weights given and attached to the
`TextField`.

The following sequence diagram shows how autocomplete operation works:

image::AutocompleteSequenceDiagram.png[]

The following activity diagram summarizes what happens when a user enter something new.

image::AutocompleteActivityDiagram.png[]

=== Logging

Expand Down
14 changes: 14 additions & 0 deletions docs/diagrams/AutocompleteActivityDiagram.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@startuml

(*) --> "user input"
if "listener detects input change" then
--> [Yes] "initialize AC model"
--> "get suggestions based on truncated input"
--> "display the result on listview"
--> (*)
else
--> [No] "display empty listview"
--> (*)
endif

@enduml
72 changes: 72 additions & 0 deletions docs/diagrams/AutocompleteSequenceDiagram.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
@startuml
!include style.puml

box Ui UI_COLOR_T1
participant ":CommandBox" as CommandBox UI_COLOR
'participant ":QueryCard" as QueryCard UI_COLOR
'participant ":TextField" as TextField UI_COLOR
end box

box Logic LOGIC_COLOR_T1
participant ":AutoComplete" as AutoComplete LOGIC_COLOR
participant ":BinarySearch" as BinarySearch LOGIC_COLOR
end box

box Model MODEL_COLOR_T1
participant ":AutoCompleteModel" as AutoCompleteModel MODEL_COLOR
participant ":Word" as Word MODEL_COLOR
end box

[-> CommandBox : autoCompleteListener()
activate CommandBox

[-> CommandBox : userInput

CommandBox -> AutoComplete : initAc()
activate AutoComplete

AutoComplete -> AutoComplete : importDictionary(file)
AutoComplete -> AutoComplete : data

create AutoCompleteModel
AutoComplete -> AutoCompleteModel : data
activate AutoCompleteModel

AutoCompleteModel -> AutoComplete
deactivate AutoCompleteModel
deactivate AutoComplete

CommandBox -> AutoComplete : getSuggestions(userInput)
activate AutoComplete

AutoComplete -> AutoCompleteModel : allMatches(userInput)
activate AutoCompleteModel

create Word
AutoCompleteModel -> Word : userInput, weight
activate Word

Word -> AutoCompleteModel
deactivate Word

AutoCompleteModel -> BinarySearch : firstIndexOf(userInput)
activate BinarySearch

BinarySearch -> AutoCompleteModel : index1
deactivate BinarySearch

AutoCompleteModel -> BinarySearch : lastIndexOf(userInput)
activate BinarySearch

BinarySearch -> AutoCompleteModel : index2
deactivate BinarySearch

AutoCompleteModel -> AutoComplete : matchedResults
deactivate AutoCompleteModel

AutoComplete -> CommandBox : matchedResults
deactivate AutoComplete

CommandBox ->[ : display()
deactivate CommandBox
@enduml
Binary file added docs/images/AutocompleteActivityDiagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/AutocompleteSequenceDiagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 1 addition & 2 deletions src/main/java/seedu/address/MainApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,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, 2, 1, false);
private static final Logger logger = LogsCenter.getLogger(MainApp.class);

protected Ui ui;
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[] importDictionary(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 = importDictionary(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;
}
}
1 change: 0 additions & 1 deletion src/main/java/seedu/address/model/ModelManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -247,5 +247,4 @@ public void updateFilteredBudgetList(Predicate<Budget> predicate) {
requireNonNull(predicate);
filteredBudgets.setPredicate(predicate);
}

}
Loading

0 comments on commit 0c00d99

Please sign in to comment.