package com.github.liblevenshtein.distance.factory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import com.github.liblevenshtein.distance.IDistance;
import com.github.liblevenshtein.transducer.Algorithm;
import static com.github.liblevenshtein.assertion.DistanceAssertions.assertThat;
public class MemoizedDistanceFactoryTest {
private static final String FOO = "foo";
private static final String FOOD = "food";
private static final String FODO = "fodo";
private static final String FDOO = "fdoo";
private static final String DFOO = "dfoo";
private static final String OO = "oo";
private static final String FO = "fo";
private static final String BOO = "boo";
private static final String FBO = "fbo";
private static final String FOB = "fob";
private static final String OFO = "ofo";
private static final String CLOG = "clog";
private static final String DOG = "dog";
private List<String> terms;
private IDistanceFactory<String> factory;
@BeforeClass
public void setUp() throws IOException {
try (final BufferedReader reader = new BufferedReader(
new InputStreamReader(
getClass().getResourceAsStream("/top-20-most-common-english-words.txt"),
StandardCharsets.UTF_8))) {
final List<String> termsList = new ArrayList<>();
String term;
while ((term = reader.readLine()) != null) {
termsList.add(term);
}
this.terms = termsList;
this.factory = new MemoizedDistanceFactory();
}
}
@DataProvider(name = "equalSelfSimilarityData")
public Iterator<Object[]> equalSelfSimilarityData() {
return new EqualSelfSimilarityDataIterator(factory, terms);
}
@DataProvider(name = "minimalityData")
public Iterator<Object[]> minimalityData() {
return new MinimalityDataIterator(factory, terms);
}
@DataProvider(name = "symmetryData")
public Iterator<Object[]> symmetryData() {
return new SymmetryDataIterator(factory, terms);
}
@DataProvider(name = "triangleInequalityData")
public Iterator<Object[]> triangleInequalityData() {
return new TriangleInequalityDataIterator(factory, terms);
}
@DataProvider(name = "penaltyData")
public Object[][] penaltyData() {
return new Object[][] {
{Algorithm.STANDARD, factory.build(Algorithm.STANDARD), 2, 2, 2},
{Algorithm.TRANSPOSITION, factory.build(Algorithm.TRANSPOSITION), 1, 2, 2},
{Algorithm.MERGE_AND_SPLIT, factory.build(Algorithm.MERGE_AND_SPLIT), 2, 1, 1},
};
}
@Test(dataProvider = "equalSelfSimilarityData")
public void testEqualSelfSimilarity(
final Algorithm algorithm,
final IDistance<String> distance,
final String term1,
final String term2) {
assertThat(distance).satisfiesEqualSelfSimilarity(term1, term2);
}
@Test(dataProvider = "minimalityData")
public void testSatisfyMinimality(
final Algorithm algorithm,
final IDistance<String> distance,
final String term1,
final String term2) {
assertThat(distance).satisfiesMinimality(term1, term2);
}
@Test(dataProvider = "symmetryData")
public void testSymmetry(
final Algorithm algorithm,
final IDistance<String> distance,
final String term1,
final String term2) {
assertThat(distance).satisfiesSymmetry(term1, term2);
}
@Test(dataProvider = "triangleInequalityData")
public void testTriangleInequality(
final Algorithm algorithm,
final IDistance<String> distance,
final String term1,
final String term2,
final String term3) {
assertThat(distance).satisfiesTriangleInequality(term1, term2, term3);
}
@Test(dataProvider = "penaltyData")
public void testPenalties(
final Algorithm algorithm,
final IDistance<String> distance,
final int transpositionPenalty,
final int mergePenalty,
final int splitPenalty) {
assertThat(distance)
.hasDistance(0, FOO, FOO)
.hasDistance(1, FOO, FOOD)
.hasDistance(1, FOO, FODO)
.hasDistance(1, FOO, FDOO)
.hasDistance(1, FOO, DFOO)
.hasDistance(1, FOO, OO)
.hasDistance(1, FOO, FO)
.hasDistance(1, FOO, BOO)
.hasDistance(1, FOO, FBO)
.hasDistance(1, FOO, FOB)
.hasDistance(transpositionPenalty, FOO, OFO)
.hasDistance(mergePenalty, CLOG, DOG)
.hasDistance(splitPenalty, DOG, CLOG);
}
private abstract static class AbstractDataIterator implements Iterator<Object[]> {
protected final Algorithm[] algorithms = Algorithm.values();
protected Object[] params;
@Override
public boolean hasNext() {
advance();
return null != params;
}
@Override
public Object[] next() {
advance();
final Object[] paramsLocal = this.params;
this.params = null;
return paramsLocal;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
public abstract void advance();
}
private static class EqualSelfSimilarityDataIterator extends AbstractDataIterator {
private final IDistanceFactory<String> factory;
private final List<String> terms;
private final Object[] buffer = new Object[4];
private int i = 0;
private int j = 0;
private int k = 0;
EqualSelfSimilarityDataIterator(
final IDistanceFactory<String> factory,
final List<String> terms) {
this.factory = factory;
this.terms = terms;
final Algorithm algorithm = algorithms[k++];
buffer[0] = algorithm;
buffer[1] = factory.build(algorithm);
}
@Override
public void advance() {
if (null == params) {
if (j < terms.size()) {
buffer[2] = terms.get(i);
buffer[3] = terms.get(j);
params = buffer;
j += 1;
}
else if (i + 1 < terms.size()) {
i += 1;
j = 0;
advance();
}
else if (k + 1 < algorithms.length) {
i = 0;
j = 0;
k += 1;
final Algorithm algorithm = algorithms[k];
buffer[0] = algorithm;
buffer[1] = factory.build(algorithm);
advance();
}
}
}
}
private static class MinimalityDataIterator extends AbstractDataIterator {
private final IDistanceFactory<String> factory;
private final List<String> terms;
private final Object[] buffer = new Object[4];
private int i = 0;
private int j = 0;
private int k = 0;
MinimalityDataIterator(
final IDistanceFactory<String> factory,
final List<String> terms) {
this.factory = factory;
this.terms = terms;
final Algorithm algorithm = algorithms[k++];
buffer[0] = algorithm;
buffer[1] = factory.build(algorithm);
}
@Override
public void advance() {
if (null == params) {
if (j == i) {
j += 1;
advance();
}
else if (j < terms.size()) {
buffer[2] = terms.get(i);
buffer[3] = terms.get(j);
params = buffer;
j += 1;
}
else if (i + 1 < terms.size()) {
i += 1;
j = 0;
advance();
}
else if (k + 1 < algorithms.length) {
i = 0;
j = 0;
k += 1;
final Algorithm algorithm = algorithms[k];
buffer[0] = algorithm;
buffer[1] = factory.build(algorithm);
advance();
}
}
}
}
private static class SymmetryDataIterator extends AbstractDataIterator {
private final IDistanceFactory<String> factory;
private final List<String> terms;
private Object[] buffer = new Object[4];
private int i = 0;
private int j = 0;
private int k = 0;
SymmetryDataIterator(
final IDistanceFactory<String> factory,
final List<String> terms) {
this.factory = factory;
this.terms = terms;
final Algorithm algorithm = algorithms[k++];
buffer[0] = algorithm;
buffer[1] = factory.build(algorithm);
}
@Override
public void advance() {
if (null == params) {
if (j < terms.size()) {
buffer[2] = terms.get(i);
buffer[3] = terms.get(j);
params = buffer;
j += 1;
}
else if (i + 1 < terms.size()) {
i += 1;
j = 0;
advance();
}
else if (k + 1 < algorithms.length) {
i = 0;
j = 0;
k += 1;
final Algorithm algorithm = algorithms[k];
buffer[0] = algorithm;
buffer[1] = factory.build(algorithm);
advance();
}
}
}
}
private static class TriangleInequalityDataIterator extends AbstractDataIterator {
private final IDistanceFactory<String> factory;
private final List<String> terms;
private Object[] buffer = new Object[5];
private int i = 0;
private int j = 0;
private int k = 0;
private int l = 0;
TriangleInequalityDataIterator(
final IDistanceFactory<String> factory,
final List<String> terms) {
this.factory = factory;
this.terms = terms;
final Algorithm algorithm = algorithms[l++];
buffer[0] = algorithm;
buffer[1] = factory.build(algorithm);
}
@Override
public void advance() {
if (null == params) {
if (j < terms.size()) {
buffer[2] = terms.get(i);
buffer[3] = terms.get(j);
buffer[4] = terms.get(k);
params = buffer;
j += 1;
}
else if (i + 1 < terms.size()) {
i += 1;
j = 0;
advance();
}
else if (k + 1 < terms.size()) {
i = 0;
j = 0;
k += 1;
advance();
}
else if (l + 1 < algorithms.length) {
i = 0;
j = 0;
k = 0;
l += 1;
final Algorithm algorithm = algorithms[l];
buffer[0] = algorithm;
buffer[1] = factory.build(algorithm);
advance();
}
}
}
}
}