RED-10290: Improve SearchImplementation logic for dictionaries
This commit is contained in:
parent
f15db27a7c
commit
d6739d4852
@ -11,6 +11,8 @@ import com.hankcs.algorithm.AhoCorasickDoubleArrayTrie;
|
||||
import com.iqser.red.service.redaction.v1.server.model.document.TextRange;
|
||||
import com.iqser.red.service.redaction.v1.server.model.document.textblock.TextBlock;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
public class DoubleArrayTrieDictionarySearch implements DictionarySearch {
|
||||
|
||||
private final AhoCorasickDoubleArrayTrie<List<DictionaryIdentifierWithKeyword>> trie;
|
||||
@ -41,8 +43,17 @@ public class DoubleArrayTrieDictionarySearch implements DictionarySearch {
|
||||
@Override
|
||||
public Stream<MatchTextRange> getBoundaries(CharSequence text) {
|
||||
|
||||
String lowerText = text.toString().toLowerCase(Locale.ROOT);
|
||||
return getMatchTextRangeStream(text, lowerText, 0);
|
||||
TextContext textContext = new TextContext(text);
|
||||
return getMatchTextRangeStream(textContext);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Stream<MatchTextRange> getBoundaries(CharSequence text, TextRange region) {
|
||||
|
||||
CharSequence subText = text.subSequence(region.start(), region.end());
|
||||
TextContext textContext = new TextContext(subText, region.start());
|
||||
return getMatchTextRangeStream(textContext);
|
||||
}
|
||||
|
||||
|
||||
@ -53,58 +64,37 @@ public class DoubleArrayTrieDictionarySearch implements DictionarySearch {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Stream<MatchTextRange> getBoundaries(CharSequence text, TextRange region) {
|
||||
|
||||
String subText = text.subSequence(region.start(), region.end()).toString();
|
||||
String lowerSubText = subText.toLowerCase(Locale.ROOT);
|
||||
return getMatchTextRangeStream(text, lowerSubText, region.start());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Stream<MatchPosition> getMatches(String text) {
|
||||
|
||||
TextContext textContext = new TextContext(text);
|
||||
List<MatchPosition> matches = new ArrayList<>();
|
||||
String lowerText = text.toLowerCase(Locale.ROOT);
|
||||
List<AhoCorasickDoubleArrayTrie.Hit<List<DictionaryIdentifierWithKeyword>>> hits = trie.parseText(lowerText);
|
||||
List<AhoCorasickDoubleArrayTrie.Hit<List<DictionaryIdentifierWithKeyword>>> hits = trie.parseText(textContext.getLowerText());
|
||||
|
||||
for (AhoCorasickDoubleArrayTrie.Hit<List<DictionaryIdentifierWithKeyword>> hit : hits) {
|
||||
String matchedText = text.substring(hit.begin, hit.end);
|
||||
List<DictionaryIdentifierWithKeyword> idWithKeywords = hit.value;
|
||||
|
||||
for (DictionaryIdentifierWithKeyword idkw : idWithKeywords) {
|
||||
MatchPosition matchPosition = new MatchPosition(idkw.identifier, hit.begin, hit.end);
|
||||
if (idkw.identifier.caseSensitive()) {
|
||||
if (matchedText.equals(idkw.keyword)) {
|
||||
matches.add(matchPosition);
|
||||
}
|
||||
} else {
|
||||
matches.add(matchPosition);
|
||||
}
|
||||
}
|
||||
addMatchPositionsForHit(textContext, matches, hit);
|
||||
}
|
||||
return matches.stream();
|
||||
}
|
||||
|
||||
|
||||
private Stream<MatchTextRange> getMatchTextRangeStream(CharSequence text, String lowerText, int offset) {
|
||||
private Stream<MatchTextRange> getMatchTextRangeStream(TextContext textContext) {
|
||||
|
||||
List<MatchTextRange> matches = new ArrayList<>();
|
||||
List<AhoCorasickDoubleArrayTrie.Hit<List<DictionaryIdentifierWithKeyword>>> hits = trie.parseText(lowerText);
|
||||
List<AhoCorasickDoubleArrayTrie.Hit<List<DictionaryIdentifierWithKeyword>>> hits = trie.parseText(textContext.getLowerText());
|
||||
|
||||
for (AhoCorasickDoubleArrayTrie.Hit<List<DictionaryIdentifierWithKeyword>> hit : hits) {
|
||||
addMatchesForHit(text, matches, hit, offset);
|
||||
addMatchesForHit(textContext, matches, hit);
|
||||
}
|
||||
return matches.stream();
|
||||
}
|
||||
|
||||
|
||||
private void addMatchesForHit(CharSequence text, List<MatchTextRange> matches, AhoCorasickDoubleArrayTrie.Hit<List<DictionaryIdentifierWithKeyword>> hit, int offset) {
|
||||
private void addMatchesForHit(TextContext textContext, List<MatchTextRange> matches, AhoCorasickDoubleArrayTrie.Hit<List<DictionaryIdentifierWithKeyword>> hit) {
|
||||
|
||||
int start = hit.begin + offset;
|
||||
int end = hit.end + offset;
|
||||
String matchedText = text.subSequence(start, end).toString();
|
||||
int start = textContext.getStart(hit.begin);
|
||||
int end = textContext.getEnd(hit.end);
|
||||
String matchedText = textContext.getMatchedText(hit.begin, hit.end);
|
||||
List<DictionaryIdentifierWithKeyword> idWithKeywords = hit.value;
|
||||
|
||||
for (DictionaryIdentifierWithKeyword idkw : idWithKeywords) {
|
||||
@ -119,6 +109,67 @@ public class DoubleArrayTrieDictionarySearch implements DictionarySearch {
|
||||
}
|
||||
|
||||
|
||||
private void addMatchPositionsForHit(TextContext textContext, List<MatchPosition> matches, AhoCorasickDoubleArrayTrie.Hit<List<DictionaryIdentifierWithKeyword>> hit) {
|
||||
|
||||
int start = textContext.getStart(hit.begin);
|
||||
int end = textContext.getEnd(hit.end);
|
||||
String matchedText = textContext.getMatchedText(hit.begin, hit.end);
|
||||
List<DictionaryIdentifierWithKeyword> idWithKeywords = hit.value;
|
||||
|
||||
for (DictionaryIdentifierWithKeyword idkw : idWithKeywords) {
|
||||
MatchPosition matchPosition = new MatchPosition(idkw.identifier, start, end);
|
||||
if (idkw.identifier.caseSensitive()) {
|
||||
if (matchedText.equals(idkw.keyword)) {
|
||||
matches.add(matchPosition);
|
||||
}
|
||||
} else {
|
||||
matches.add(matchPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class TextContext {
|
||||
|
||||
private final CharSequence text;
|
||||
@Getter
|
||||
private final String lowerText;
|
||||
private final int offset;
|
||||
|
||||
|
||||
public TextContext(CharSequence text, int offset) {
|
||||
|
||||
this.text = text;
|
||||
this.lowerText = text.toString().toLowerCase(Locale.ROOT);
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
|
||||
public TextContext(CharSequence text) {
|
||||
|
||||
this(text, 0);
|
||||
}
|
||||
|
||||
|
||||
public int getStart(int hitBegin) {
|
||||
|
||||
return hitBegin + offset;
|
||||
}
|
||||
|
||||
|
||||
public int getEnd(int hitEnd) {
|
||||
|
||||
return hitEnd + offset;
|
||||
}
|
||||
|
||||
|
||||
public String getMatchedText(int hitBegin, int hitEnd) {
|
||||
|
||||
return text.subSequence(hitBegin, hitEnd).toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private record DictionaryIdentifierWithKeyword(DictionaryIdentifier identifier, String keyword) {
|
||||
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ public class DictionarySearchService {
|
||||
@Observed(name = "DictionarySearchService", contextualName = "add-dictionary-entries")
|
||||
public void addDictionaryEntities(Dictionary dictionary, List<SemanticNode> semanticNodes) {
|
||||
|
||||
semanticNodes.parallelStream().forEach(node -> addDictionaryEntities(dictionary, node));
|
||||
semanticNodes.forEach(node -> addDictionaryEntities(dictionary, node));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.hankcs.algorithm.AhoCorasickDoubleArrayTrie;
|
||||
@ -30,7 +31,7 @@ public class DictionarySearchImplementationsTest {
|
||||
|
||||
|
||||
@Test
|
||||
//@Disabled
|
||||
@Disabled
|
||||
public void performanceTest() {
|
||||
|
||||
Map<DictionaryIdentifier, List<String>> dictionaryValues = new HashMap<>();
|
||||
@ -101,9 +102,7 @@ public class DictionarySearchImplementationsTest {
|
||||
System.out.printf("DoubleArrayTrieDictionarySearch search took %d ms and found %d matches\n", doubleArrayTrieSearchDuration, doubleArrayTrieMatches.size());
|
||||
|
||||
// Assert that all implementations found matches
|
||||
assert !trieDictionaryMatches.isEmpty()
|
||||
&& !searchMatches.isEmpty()
|
||||
&& !doubleArrayTrieMatches.isEmpty() : "All implementations should find entities.";
|
||||
assert !trieDictionaryMatches.isEmpty() && !searchMatches.isEmpty() && !doubleArrayTrieMatches.isEmpty() : "All implementations should find entities.";
|
||||
|
||||
// Ensure all implementations found the same number of matches
|
||||
assertEquals(trieDictionaryMatches.size(), searchMatches.size(), "Mismatch between TrieDictionarySearch and SearchImplementations");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user