Skip to content
This repository has been archived by the owner on Oct 27, 2022. It is now read-only.

First skeleton of the additional coding task #8

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gCoffee/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ android {
}

dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation files('libs/commons-text-1.9.jar')
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
Expand Down
69 changes: 61 additions & 8 deletions gCoffee/app/src/main/java/com/mborowiec/gcoffee/Order.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
package com.mborowiec.gcoffee;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
Expand All @@ -15,13 +11,18 @@
import android.widget.ListView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

import com.google.android.material.bottomnavigation.BottomNavigationView;

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Order extends AppCompatActivity implements AdapterView.OnItemClickListener {
public class Order extends AppCompatActivity implements AdapterView.OnItemClickListener, OrderMatch{

@Override
protected void onCreate(Bundle savedInstanceState) {
Expand Down Expand Up @@ -142,14 +143,66 @@ private void handleDeepLinks(Uri data) {
}

String[] coffeeList = this.getResources().getStringArray(R.array.coffee_list);
boolean contains = menuContainsItem(coffee, coffeeList);
String menuMatch = findMatch(coffee, coffeeList);

if (contains) showConfirmationDialog(coffee);
else {
if (menuMatch == null) {
Toast.makeText(this, getString(R.string.not_on_menu, coffee),
Toast.LENGTH_SHORT).show();
}
else {
showConfirmationDialog(menuMatch);
}
}

/**
* Looks for the closest match (with closest distance) in the menu.
* @param order an input with potential typos
* @return the closest order match
*/
public String findMatch(String order, String[] menu) {
//TODO: set treshold for Levenshtein distance?

if (order == null) {
return null;
}

StringUtil util = new StringUtil();
String normalizedOrder = util.normalize(order);

// Return null if the string is empty after normalizing
if (normalizedOrder.equals("")) {
return null;
}

if (menuContainsItem(normalizedOrder, menu)) {
return normalizedOrder;
}
else {
int[] distances = new int[menu.length];

// Check Levenshtein distances between the order and menu items
for (int i = 0; i < menu.length; i++) {
String normalizedMenuItem = util.normalize(menu[i]);
int distance = util.calculateLevenshteinDistance(normalizedOrder,normalizedMenuItem);
distances[i] = distance;
}

// Find the item with the smallest distance (most likely to be a match)
int indexMin = 0;
int min = distances[indexMin];

for (int i = 1; i < distances.length; i++){
if (distances[i] < min) {
min = distances[i];
indexMin = i;
}
}
// Return the menu item with smallest distance
return menu[indexMin];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When happens if you pass an empty array as your menu?

}
}

//TODO: removing "fluff" from the input
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.mborowiec.gcoffee;

public interface OrderMatch {

public boolean menuContainsItem(String item, String[] menu);

public String findMatch(String order, String[] menu);
}
33 changes: 33 additions & 0 deletions gCoffee/app/src/main/java/com/mborowiec/gcoffee/StringUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.mborowiec.gcoffee;

import org.apache.commons.text.similarity.LevenshteinDistance;

public class StringUtil {

/**
* Normalizes the string by removing punctuation, removing whitespaces
* and making the string lowercase.
* @param input input string
* @return a normalized string
*/
public String normalize(String input) {
String normalized = input.replaceAll("[^a-zA-Z ]" , "")
.replaceAll("\\s+", "")
.toLowerCase();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some strings to test/consider:
"Latte "
"latté"


return normalized;
}

/**
* Calculates the Levenshtein distance between two strings
* @param str1 first input string
* @param str2 second input string
* @return distance between strings
*/
public Integer calculateLevenshteinDistance(String str1, String str2) {
LevenshteinDistance levenshteinDistance = new LevenshteinDistance();
int distance = levenshteinDistance.apply(str1, str2);

return distance;
}
}
60 changes: 60 additions & 0 deletions gCoffee/app/src/test/java/com/mborowiec/gcoffee/OrderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,64 @@ public void testCheckMenuNull() {
boolean test = testClass.menuContainsItem(null, coffeeList);
assertFalse(test);
}

@Test
public void testFindMatchExactMatch() {
String testString = testClass.findMatch("latte", coffeeList);
assertEquals("latte", testString);
}

@Test
public void testFindMatchSimpleTypo() {
Copy link

@quantumFeline quantumFeline Sep 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about giving more descriptive test names?.. e.g. "whitespaceMissing", "middleLetterMissing", "lastLetterMissing" etc. From my experience, it is helpful in case when some tests start failing :)

String testString = testClass.findMatch("flatwhite", coffeeList);
assertEquals("Flat White", testString);
}

@Test
public void testFindMatchSimpleTypo1() {
String testString = testClass.findMatch("capuccino", coffeeList);
assertEquals("Cappuccino", testString);
}

@Test
public void testFindMatchSimpleTypo2() {
String testString = testClass.findMatch("american", coffeeList);
assertEquals("Americano", testString);
}

@Test
public void testFindMatchSimpleTypo3() {
String testString = testClass.findMatch("fatwhite", coffeeList);
assertEquals("Flat White", testString);
}

@Test
public void testFindMatchSimpleTypo4() {
String testString = testClass.findMatch("mcha", coffeeList);
assertEquals("Mocha", testString);
}

@Test
public void testFindMatchNull() {
String testString = testClass.findMatch(null, coffeeList);
assertEquals(null, testString);
}

@Test
public void testFindMatchEmptyString() {
String testString = testClass.findMatch("", coffeeList);
assertEquals(null, testString);
}

@Test
public void testFindMatchWhitespace() {
String testString = testClass.findMatch(" ", coffeeList);
assertEquals(null, testString);
}
//The case not handled yet - returns latte
@Test
public void testFindMatchNotOnMenu() {
String testString = testClass.findMatch("black tea", coffeeList);
assertEquals(null, testString);
}
}