diff --git a/app/build.gradle b/app/build.gradle index 69819c3f6..56a4694af 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,20 +12,24 @@ dependencies { compile 'ch.acra:acra:4.7.0' compile 'org.mediawiki:api:1.3' compile 'commons-codec:commons-codec:1.10' - compile "com.android.support:support-v4:${project.supportLibVersion}" - compile "com.android.support:appcompat-v7:${project.supportLibVersion}" - compile "com.android.support:design:${project.supportLibVersion}" - compile 'com.google.code.gson:gson:2.8.0' - compile "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION" compile 'com.github.pedrovgs:renderers:3.3.3' - annotationProcessor "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION" + compile 'com.google.code.gson:gson:2.8.0' compile 'com.jakewharton.timber:timber:4.5.1' - compile 'com.squareup.okhttp3:okhttp:3.8.1' - compile 'com.squareup.okio:okio:1.13.0' + compile 'info.debatty:java-string-similarity:0.24' compile ('com.mapbox.mapboxsdk:mapbox-android-sdk:5.1.0@aar'){ transitive=true } + compile "com.android.support:support-v4:${project.supportLibVersion}" + compile "com.android.support:appcompat-v7:${project.supportLibVersion}" + compile "com.android.support:design:${project.supportLibVersion}" + + compile "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION" + annotationProcessor "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION" + + compile 'com.squareup.okhttp3:okhttp:3.8.1' + compile 'com.squareup.okio:okio:1.13.0' + compile 'io.reactivex.rxjava2:rxandroid:2.0.1' // Because RxAndroid releases are few and far between, it is recommended you also // explicitly depend on RxJava's latest version for bug fixes and new features. diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java b/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java index f654dca7d..20802f6b7 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java @@ -25,6 +25,7 @@ import com.pedrogomez.renderers.RVRendererAdapter; import java.util.ArrayList; import java.util.Calendar; +import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -36,6 +37,7 @@ import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.R; import fr.free.nrw.commons.data.Category; import fr.free.nrw.commons.upload.MwVolleyApi; +import fr.free.nrw.commons.utils.StringSortingUtils; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; @@ -107,7 +109,7 @@ public class CategorizationFragment extends Fragment { RxTextView.textChanges(categoriesFilter) .takeUntil(RxView.detaches(categoriesFilter)) - .debounce(300, TimeUnit.MILLISECONDS) + .debounce(500, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribe(filter -> updateCategoryList(filter.toString())); return rootView; @@ -200,6 +202,7 @@ public class CategorizationFragment extends Fragment { ) .filter(categoryItem -> !containsYear(categoryItem.getName())) .distinct() + .sorted(sortBySimilarity(filter)) .observeOn(AndroidSchedulers.mainThread()) .subscribe( s -> categoriesAdapter.add(s), Timber::e, () -> { @@ -221,6 +224,12 @@ public class CategorizationFragment extends Fragment { ); } + private Comparator sortBySimilarity(final String filter) { + Comparator stringSimilarityComparator = StringSortingUtils.sortBySimilarity(filter); + return (firstItem, secondItem) -> stringSimilarityComparator + .compare(firstItem.getName(), secondItem.getName()); + } + private List getStringList(List input) { List output = new ArrayList<>(); for (CategoryItem item : input) { diff --git a/app/src/main/java/fr/free/nrw/commons/utils/StringSortingUtils.java b/app/src/main/java/fr/free/nrw/commons/utils/StringSortingUtils.java new file mode 100644 index 000000000..03b1469e0 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/utils/StringSortingUtils.java @@ -0,0 +1,46 @@ +package fr.free.nrw.commons.utils; + +import java.util.Comparator; + +import info.debatty.java.stringsimilarity.Levenshtein; + +public class StringSortingUtils { + + private StringSortingUtils() { + //no-op + } + + /** + * Returns Comparator for sorting strings by its similarity with Levenshtein + * algorithm. By using this Comparator we get results from the highest to + * the lowest match. + * + * @param filter pattern to compare similarity + * @return Comparator with string similarity + */ + + public static Comparator sortBySimilarity(final String filter) { + return (firstItem, secondItem) -> { + double firstItemSimilarity = calculateSimilarity(firstItem, filter); + double secondItemSimilarity = calculateSimilarity(secondItem, filter); + return (int) Math.signum(secondItemSimilarity - firstItemSimilarity); + }; + } + + private static double calculateSimilarity(String firstString, String secondString) { + String longer = firstString.toLowerCase(); + String shorter = secondString.toLowerCase(); + + if (firstString.length() < secondString.length()) { + longer = secondString; + shorter = firstString; + } + int longerLength = longer.length(); + if (longerLength == 0) { + return 1.0; + } + + double distanceBetweenStrings = new Levenshtein().distance(longer, shorter); + return (longerLength - distanceBetweenStrings) / (double) longerLength; + } +} \ No newline at end of file diff --git a/app/src/test/java/fr/free/nrw/commons/utils/StringSortingUtilsTest.java b/app/src/test/java/fr/free/nrw/commons/utils/StringSortingUtilsTest.java new file mode 100644 index 000000000..2fc2fac88 --- /dev/null +++ b/app/src/test/java/fr/free/nrw/commons/utils/StringSortingUtilsTest.java @@ -0,0 +1,40 @@ +package fr.free.nrw.commons.utils; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.junit.Assert; +import org.junit.Test; + +public class StringSortingUtilsTest { + + @Test + public void testSortingNumbersBySimilarity() throws Exception { + List actualList = Arrays.asList("1234567", "4567", "12345", "123", "1234"); + List expectedList = Arrays.asList("1234", "12345", "123", "1234567", "4567"); + + Collections.sort(actualList, StringSortingUtils.sortBySimilarity("1234")); + Assert.assertEquals(expectedList, actualList); + } + + @Test + public void testSortingTextBySimilarity() throws Exception { + List actualList = Arrays.asList("The quick brown fox", + "quick brown fox", + "The", + "The quick ", + "The fox", + "brown fox", + "fox"); + List expectedList = Arrays.asList("The", + "The fox", + "The quick ", + "The quick brown fox", + "quick brown fox", + "brown fox", + "fox"); + + Collections.sort(actualList, StringSortingUtils.sortBySimilarity("The")); + Assert.assertEquals(expectedList, actualList); + } +} \ No newline at end of file