Skip to content

Commit

Permalink
Merge pull request #166 from AY2324S2-CS2103T-W12-3/19-fuzzy-search
Browse files Browse the repository at this point in the history
Implement substring search feature
  • Loading branch information
aureliony authored Apr 4, 2024
2 parents fcc3ea6 + 40bc228 commit 3f03477
Show file tree
Hide file tree
Showing 11 changed files with 320 additions and 294 deletions.
30 changes: 16 additions & 14 deletions docs/UserGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,14 +250,16 @@ Example: `asset old/hammer new/screwdriver` edits the asset `hammer`, changing i

### Finding Contacts: `find`

Finds contacts whose names, tags or assets contain any of the given keywords.
Finds contacts by names, tags or assets.

Format: `find KEYWORD [KEYWORD]...`
Format: `find QUERY`

Example: `find John` searches all contact names, tags and assets for the keyword `John`.
Example: `find John` searches all contact names, tags and assets for the query `John`.

* At least one keyword must be provided.
* Keywords are case-insensitive.
* The query is case-insensitive.
* All whitespaces in both the query and fields will be ignored.
* Each field is individually checked against the query.
* A match is found if the query is a substring of the field being checked.

--------------------------------------------------------------------------------------------------------------------

Expand Down Expand Up @@ -326,15 +328,15 @@ Furthermore, certain edits can cause the AssetBook-3 to behave in unexpected way

## Command summary

Action | Format | Example
-----------------|-------------------------------------------------------------------------------|---
**Add** | `add n/NAME p/PHONE e/EMAIL o/OFFICE [t/TAG]... [a/ASSET]...` | `add n/John Doe e/johndoe@example.com p/+12345678 a/L293D`
**Delete** | `delete INDEX` | `delete 1`
**Edit contact** | `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [o/OFFICE] [t/TAG]... [a/ASSET]...` | `edit 1 e/newemail@example.com`
**Edit asset** | `asset old/OLD_ASSET_NAME new/NEW_ASSET_NAME` | `asset old/hammer new/screwdriver`
**Find** | `find KEYWORD [KEYWORD]...` | `find John`
**Undo** | `undo` | `undo`
**Exit** | `exit` | `exit`
Action | Format | Example
-----------------|------------------------------------------------------------------------------|---
**Add** | `add n/NAME p/PHONE e/EMAIL o/OFFICE [t/TAG]... [a/ASSET]...` | `add n/John Doe e/johndoe@example.com p/+12345678 a/L293D`
**Delete** | `delete INDEX` | `delete 1`
**Edit contact** | `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [o/OFFICE] [t/TAG]... [a/ASSET]...` | `edit 1 e/newemail@example.com`
**Edit asset** | `asset old/OLD_ASSET_NAME new/NEW_ASSET_NAME` | `asset old/hammer new/screwdriver`
**Find** | `find QUERY` | `find John`
**Undo** | `undo` | `undo`
**Exit** | `exit` | `exit`

---{.double}

Expand Down
34 changes: 13 additions & 21 deletions src/main/java/seedu/address/logic/commands/FindCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,31 @@
import static java.util.Objects.requireNonNull;
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;

import java.util.Arrays;
import java.util.function.Predicate;

import seedu.address.commons.util.ToStringBuilder;
import seedu.address.logic.Messages;
import seedu.address.model.Model;
import seedu.address.model.person.PersonMatchesKeywordsPredicate;
import seedu.address.model.person.Person;
import seedu.address.model.person.PersonMatchesQueryPredicate;

/**
* Finds and lists all persons in address book whose name contains any of the argument keywords.
* Keyword matching is case insensitive.
* Finds and lists all persons in the address book such that certain fields match the predicate.
* See {@code PersonMatchesQueryPredicate}.
*/
public class FindCommand extends Command {

public static final String COMMAND_WORD = "find";

public static final String MESSAGE_USAGE = COMMAND_WORD
+ ": Finds all persons whose names, assets or tags contain any of "
+ "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n"
+ "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
+ "Example: " + COMMAND_WORD + " alice bob charlie";
+ ": Finds all persons whose names, assets or tags contains "
+ "the specified query (case-insensitive) and displays them as a list with index numbers.\n"
+ "All whitespaces are ignored.\n"
+ "Parameters: QUERY\n"
+ "Example: " + COMMAND_WORD + " alex";

private final PersonMatchesKeywordsPredicate predicate;
private final Predicate<Person> predicate;

public FindCommand(PersonMatchesKeywordsPredicate predicate) {
public FindCommand(Predicate<Person> predicate) {
this.predicate = predicate;
}

Expand All @@ -49,9 +50,7 @@ public static FindCommand of(String args) throws IllegalArgumentException {
String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
}

String[] nameKeywords = trimmedArgs.split("\\s+");

return new FindCommand(new PersonMatchesKeywordsPredicate(Arrays.asList(nameKeywords)));
return new FindCommand(new PersonMatchesQueryPredicate(trimmedArgs));
}

@Override
Expand All @@ -69,11 +68,4 @@ public boolean equals(Object other) {
return predicate.equals(otherFindCommand.predicate);
}

@Override
public String toString() {
return new ToStringBuilder(this)
.add("predicate", predicate)
.toString();
}

}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package seedu.address.model.person;

import static java.util.Objects.requireNonNull;

import java.util.function.Predicate;

/**
* Tests that a {@code Person}'s {@code Name}, {@code Tags} or {@code Assets} matches the search query.
* The algorithm removes all whitespaces and checks if the query is a substring of a field.
* The substring matching is case-insensitive.
*/
public class PersonMatchesQueryPredicate implements Predicate<Person> {

private static final String STRIP_WHITESPACE_REGEX = "\\s+";
private static final String EMPTY_STRING = "";

private final String query;

/**
* Processes the query string and constructs the object.
* @param query the query string.
* @throws NullPointerException if the query string is null.
* @throws AssertionError if the query string is empty.
*/
public PersonMatchesQueryPredicate(String query) {
requireNonNull(query);
assert !query.isEmpty();
this.query = processString(query);
}

private static String processString(String str) {
return str.toLowerCase().replaceAll(STRIP_WHITESPACE_REGEX, EMPTY_STRING);
}

//@@author rizkidelta
@Override
public boolean test(Person person) {
return doesStringMatchQuery(person.getName().toString(), query)
|| person.getTags().stream().anyMatch(tag -> doesStringMatchQuery(tag.get(), query)
|| person.getAssets().stream().anyMatch(asset -> doesStringMatchQuery(asset.get(), query)));
}

//@@author rizkidelta
private static boolean doesStringMatchQuery(String text, String query) {
requireNonNull(text);

// only need to process text, as query is already processed in the constructor
text = processString(text);
return text.contains(query);
}

@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}

// instanceof handles nulls
if (!(other instanceof PersonMatchesQueryPredicate)) {
return false;
}

PersonMatchesQueryPredicate otherPersonMatchesQueryPredicate = (PersonMatchesQueryPredicate) other;
return query.equals(otherPersonMatchesQueryPredicate.query);
}

}
2 changes: 1 addition & 1 deletion src/main/java/seedu/address/model/tag/Tag.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

/**
* Represents a Tag in the address book.
* Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)}
* Guarantees: immutable; name is valid as declared in {@link #isValid(String)}
*/
public class Tag {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@
import static seedu.address.testutil.Assert.assertThrows;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import seedu.address.commons.core.index.Index;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.AddressBook;
import seedu.address.model.Model;
import seedu.address.model.person.Person;
import seedu.address.model.person.PersonMatchesKeywordsPredicate;
import seedu.address.model.person.PersonMatchesQueryPredicate;
import seedu.address.testutil.EditPersonDescriptorBuilder;

/**
Expand Down Expand Up @@ -118,7 +117,7 @@ public static void showPersonAtIndex(Model model, Index targetIndex) {

Person person = model.getFilteredPersonList().get(targetIndex.getZeroBased());
final String[] splitName = person.getName().toString().split("\\s+");
model.updateFilteredPersonList(new PersonMatchesKeywordsPredicate(Arrays.asList(splitName[0])));
model.updateFilteredPersonList(new PersonMatchesQueryPredicate(splitName[0]));

assertEquals(1, model.getFilteredPersonList().size());
}
Expand Down
Loading

0 comments on commit 3f03477

Please sign in to comment.