From ac4e5c597c7ca760da72baf9120f30335e1eb4c9 Mon Sep 17 00:00:00 2001 From: sspanak Date: Mon, 16 Dec 2024 12:41:06 +0200 Subject: [PATCH] added dynamic word positions limit when searching to prevent some words being inaccessible in languages with many letters per key --- .../tt9/db/entities/LongPositionsCache.java | 44 +++++++++++++++++++ .../github/sspanak/tt9/db/sqlite/ReadOps.java | 32 +++++++++++++- .../sspanak/tt9/db/words/WordStore.java | 9 +++- .../preferences/settings/SettingsStore.java | 1 + 4 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/io/github/sspanak/tt9/db/entities/LongPositionsCache.java diff --git a/app/src/main/java/io/github/sspanak/tt9/db/entities/LongPositionsCache.java b/app/src/main/java/io/github/sspanak/tt9/db/entities/LongPositionsCache.java new file mode 100644 index 00000000..47dec90a --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/db/entities/LongPositionsCache.java @@ -0,0 +1,44 @@ +package io.github.sspanak.tt9.db.entities; + +import androidx.annotation.NonNull; + +import java.util.HashMap; + +import io.github.sspanak.tt9.languages.Language; +import io.github.sspanak.tt9.preferences.settings.SettingsStore; + +public class LongPositionsCache { + private final HashMap> positions = new HashMap<>(); + + public boolean contains(@NonNull Language language) { + return positions.containsKey(language.getId()); + } + + public void put(@NonNull Language language, @NonNull String sequence, int wordCount) { + if (wordCount < SettingsStore.SUGGESTIONS_POSITIONS_LIMIT && !contains(language)) { + positions.put(language.getId(), null); + return; + } + + HashMap words = positions.get(language.getId()); + if (words == null) { + words = new HashMap<>(); + positions.put(language.getId(), words); + } + words.put(sequence, wordCount); + } + + public int get(@NonNull Language language, @NonNull String sequence) { + if (!contains(language)) { + return SettingsStore.SUGGESTIONS_POSITIONS_LIMIT; + } + + HashMap words = positions.get(language.getId()); + if (words == null) { + return SettingsStore.SUGGESTIONS_POSITIONS_LIMIT; + } + + Integer wordCount = words.get(sequence); + return wordCount == null ? SettingsStore.SUGGESTIONS_POSITIONS_LIMIT : wordCount; + } +} diff --git a/app/src/main/java/io/github/sspanak/tt9/db/sqlite/ReadOps.java b/app/src/main/java/io/github/sspanak/tt9/db/sqlite/ReadOps.java index 0ec4fe01..de39ead7 100644 --- a/app/src/main/java/io/github/sspanak/tt9/db/sqlite/ReadOps.java +++ b/app/src/main/java/io/github/sspanak/tt9/db/sqlite/ReadOps.java @@ -12,6 +12,7 @@ import androidx.annotation.Nullable; import java.util.ArrayList; +import io.github.sspanak.tt9.db.entities.LongPositionsCache; import io.github.sspanak.tt9.db.entities.NormalizationList; import io.github.sspanak.tt9.db.entities.WordList; import io.github.sspanak.tt9.db.entities.WordPositionsStringBuilder; @@ -20,10 +21,12 @@ import io.github.sspanak.tt9.db.words.SlowQueryStats; import io.github.sspanak.tt9.db.words.WordStore; import io.github.sspanak.tt9.languages.EmojiLanguage; import io.github.sspanak.tt9.languages.Language; +import io.github.sspanak.tt9.preferences.settings.SettingsStore; import io.github.sspanak.tt9.util.Logger; public class ReadOps { private final String LOG_TAG = "ReadOperations"; + private final LongPositionsCache longPositionsCache = new LongPositionsCache(); /** @@ -253,7 +256,7 @@ public class ReadOps { .append(sequence) .append("' OR sequence BETWEEN '").append(sequence).append("1' AND '").append(sequence).append(rangeEnd).append("'"); sql.append(" ORDER BY `start` "); - sql.append(" LIMIT 100"); + sql.append(" LIMIT ").append(longPositionsCache.get(language, sequence)); } String positionsSql = sql.toString(); @@ -336,4 +339,31 @@ public class ReadOps { return pairs; } + + + /** + * Returns the sequences that result in more words than the standard performance-balanced limit of 100. + */ + public void cacheLongPositionsIfMissing(@NonNull SQLiteDatabase db, @NonNull Language language) { + if (longPositionsCache.contains(language)) { + return; + } + + String[] select = new String[]{"sequence", "`end` - `start`"}; + String table = Tables.getWordPositions(language.getId()); + String where = "LENGTH(sequence) > " + SettingsStore.SUGGESTIONS_POSITIONS_LIMIT; + + boolean hasResults = false; + + try (Cursor cursor = db.query(table, select, where, null, null, null, null)) { + while (cursor.moveToNext()) { + hasResults = true; + longPositionsCache.put(language, cursor.getString(0), cursor.getInt(1)); + } + } + + if (!hasResults) { + longPositionsCache.put(language, "", 0); + } + } } diff --git a/app/src/main/java/io/github/sspanak/tt9/db/words/WordStore.java b/app/src/main/java/io/github/sspanak/tt9/db/words/WordStore.java index d85f474c..2018ba02 100644 --- a/app/src/main/java/io/github/sspanak/tt9/db/words/WordStore.java +++ b/app/src/main/java/io/github/sspanak/tt9/db/words/WordStore.java @@ -78,6 +78,10 @@ public class WordStore extends BaseSyncStore { return new ArrayList<>(); } + Timer.start("cache_long_positions"); + readOps.cacheLongPositionsIfMissing(sqlite.getDb(), language); + long longPositionsTime = Timer.stop("cache_long_positions"); + final int minWords = Math.max(minimumWords, 0); final int maxWords = Math.max(maximumWords, minWords); final String filter = wordFilter == null ? "" : wordFilter; @@ -90,7 +94,7 @@ public class WordStore extends BaseSyncStore { ArrayList words = readOps.getWords(sqlite.getDb(), cancel, language, positions, filter, maxWords, false).toStringList(); long wordsTime = Timer.stop("get_words"); - printLoadingSummary(sequence, words, positionsTime, wordsTime); + printLoadingSummary(sequence, words, longPositionsTime, positionsTime, wordsTime); if (!cancel.isCanceled()) { // do not cache empty results from aborted queries SlowQueryStats.add(SlowQueryStats.generateKey(language, sequence, wordFilter, minWords), (int) (positionsTime + wordsTime), positions); } @@ -259,7 +263,7 @@ public class WordStore extends BaseSyncStore { } - private void printLoadingSummary(String sequence, ArrayList words, long positionIndexTime, long wordsTime) { + private void printLoadingSummary(String sequence, ArrayList words, long longPositionsTime, long positionIndexTime, long wordsTime) { if (!Logger.isDebugLevel()) { return; } @@ -269,6 +273,7 @@ public class WordStore extends BaseSyncStore { .append("\nWord Count: ").append(words.size()) .append(".\nTime: ").append(positionIndexTime + wordsTime) .append(" ms (positions: ").append(positionIndexTime) + .append(" ms, long positions: ").append(longPositionsTime) .append(" ms, words: ").append(wordsTime).append(" ms)."); if (words.isEmpty()) { diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsStore.java b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsStore.java index eef1b3dc..b1953e3c 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsStore.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsStore.java @@ -32,6 +32,7 @@ public class SettingsStore extends SettingsUI { public final static float SOFT_KEY_COMPLEX_LABEL_SUB_TITLE_RELATIVE_SIZE = 0.8f; public final static int SUGGESTIONS_MAX = 20; public final static int SUGGESTIONS_MIN = 8; + public final static int SUGGESTIONS_POSITIONS_LIMIT = 100; public final static int SUGGESTIONS_SELECT_ANIMATION_DURATION = 66; public final static int SUGGESTIONS_TRANSLATE_ANIMATION_DURATION = 0; public final static int TEXT_INPUT_DEBOUNCE_TIME = 500; // ms