Feature(FCLCore): better mod search
This commit is contained in:
parent
b11bc6a714
commit
808f593ee1
|
@ -17,23 +17,22 @@
|
|||
*/
|
||||
package com.tungsten.fcl.game;
|
||||
|
||||
import android.app.appsearch.SearchResult;
|
||||
import static com.tungsten.fclcore.util.Logging.LOG;
|
||||
|
||||
import com.tungsten.fcl.util.ModTranslations;
|
||||
import com.tungsten.fclcore.mod.LocalModFile;
|
||||
import com.tungsten.fclcore.mod.RemoteMod;
|
||||
import com.tungsten.fclcore.mod.RemoteModRepository;
|
||||
import com.tungsten.fclcore.util.Lang;
|
||||
import com.tungsten.fclcore.util.Pair;
|
||||
import com.tungsten.fclcore.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class LocalizedRemoteModRepository implements RemoteModRepository {
|
||||
// Yes, I'm not kidding you. The similarity check is based on these two magic number. :)
|
||||
private static final int CONTAIN_CHINESE_WEIGHT = 10;
|
||||
|
||||
private static final int INITIAL_CAPACITY = 16;
|
||||
|
@ -64,45 +63,45 @@ public abstract class LocalizedRemoteModRepository implements RemoteModRepositor
|
|||
if (count >= 3) break;
|
||||
}
|
||||
|
||||
SearchResult searchResult = getBackedRemoteModRepository().search(gameVersion, category, pageOffset, pageSize, String.join(" ", englishSearchFiltersSet), getBackedRemoteModRepositorySortOrder(), sortOrder);
|
||||
|
||||
RemoteMod[] searchResultArray = new RemoteMod[pageSize];
|
||||
int chineseIndex = 0, englishIndex = searchResultArray.length - 1;
|
||||
for (RemoteMod remoteMod : Lang.toIterable(searchResult.getUnsortedResults())) {
|
||||
if (chineseIndex > englishIndex) {
|
||||
throw new IOException("There are too many search results!");
|
||||
}
|
||||
int totalPages, chineseIndex = 0, englishIndex = pageSize - 1;
|
||||
{
|
||||
SearchResult searchResult = getBackedRemoteModRepository().search(gameVersion, category, pageOffset, pageSize, String.join(" ", englishSearchFiltersSet), getBackedRemoteModRepositorySortOrder(), sortOrder);
|
||||
for (Iterator<RemoteMod> iterator = searchResult.getUnsortedResults().iterator(); iterator.hasNext(); ) {
|
||||
if (chineseIndex > englishIndex) {
|
||||
LOG.log(Level.WARNING, "Too many search results! Are the backed remote mod repository broken? Or are the API broken?");
|
||||
continue;
|
||||
}
|
||||
|
||||
ModTranslations.Mod chineseTranslation = ModTranslations.getTranslationsByRepositoryType(getType()).getModByCurseForgeId(remoteMod.getSlug());
|
||||
if (chineseTranslation != null && !StringUtils.isBlank(chineseTranslation.getName()) && StringUtils.containsChinese(chineseTranslation.getName())) {
|
||||
searchResultArray[chineseIndex++] = remoteMod;
|
||||
} else {
|
||||
searchResultArray[englishIndex--] = remoteMod;
|
||||
RemoteMod remoteMod = iterator.next();
|
||||
ModTranslations.Mod chineseTranslation = ModTranslations.getTranslationsByRepositoryType(getType()).getModByCurseForgeId(remoteMod.getSlug());
|
||||
if (chineseTranslation != null && !StringUtils.isBlank(chineseTranslation.getName()) && StringUtils.containsChinese(chineseTranslation.getName())) {
|
||||
searchResultArray[chineseIndex++] = remoteMod;
|
||||
} else {
|
||||
searchResultArray[englishIndex--] = remoteMod;
|
||||
}
|
||||
}
|
||||
totalPages = searchResult.getTotalPages();
|
||||
}
|
||||
int totalPages = searchResult.getTotalPages();
|
||||
searchResult = null; // Release memory
|
||||
|
||||
StringUtils.DynamicCommonSubsequence calc = new StringUtils.DynamicCommonSubsequence(16, 16);
|
||||
StringUtils.LevCalculator levCalculator = new StringUtils.LevCalculator();
|
||||
return new SearchResult(Stream.concat(Arrays.stream(searchResultArray, 0, chineseIndex).map(remoteMod -> {
|
||||
ModTranslations.Mod chineseRemoteMod = ModTranslations.getTranslationsByRepositoryType(getType()).getModByCurseForgeId(remoteMod.getSlug());
|
||||
if (chineseRemoteMod == null || StringUtils.isBlank(chineseRemoteMod.getName()) || !StringUtils.containsChinese(chineseRemoteMod.getName())) {
|
||||
return Pair.pair(remoteMod, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
String chineseRemoteModName = chineseRemoteMod.getName();
|
||||
if (searchFilter.isEmpty() || chineseRemoteModName.isEmpty()) {
|
||||
return Pair.pair(remoteMod, Math.max(searchFilter.length(), chineseRemoteModName.length()));
|
||||
}
|
||||
|
||||
int weight = calc.calc(searchFilter, chineseRemoteModName);
|
||||
for (int i = 0;i < searchFilter.length(); i ++) {
|
||||
int l = levCalculator.calc(searchFilter, chineseRemoteModName);
|
||||
for (int i = 0; i < searchFilter.length(); i++) {
|
||||
if (chineseRemoteModName.indexOf(searchFilter.charAt(i)) >= 0) {
|
||||
return Pair.pair(remoteMod, weight + CONTAIN_CHINESE_WEIGHT);
|
||||
l -= CONTAIN_CHINESE_WEIGHT;
|
||||
}
|
||||
}
|
||||
return Pair.pair(remoteMod, weight);
|
||||
}).sorted(Comparator.<Pair<RemoteMod, Integer>>comparingInt(Pair::getValue).reversed()).map(Pair::getKey), Arrays.stream(searchResultArray, englishIndex + 1, searchResultArray.length)), totalPages);
|
||||
return Pair.pair(remoteMod, l);
|
||||
}).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), Arrays.stream(searchResultArray, englishIndex + 1, searchResultArray.length)), totalPages);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -112,7 +112,7 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
|||
.getJson(new TypeToken<Response<List<CurseAddon>>>() {
|
||||
}.getType());
|
||||
Stream<RemoteMod> res = response.getData().stream().map(CurseAddon::toMod);
|
||||
if (sortType != SortType.NAME || searchFilter.length() == 0) {
|
||||
if (sortType != SortType.NAME || searchFilter.isEmpty()) {
|
||||
return new SearchResult(res, (int)Math.ceil((double)response.pagination.totalCount / pageSize));
|
||||
}
|
||||
|
||||
|
@ -122,25 +122,11 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
|||
searchFilterWords.put(s, searchFilterWords.getOrDefault(s, 0) + 1);
|
||||
}
|
||||
|
||||
StringUtils.LevCalculator levCalculator = new StringUtils.LevCalculator();
|
||||
|
||||
return new SearchResult(res.map(remoteMod -> {
|
||||
String lowerCaseResult = remoteMod.getTitle().toLowerCase();
|
||||
int[][] lev = new int[lowerCaseSearchFilter.length() + 1][lowerCaseResult.length() + 1];
|
||||
for (int i = 0; i < lowerCaseResult.length() + 1; i++) {
|
||||
lev[0][i] = i;
|
||||
}
|
||||
for (int i = 0; i < lowerCaseSearchFilter.length() + 1; i++) {
|
||||
lev[i][0] = i;
|
||||
}
|
||||
for (int i = 1; i < lowerCaseSearchFilter.length() + 1; i++) {
|
||||
for (int j = 1; j < lowerCaseResult.length() + 1; j++) {
|
||||
int countByInsert = lev[i][j - 1] + 1;
|
||||
int countByDel = lev[i - 1][j] + 1;
|
||||
int countByReplace = lowerCaseSearchFilter.charAt(i - 1) == lowerCaseResult.charAt(j - 1) ? lev[i - 1][j - 1] : lev[i - 1][j - 1] + 1;
|
||||
lev[i][j] = Math.min(countByInsert, Math.min(countByDel, countByReplace));
|
||||
}
|
||||
}
|
||||
|
||||
int diff = lev[lowerCaseSearchFilter.length()][lowerCaseResult.length()];
|
||||
int diff = levCalculator.calc(lowerCaseSearchFilter, lowerCaseResult);
|
||||
|
||||
for (String s : StringUtils.tokenize(lowerCaseResult)) {
|
||||
if (searchFilterWords.containsKey(s)) {
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
*/
|
||||
package com.tungsten.fclcore.util;
|
||||
|
||||
import com.tungsten.fclcore.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.*;
|
||||
|
@ -347,29 +345,59 @@ public final class StringUtils {
|
|||
return true;
|
||||
}
|
||||
|
||||
public static class DynamicCommonSubsequence {
|
||||
private LongestCommonSubsequence calculator;
|
||||
public static class LevCalculator {
|
||||
private int[][] lev;
|
||||
|
||||
public DynamicCommonSubsequence(int intLengthA, int intLengthB) {
|
||||
if (intLengthA > intLengthB) {
|
||||
calculator = new LongestCommonSubsequence(intLengthA, intLengthB);
|
||||
} else {
|
||||
calculator = new LongestCommonSubsequence(intLengthB, intLengthA);
|
||||
public LevCalculator() {
|
||||
}
|
||||
|
||||
public LevCalculator(int length1, int length2) {
|
||||
allocate(length1, length2);
|
||||
}
|
||||
|
||||
private void allocate(int length1, int length2) {
|
||||
length1 += 1;
|
||||
length2 += 1;
|
||||
lev = new int[length1][length2];
|
||||
for (int i = 1; i < length1; i++) {
|
||||
lev[i][0] = i;
|
||||
}
|
||||
int[] cache = lev[0];
|
||||
for (int i = 0; i < length2; i++) {
|
||||
cache[i] = i;
|
||||
}
|
||||
}
|
||||
|
||||
public int getLength1() {
|
||||
return lev.length;
|
||||
}
|
||||
|
||||
public int getLength2() {
|
||||
return lev[0].length;
|
||||
}
|
||||
|
||||
private int min(int a, int b, int c) {
|
||||
return Math.min(a, Math.min(b, c));
|
||||
}
|
||||
|
||||
public int calc(CharSequence a, CharSequence b) {
|
||||
if (a.length() < b.length()) {
|
||||
CharSequence t = a;
|
||||
a = b;
|
||||
b = t;
|
||||
if (lev == null || a.length() >= lev.length || b.length() >= lev[0].length) {
|
||||
allocate(a.length(), b.length());
|
||||
}
|
||||
|
||||
if (calculator.maxLengthA < a.length() || calculator.maxLengthB < b.length()) {
|
||||
calculator = new LongestCommonSubsequence(a.length(), b.length());
|
||||
int lengthA = a.length() + 1, lengthB = b.length() + 1;
|
||||
|
||||
for (int i = 1; i < lengthA; i++) {
|
||||
for (int j = 1; j < lengthB; j++) {
|
||||
lev[i][j] = min(
|
||||
lev[i][j - 1] + 1, // insert
|
||||
lev[i - 1][j] + 1, // del
|
||||
a.charAt(i - 1) == b.charAt(j - 1) ? lev[i - 1][j - 1] : lev[i - 1][j - 1] + 1 // replace
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return calculator.calc(a, b);
|
||||
return lev[a.length()][b.length()];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue