Feature(FCLCore): better mod search

This commit is contained in:
Tungstend 2024-01-15 21:39:07 +08:00
parent b11bc6a714
commit 808f593ee1
3 changed files with 72 additions and 59 deletions

View File

@ -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

View File

@ -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)) {

View File

@ -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()];
}
}