Skip to content

Commit

Permalink
Merge pull request #71 from tingxuanp/add-partial-search
Browse files Browse the repository at this point in the history
Implement partial search for Find command
  • Loading branch information
DanzaSeah authored Oct 10, 2024
2 parents f7696b4 + 0b8bf83 commit 146b66b
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 8 deletions.
22 changes: 22 additions & 0 deletions src/main/java/seedu/address/commons/util/StringUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,28 @@ public static boolean containsWordIgnoreCase(String sentence, String word) {
.anyMatch(preppedWord::equalsIgnoreCase);
}

/**
* Returns true if the {@code sentence} contains the {@code word}.
* Ignores case, and only a partial match is required.
* <br>examples:<pre>
* containsPartialWordIgnoreCase("ABc def", "abc") == true
* containsPartialWordIgnoreCase("ABc def", "DEF") == true
* containsPartialWordIgnoreCase("ABc def", "AB") == true
* containsPartialWordIgnoreCase("ABc def", "cd") == false // no partial match with a word
* </pre>
* @param sentence cannot be null
* @param word cannot be null, cannot be empty
*/
public static boolean containsPartialWordIgnoreCase(String sentence, String word) {
requireNonNull(sentence);
requireNonNull(word);

String preppedSentence = sentence.toLowerCase();
String preppedWord = word.trim().toLowerCase();
checkArgument(!preppedWord.isEmpty(), "Word parameter cannot be empty");
return preppedSentence.contains(preppedWord);
}

/**
* Returns a detailed message of the t, including the stack trace.
*/
Expand Down
15 changes: 11 additions & 4 deletions src/main/java/seedu/address/logic/commands/FindCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
import static java.util.Objects.requireNonNull;

import seedu.address.commons.util.ToStringBuilder;
import seedu.address.logic.Messages;
import seedu.address.model.Model;
import seedu.address.model.person.NameContainsKeywordsPredicate;

/**
* Finds and lists all persons in address book whose name contains any of the argument keywords.
* Keyword matching is case insensitive.
* Keyword matching is case-insensitive and allows partial matching.
*/
public class FindCommand extends Command {

Expand All @@ -20,6 +19,10 @@ public class FindCommand extends Command {
+ "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
+ "Example: " + COMMAND_WORD + " alice bob charlie";

public static final String MESSAGE_FIND_PERSON_SUCCESS = "Search for \"%s\" was successful. Showing results:";

public static final String MESSAGE_FIND_PERSON_UNSUCCESSFUL = "No contacts found.";

private final NameContainsKeywordsPredicate predicate;

public FindCommand(NameContainsKeywordsPredicate predicate) {
Expand All @@ -30,8 +33,12 @@ public FindCommand(NameContainsKeywordsPredicate predicate) {
public CommandResult execute(Model model) {
requireNonNull(model);
model.updateFilteredPersonList(predicate);
return new CommandResult(
String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size()));

if (!model.getFilteredPersonList().isEmpty()) {
return new CommandResult(String.format(MESSAGE_FIND_PERSON_SUCCESS, predicate.getDisplayString()));
} else {
return new CommandResult(MESSAGE_FIND_PERSON_UNSUCCESSFUL);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public NameContainsKeywordsPredicate(List<String> keywords) {
@Override
public boolean test(Person person) {
return keywords.stream()
.anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword));
.anyMatch(keyword -> StringUtil.containsPartialWordIgnoreCase(person.getName().fullName, keyword));
}

@Override
Expand All @@ -41,4 +41,8 @@ public boolean equals(Object other) {
public String toString() {
return new ToStringBuilder(this).add("keywords", keywords).toString();
}

public String getDisplayString() {
return String.join(", ", keywords);
}
}
88 changes: 88 additions & 0 deletions src/test/java/seedu/address/commons/util/StringUtilTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,94 @@ public void containsWordIgnoreCase_validInputs_correctResult() {
assertTrue(StringUtil.containsWordIgnoreCase("AAA bBb ccc bbb", "bbB"));
}

//---------------- Tests for containsPartialWordIgnoreCase --------------------------------------
/*
* Invalid equivalence partitions for word: null, empty, multiple words
* Invalid equivalence partitions for sentence: null
* The four test cases below test one invalid input at a time.
*/

@Test
public void containsPartialWordIgnoreCase_nullWord_throwsNullPointerException() {
assertThrows(NullPointerException.class, () ->
StringUtil.containsPartialWordIgnoreCase("typical sentence", null));
}

@Test
public void containsPartialWordIgnoreCase_emptyWord_throwsIllegalArgumentException() {
assertThrows(IllegalArgumentException.class, "Word parameter cannot be empty", () ->
StringUtil.containsPartialWordIgnoreCase("typical sentence", " "));
}

@Test
public void containsPartialWordIgnoreCase_nullSentence_throwsNullPointerException() {
assertThrows(NullPointerException.class, () ->
StringUtil.containsPartialWordIgnoreCase(null, "abc"));
}

/*
* Valid equivalence partitions for word:
* - any word
* - word containing symbols/numbers
* - word with leading/trailing spaces
*
* Valid equivalence partitions for sentence:
* - empty string
* - one word
* - multiple words
* - sentence with extra spaces
*
* Possible scenarios returning true:
* - partially matches first word in sentence
* - partially matches last word in sentence
* - partially matches middle word in sentence
* - partially matches multiple words
*
* Possible scenarios returning false:
* - query word is not a partial word of any word in sentence
*
* The test method below tries to verify all above.
*/

@Test
public void containsPartialWordIgnoreCase_validInputs_correctResult() {

// Empty sentence
assertFalse(StringUtil.containsPartialWordIgnoreCase("", "abc")); // Boundary case
assertFalse(StringUtil.containsPartialWordIgnoreCase(" ", "123"));

// Matches a full word in the sentence, different upper/lower case letters
assertTrue(StringUtil.containsPartialWordIgnoreCase("aaa bBb ccc", "Bbb")); // First word (boundary case)
assertTrue(StringUtil.containsPartialWordIgnoreCase("aaa bBb ccc@1", "CCc@1")); // Last word (boundary case)
assertTrue(StringUtil.containsPartialWordIgnoreCase(" AAA bBb ccc ", "aaa")); // Sentence has extra spaces
assertTrue(StringUtil.containsPartialWordIgnoreCase("Aaa", "aaa")); // Only one word in sentence (boundary case)
assertTrue(StringUtil.containsPartialWordIgnoreCase("aaa bbb ccc", " ccc ")); // Leading/trailing spaces

// Matches multiple full words in sentence
assertTrue(StringUtil.containsPartialWordIgnoreCase("AAA bBb ccc bbb", "bbB"));
assertTrue(StringUtil.containsPartialWordIgnoreCase("AAA bBb ccc bbb ccc", "cCc"));

// Matches partial words in the sentence, different upper/lower case letters
assertTrue(StringUtil.containsPartialWordIgnoreCase("aaa bbb ccc", "bb"));
assertTrue(StringUtil.containsPartialWordIgnoreCase("aaa bbb ccc", "Bb"));
assertTrue(StringUtil.containsPartialWordIgnoreCase("aaa bbb ccc", "Aa"));
assertTrue(StringUtil.containsPartialWordIgnoreCase("aaa bbb ccc", "C"));
assertTrue(StringUtil.containsPartialWordIgnoreCase("aaa bbb ccc!1", "!1")); // Symbols and numbers

// Matches multiple partial words in sentence
assertTrue(StringUtil.containsPartialWordIgnoreCase("AAA bBb ccc bbb cc", "c"));
assertTrue(StringUtil.containsPartialWordIgnoreCase("AAA bBb ccc bbb cc", "B"));
assertTrue(StringUtil.containsPartialWordIgnoreCase("AAA bbb ccc bbb cc", "B"));

// When partial words do not match - query word bigger than sentence word
assertFalse(StringUtil.containsPartialWordIgnoreCase("aaa bbb ccc", "bbbb"));

// When partial words do not match - query word does not exist as a partial match
assertFalse(StringUtil.containsPartialWordIgnoreCase("aaa bbb ccc", "ab"));
assertFalse(StringUtil.containsPartialWordIgnoreCase("aaa bbb ccc", "BA"));
}


//---------------- Tests for getDetails --------------------------------------

/*
Expand Down
41 changes: 38 additions & 3 deletions src/test/java/seedu/address/logic/commands/FindCommandTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.logic.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
import static seedu.address.logic.commands.FindCommand.MESSAGE_FIND_PERSON_SUCCESS;
import static seedu.address.logic.commands.FindCommand.MESSAGE_FIND_PERSON_UNSUCCESSFUL;
import static seedu.address.testutil.TypicalPersons.ALICE;
import static seedu.address.testutil.TypicalPersons.BENSON;
import static seedu.address.testutil.TypicalPersons.CARL;
import static seedu.address.testutil.TypicalPersons.DANIEL;
import static seedu.address.testutil.TypicalPersons.ELLE;
import static seedu.address.testutil.TypicalPersons.FIONA;
import static seedu.address.testutil.TypicalPersons.GEORGE;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;

import java.util.Arrays;
Expand Down Expand Up @@ -56,7 +61,7 @@ public void equals() {

@Test
public void execute_zeroKeywords_noPersonFound() {
String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0);
String expectedMessage = MESSAGE_FIND_PERSON_UNSUCCESSFUL;
NameContainsKeywordsPredicate predicate = preparePredicate(" ");
FindCommand command = new FindCommand(predicate);
expectedModel.updateFilteredPersonList(predicate);
Expand All @@ -66,14 +71,44 @@ public void execute_zeroKeywords_noPersonFound() {

@Test
public void execute_multipleKeywords_multiplePersonsFound() {
String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 3);
NameContainsKeywordsPredicate predicate = preparePredicate("Kurz Elle Kunz");
String expectedMessage = String.format(MESSAGE_FIND_PERSON_SUCCESS, predicate.getDisplayString());
FindCommand command = new FindCommand(predicate);
expectedModel.updateFilteredPersonList(predicate);
assertCommandSuccess(command, model, expectedMessage, expectedModel);
assertEquals(Arrays.asList(CARL, ELLE, FIONA), model.getFilteredPersonList());
}

@Test
public void execute_partialMatchKeyword_correctPersonFound() {
NameContainsKeywordsPredicate predicate = preparePredicate("ell");
String expectedMessage = String.format(MESSAGE_FIND_PERSON_SUCCESS, predicate.getDisplayString());
FindCommand command = new FindCommand(predicate);
expectedModel.updateFilteredPersonList(predicate);
assertCommandSuccess(command, model, expectedMessage, expectedModel);
assertEquals(Arrays.asList(ELLE), model.getFilteredPersonList());
}

@Test
public void execute_partialMatchKeyword_multiplePersonsFound() {
NameContainsKeywordsPredicate predicate = preparePredicate("e");
String expectedMessage = String.format(MESSAGE_FIND_PERSON_SUCCESS, predicate.getDisplayString());
FindCommand command = new FindCommand(predicate);
expectedModel.updateFilteredPersonList(predicate);
assertCommandSuccess(command, model, expectedMessage, expectedModel);
assertEquals(Arrays.asList(ALICE, BENSON, DANIEL, ELLE, GEORGE), model.getFilteredPersonList());
}

@Test
public void execute_absentPartialMatchKeyword_noPersonFound() {
NameContainsKeywordsPredicate predicate = preparePredicate("x");
String expectedMessage = MESSAGE_FIND_PERSON_UNSUCCESSFUL;
FindCommand command = new FindCommand(predicate);
expectedModel.updateFilteredPersonList(predicate);
assertCommandSuccess(command, model, expectedMessage, expectedModel);
assertEquals(Collections.emptyList(), model.getFilteredPersonList());
}

@Test
public void toStringMethod() {
NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Arrays.asList("keyword"));
Expand Down

0 comments on commit 146b66b

Please sign in to comment.