224 lines
5.3 KiB
Java
224 lines
5.3 KiB
Java
package net.minecraft.client.searchtree;
|
|
|
|
import com.google.common.collect.Lists;
|
|
import com.google.common.collect.Sets;
|
|
import com.mojang.logging.LogUtils;
|
|
import it.unimi.dsi.fastutil.Arrays;
|
|
import it.unimi.dsi.fastutil.Swapper;
|
|
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
|
import it.unimi.dsi.fastutil.ints.IntComparator;
|
|
import it.unimi.dsi.fastutil.ints.IntList;
|
|
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
|
import it.unimi.dsi.fastutil.ints.IntSet;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import net.fabricmc.api.EnvType;
|
|
import net.fabricmc.api.Environment;
|
|
import org.slf4j.Logger;
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
public class SuffixArray<T> {
|
|
private static final boolean DEBUG_COMPARISONS = Boolean.parseBoolean(System.getProperty("SuffixArray.printComparisons", "false"));
|
|
private static final boolean DEBUG_ARRAY = Boolean.parseBoolean(System.getProperty("SuffixArray.printArray", "false"));
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
private static final int END_OF_TEXT_MARKER = -1;
|
|
private static final int END_OF_DATA = -2;
|
|
protected final List<T> list = Lists.<T>newArrayList();
|
|
private final IntList chars = new IntArrayList();
|
|
private final IntList wordStarts = new IntArrayList();
|
|
private IntList suffixToT = new IntArrayList();
|
|
private IntList offsets = new IntArrayList();
|
|
private int maxStringLength;
|
|
|
|
public void add(T object, String contents) {
|
|
this.maxStringLength = Math.max(this.maxStringLength, contents.length());
|
|
int i = this.list.size();
|
|
this.list.add(object);
|
|
this.wordStarts.add(this.chars.size());
|
|
|
|
for (int j = 0; j < contents.length(); j++) {
|
|
this.suffixToT.add(i);
|
|
this.offsets.add(j);
|
|
this.chars.add(contents.charAt(j));
|
|
}
|
|
|
|
this.suffixToT.add(i);
|
|
this.offsets.add(contents.length());
|
|
this.chars.add(-1);
|
|
}
|
|
|
|
public void generate() {
|
|
int i = this.chars.size();
|
|
int[] is = new int[i];
|
|
int[] js = new int[i];
|
|
int[] ks = new int[i];
|
|
int[] ls = new int[i];
|
|
IntComparator intComparator = (ix, jx) -> js[ix] == js[jx] ? Integer.compare(ks[ix], ks[jx]) : Integer.compare(js[ix], js[jx]);
|
|
Swapper swapper = (ix, jx) -> {
|
|
if (ix != jx) {
|
|
int kx = js[ix];
|
|
js[ix] = js[jx];
|
|
js[jx] = kx;
|
|
kx = ks[ix];
|
|
ks[ix] = ks[jx];
|
|
ks[jx] = kx;
|
|
kx = ls[ix];
|
|
ls[ix] = ls[jx];
|
|
ls[jx] = kx;
|
|
}
|
|
};
|
|
|
|
for (int j = 0; j < i; j++) {
|
|
is[j] = this.chars.getInt(j);
|
|
}
|
|
|
|
int j = 1;
|
|
|
|
for (int k = Math.min(i, this.maxStringLength); j * 2 < k; j *= 2) {
|
|
for (int l = 0; l < i; ls[l] = l++) {
|
|
js[l] = is[l];
|
|
ks[l] = l + j < i ? is[l + j] : -2;
|
|
}
|
|
|
|
Arrays.quickSort(0, i, intComparator, swapper);
|
|
|
|
for (int l = 0; l < i; l++) {
|
|
if (l > 0 && js[l] == js[l - 1] && ks[l] == ks[l - 1]) {
|
|
is[ls[l]] = is[ls[l - 1]];
|
|
} else {
|
|
is[ls[l]] = l;
|
|
}
|
|
}
|
|
}
|
|
|
|
IntList intList = this.suffixToT;
|
|
IntList intList2 = this.offsets;
|
|
this.suffixToT = new IntArrayList(intList.size());
|
|
this.offsets = new IntArrayList(intList2.size());
|
|
|
|
for (int m = 0; m < i; m++) {
|
|
int n = ls[m];
|
|
this.suffixToT.add(intList.getInt(n));
|
|
this.offsets.add(intList2.getInt(n));
|
|
}
|
|
|
|
if (DEBUG_ARRAY) {
|
|
this.print();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prints the entire array to the logger, on debug level
|
|
*/
|
|
private void print() {
|
|
for (int i = 0; i < this.suffixToT.size(); i++) {
|
|
LOGGER.debug("{} {}", i, this.getString(i));
|
|
}
|
|
|
|
LOGGER.debug("");
|
|
}
|
|
|
|
private String getString(int index) {
|
|
int i = this.offsets.getInt(index);
|
|
int j = this.wordStarts.getInt(this.suffixToT.getInt(index));
|
|
StringBuilder stringBuilder = new StringBuilder();
|
|
|
|
for (int k = 0; j + k < this.chars.size(); k++) {
|
|
if (k == i) {
|
|
stringBuilder.append('^');
|
|
}
|
|
|
|
int l = this.chars.getInt(j + k);
|
|
if (l == -1) {
|
|
break;
|
|
}
|
|
|
|
stringBuilder.append((char)l);
|
|
}
|
|
|
|
return stringBuilder.toString();
|
|
}
|
|
|
|
private int compare(String string, int index) {
|
|
int i = this.wordStarts.getInt(this.suffixToT.getInt(index));
|
|
int j = this.offsets.getInt(index);
|
|
|
|
for (int k = 0; k < string.length(); k++) {
|
|
int l = this.chars.getInt(i + j + k);
|
|
if (l == -1) {
|
|
return 1;
|
|
}
|
|
|
|
char c = string.charAt(k);
|
|
char d = (char)l;
|
|
if (c < d) {
|
|
return -1;
|
|
}
|
|
|
|
if (c > d) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
public List<T> search(String query) {
|
|
int i = this.suffixToT.size();
|
|
int j = 0;
|
|
int k = i;
|
|
|
|
while (j < k) {
|
|
int l = j + (k - j) / 2;
|
|
int m = this.compare(query, l);
|
|
if (DEBUG_COMPARISONS) {
|
|
LOGGER.debug("comparing lower \"{}\" with {} \"{}\": {}", query, l, this.getString(l), m);
|
|
}
|
|
|
|
if (m > 0) {
|
|
j = l + 1;
|
|
} else {
|
|
k = l;
|
|
}
|
|
}
|
|
|
|
if (j >= 0 && j < i) {
|
|
int lx = j;
|
|
k = i;
|
|
|
|
while (j < k) {
|
|
int mx = j + (k - j) / 2;
|
|
int n = this.compare(query, mx);
|
|
if (DEBUG_COMPARISONS) {
|
|
LOGGER.debug("comparing upper \"{}\" with {} \"{}\": {}", query, mx, this.getString(mx), n);
|
|
}
|
|
|
|
if (n >= 0) {
|
|
j = mx + 1;
|
|
} else {
|
|
k = mx;
|
|
}
|
|
}
|
|
|
|
int mxx = j;
|
|
IntSet intSet = new IntOpenHashSet();
|
|
|
|
for (int o = lx; o < mxx; o++) {
|
|
intSet.add(this.suffixToT.getInt(o));
|
|
}
|
|
|
|
int[] is = intSet.toIntArray();
|
|
java.util.Arrays.sort(is);
|
|
Set<T> set = Sets.<T>newLinkedHashSet();
|
|
|
|
for (int p : is) {
|
|
set.add(this.list.get(p));
|
|
}
|
|
|
|
return Lists.<T>newArrayList(set);
|
|
} else {
|
|
return Collections.emptyList();
|
|
}
|
|
}
|
|
}
|