190 lines
5.3 KiB
Java
190 lines
5.3 KiB
Java
package core.benchmark;
|
|
|
|
import java.time.Instant;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
|
|
import core.Constants;
|
|
import dataStructures.Pair;
|
|
import utils.io.DirectoryMonitor;
|
|
import utils.io.FileUtils;
|
|
import utils.io.MonitoredFile;
|
|
|
|
// All things considered, this doesn't need to be particularly efficient since anything
|
|
// less than 10ms of search time won't be noticable to an end user really, not for
|
|
// a networked application that's not performance bound.
|
|
public class BenchmarkManager
|
|
{
|
|
// Variables
|
|
public final String directory = Constants.AssetDirectory + "userbench/";
|
|
public final String extension = ".csv";
|
|
public final String lineDelimiter = "\n";
|
|
|
|
private List<BenchmarkEntry> raw;
|
|
private DirectoryMonitor directoryMonitor;
|
|
private boolean needsUpdate;
|
|
|
|
// Read in all extensions
|
|
public BenchmarkManager()
|
|
{
|
|
long start = Instant.now().toEpochMilli();
|
|
|
|
raw = new ArrayList<BenchmarkEntry>();
|
|
directoryMonitor = new DirectoryMonitor(directory);
|
|
needsUpdate = false;
|
|
|
|
rebuildLookup();
|
|
|
|
BenchmarkLog.log("Took " + (Instant.now().toEpochMilli() - start) + " ms to load " + raw.size() + " entries from " + directoryMonitor.getCurrentFiles().size() + " file" + (raw.size() > 1 ? "s" : ""));
|
|
}
|
|
|
|
// Rebuilds the files being monitored
|
|
public void rebuildLookup()
|
|
{
|
|
long start = Instant.now().toEpochMilli();
|
|
|
|
synchronized(raw)
|
|
{
|
|
raw.clear();
|
|
List<MonitoredFile> files;
|
|
|
|
synchronized(directoryMonitor)
|
|
{
|
|
files = directoryMonitor.getCurrentFiles();
|
|
}
|
|
|
|
if(files != null)
|
|
{
|
|
for(MonitoredFile mf : files)
|
|
{
|
|
String contents = FileUtils.readContent(mf.path);
|
|
String[] lines = contents.split(lineDelimiter);
|
|
|
|
for(int i = 1; i < lines.length; ++i)
|
|
raw.add(new BenchmarkEntry(lines[i]));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BenchmarkLog.warn("No " + extension + " files where found in " + directory);
|
|
}
|
|
}
|
|
|
|
long end = Instant.now().toEpochMilli();
|
|
BenchmarkLog.log("Rebuilt data in " + (end - start) + "ms");
|
|
}
|
|
|
|
// Re-evaluates and re-orders the input list based on the Levenshtein distance of the
|
|
// original search and the contents of the provided list of benchmark entries.
|
|
public List<BenchmarkEntry> evaluateLevenshteinDistance(List<BenchmarkEntry> entries, String search)
|
|
{
|
|
// Populate cost list with heuristic results
|
|
List<Pair<BenchmarkEntry, Integer>> cost = new ArrayList<Pair<BenchmarkEntry, Integer>>();
|
|
int bestSoFar = Integer.MAX_VALUE;
|
|
for(int i = 0; i < entries.size(); ++i)
|
|
{
|
|
BenchmarkEntry entry = entries.get(i);
|
|
String entryString = entry.brand + " " + entry.model;
|
|
|
|
int heuristic = levenshteinHeuristic(entryString, search, bestSoFar);
|
|
if(heuristic < bestSoFar)
|
|
bestSoFar = heuristic;
|
|
|
|
cost.add(new Pair<BenchmarkEntry, Integer>(entry, heuristic));
|
|
}
|
|
|
|
// Sort list
|
|
Collections.sort(cost, (i1, i2) -> i1.Second.compareTo(i2.Second));
|
|
|
|
// Build new output list
|
|
List<BenchmarkEntry> sortedEntries = new ArrayList<BenchmarkEntry>();
|
|
for(int i = 0; i < cost.size(); ++i)
|
|
sortedEntries.add(cost.get(i).First);
|
|
|
|
return sortedEntries;
|
|
}
|
|
|
|
// Finds the minimum integer in an array of ints and returns it.
|
|
private int min(int[] arr)
|
|
{
|
|
int min = Integer.MAX_VALUE;
|
|
for(int i = 0; i < arr.length; ++i)
|
|
{
|
|
if(arr[i] < min)
|
|
min = arr[i];
|
|
}
|
|
|
|
return min;
|
|
}
|
|
|
|
// Returns cost based on distance of characters from string. This is a kinda sloppy way
|
|
// to do it, but because I keep tabs on the best result so far, it could be much worse.
|
|
// Still chucks a lot - a non-recursive result w/ memoization would be best but this will do.
|
|
private int levenshteinHeuristic(String str1, String str2, int bestSoFar)
|
|
{
|
|
int cost;
|
|
|
|
if(str1.length() <= 0)
|
|
return str2.length();
|
|
|
|
if(str2.length() <= 0)
|
|
return str1.length();
|
|
|
|
if(str1.charAt(0) == str2.charAt(0))
|
|
cost = 0;
|
|
else
|
|
cost = 1;
|
|
|
|
int distance = Math.abs(str1.length() - str2.length());
|
|
if(distance > bestSoFar)
|
|
return distance;
|
|
|
|
int s1 = levenshteinHeuristic(str1.substring(1), str2, bestSoFar) + 1;
|
|
int s2 = levenshteinHeuristic(str1, str2.substring(1), bestSoFar) + 1;
|
|
int s3 = levenshteinHeuristic(str1.substring(1), str2.substring(1), bestSoFar) + cost;
|
|
|
|
return min(new int[] {s1, s2, s3 });
|
|
}
|
|
|
|
// Search for a substring in the model name
|
|
public List<BenchmarkEntry> findModel(String modelSubstr)
|
|
{
|
|
long start = Instant.now().toEpochMilli();
|
|
List<BenchmarkEntry> matching = new ArrayList<BenchmarkEntry>();
|
|
String searchSubstr = modelSubstr.toLowerCase();
|
|
|
|
for(BenchmarkEntry e : raw)
|
|
{
|
|
String model = e.model.toLowerCase().trim();
|
|
|
|
if(model.contains(searchSubstr))
|
|
matching.add(e);
|
|
}
|
|
|
|
BenchmarkLog.log("Searched for '" + searchSubstr + "' for "+ (Instant.now().toEpochMilli() - start) + "ms and found " + matching.size() + " entries.");
|
|
return matching;
|
|
}
|
|
|
|
// Keeps tabs on any changes of the files.
|
|
public void update()
|
|
{
|
|
needsUpdate = false;
|
|
directoryMonitor.update(this::onRescan, this::onRescan, this::onRescan);
|
|
|
|
if(needsUpdate)
|
|
rebuildLookup();
|
|
}
|
|
|
|
// When a file is changed, handle it.
|
|
private void onRescan(MonitoredFile file)
|
|
{
|
|
// For now, all we need is to note that something was adjusted.
|
|
if(file.path.toString().contains(extension))
|
|
{
|
|
BenchmarkLog.log("File status changed: " + file.path);
|
|
needsUpdate = true;
|
|
}
|
|
}
|
|
}
|