/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.hydromatic.optiq.util;
import net.hydromatic.optiq.test.OptiqAssert;
import org.eigenbase.util.TestUtil;
import org.junit.Test;
import java.util.*;
import static org.junit.Assert.*;
/**
* Unit test for {@link PartiallyOrderedSet}.
*/
public class PartiallyOrderedSetTest {
private static final boolean DEBUG = false;
// 100, 250, 1000, 3000 are reasonable
private static final int SCALE = OptiqAssert.ENABLE_SLOW ? 250 : 50;
final long seed = new Random().nextLong();
final Random random = new Random(seed);
static final PartiallyOrderedSet.Ordering<String> STRING_SUBSET_ORDERING =
new PartiallyOrderedSet.Ordering<String>() {
public boolean lessThan(String e1, String e2) {
// e1 < e2 if every char in e1 is also in e2
for (int i = 0; i < e1.length(); i++) {
if (e2.indexOf(e1.charAt(i)) < 0) {
return false;
}
}
return true;
}
};
// Integers, ordered by division. Top is 1, its children are primes,
// etc.
static final PartiallyOrderedSet.Ordering<Integer> IS_DIVISOR =
new PartiallyOrderedSet.Ordering<Integer>() {
public boolean lessThan(Integer e1, Integer e2) {
return e2 % e1 == 0;
}
};
// Bottom is 1, parents are primes, etc.
static final PartiallyOrderedSet.Ordering<Integer> IS_DIVISOR_INVERSE =
new PartiallyOrderedSet.Ordering<Integer>() {
public boolean lessThan(Integer e1, Integer e2) {
return e1 % e2 == 0;
}
};
// Ordered by bit inclusion. E.g. the children of 14 (1110) are
// 12 (1100), 10 (1010) and 6 (0110).
static final PartiallyOrderedSet.Ordering<Integer> IS_BIT_SUBSET =
new PartiallyOrderedSet.Ordering<Integer>() {
public boolean lessThan(Integer e1, Integer e2) {
return (e2 & e1) == e2;
}
};
// Ordered by bit inclusion. E.g. the children of 14 (1110) are
// 12 (1100), 10 (1010) and 6 (0110).
static final PartiallyOrderedSet.Ordering<Integer> IS_BIT_SUPERSET =
new PartiallyOrderedSet.Ordering<Integer>() {
public boolean lessThan(Integer e1, Integer e2) {
return (e2 & e1) == e1;
}
};
@Test public void testPoset() {
String empty = "''";
String abcd = "'abcd'";
PartiallyOrderedSet<String> poset =
new PartiallyOrderedSet<String>(STRING_SUBSET_ORDERING);
assertEquals(0, poset.size());
final StringBuilder buf = new StringBuilder();
poset.out(buf);
TestUtil.assertEqualsVerbose(
"PartiallyOrderedSet size: 0 elements: {\n"
+ "}",
buf.toString());
poset.add("a");
printValidate(poset);
poset.add("b");
printValidate(poset);
poset.clear();
assertEquals(0, poset.size());
poset.add(empty);
printValidate(poset);
poset.add(abcd);
printValidate(poset);
assertEquals(2, poset.size());
assertEquals("['abcd']", poset.getNonChildren().toString());
assertEquals("['']", poset.getNonParents().toString());
final String ab = "'ab'";
poset.add(ab);
printValidate(poset);
assertEquals(3, poset.size());
assertEquals("[]", poset.getChildren(empty).toString());
assertEquals("['ab']", poset.getParents(empty).toString());
assertEquals("['ab']", poset.getChildren(abcd).toString());
assertEquals("[]", poset.getParents(abcd).toString());
assertEquals("['']", poset.getChildren(ab).toString());
assertEquals("['abcd']", poset.getParents(ab).toString());
// "bcd" is child of "abcd" and parent of ""
final String bcd = "'bcd'";
poset.add(bcd);
printValidate(poset);
assertTrue(poset.isValid(false));
assertEquals("['']", poset.getChildren(bcd).toString());
assertEquals("['abcd']", poset.getParents(bcd).toString());
assertEquals("['ab', 'bcd']", poset.getChildren(abcd).toString());
buf.setLength(0);
poset.out(buf);
TestUtil.assertEqualsVerbose(
"PartiallyOrderedSet size: 4 elements: {\n"
+ " 'abcd' parents: [] children: ['ab', 'bcd']\n"
+ " 'ab' parents: ['abcd'] children: ['']\n"
+ " 'bcd' parents: ['abcd'] children: ['']\n"
+ " '' parents: ['ab', 'bcd'] children: []\n"
+ "}",
buf.toString());
final String b = "'b'";
// ancestors of an element not in the set
assertEqualsList("['ab', 'abcd', 'bcd']", poset.getAncestors(b));
poset.add(b);
printValidate(poset);
assertEquals("['abcd']", poset.getNonChildren().toString());
assertEquals("['']", poset.getNonParents().toString());
assertEquals("['']", poset.getChildren(b).toString());
assertEqualsList("['ab', 'bcd']", poset.getParents(b));
assertEquals("['']", poset.getChildren(b).toString());
assertEquals("['ab', 'bcd']", poset.getChildren(abcd).toString());
assertEquals("['b']", poset.getChildren(bcd).toString());
assertEquals("['b']", poset.getChildren(ab).toString());
assertEqualsList("['ab', 'abcd', 'bcd']", poset.getAncestors(b));
// descendants and ancestors of an element with no descendants
assertEquals("[]", poset.getDescendants(empty).toString());
assertEqualsList(
"['ab', 'abcd', 'b', 'bcd']",
poset.getAncestors(empty));
// some more ancestors of missing elements
assertEqualsList("['abcd']", poset.getAncestors("'ac'"));
assertEqualsList("[]", poset.getAncestors("'z'"));
assertEqualsList("['ab', 'abcd']", poset.getAncestors("'a'"));
}
@Test public void testPosetTricky() {
PartiallyOrderedSet<String> poset =
new PartiallyOrderedSet<String>(STRING_SUBSET_ORDERING);
// A tricky little poset with 4 elements:
// {a <= ab and ac, b < ab, ab, ac}
poset.clear();
poset.add("'a'");
printValidate(poset);
poset.add("'b'");
printValidate(poset);
poset.add("'ac'");
printValidate(poset);
poset.add("'ab'");
printValidate(poset);
}
@Test public void testPosetBits() {
final PartiallyOrderedSet<Integer> poset =
new PartiallyOrderedSet<Integer>(IS_BIT_SUPERSET);
poset.add(2112); // {6, 11} i.e. 64 + 2048
poset.add(2240); // {6, 7, 11} i.e. 64 + 128 + 2048
poset.add(2496); // {6, 7, 8, 11} i.e. 64 + 128 + 256 + 2048
printValidate(poset);
poset.remove(2240);
printValidate(poset);
poset.add(2240); // {6, 7, 11} i.e. 64 + 128 + 2048
printValidate(poset);
}
@Test public void testPosetBitsRemoveParent() {
final PartiallyOrderedSet<Integer> poset =
new PartiallyOrderedSet<Integer>(IS_BIT_SUPERSET);
poset.add(66); // {bit 2, bit 6}
poset.add(68); // {bit 3, bit 6}
poset.add(72); // {bit 4, bit 6}
poset.add(64); // {bit 6}
printValidate(poset);
poset.remove(64); // {bit 6}
printValidate(poset);
}
@Test public void testDivisorPoset() {
if (!OptiqAssert.ENABLE_SLOW) {
return;
}
PartiallyOrderedSet<Integer> integers =
new PartiallyOrderedSet<Integer>(IS_DIVISOR, range(1, 1000));
assertEquals(
"[1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 24, 30, 40, 60]",
new TreeSet<Integer>(integers.getDescendants(120)).toString());
assertEquals(
"[240, 360, 480, 600, 720, 840, 960]",
new TreeSet<Integer>(integers.getAncestors(120)).toString());
assertTrue(integers.getDescendants(1).isEmpty());
assertEquals(
998,
integers.getAncestors(1).size());
assertTrue(integers.isValid(true));
}
@Test public void testDivisorSeries() {
checkPoset(IS_DIVISOR, DEBUG, range(1, SCALE * 3), false);
}
@Test public void testDivisorRandom() {
boolean ok = false;
try {
checkPoset(
IS_DIVISOR, DEBUG, random(random, SCALE, SCALE * 3), false);
ok = true;
} finally {
if (!ok) {
System.out.println("Random seed: " + seed);
}
}
}
@Test public void testDivisorRandomWithRemoval() {
boolean ok = false;
try {
checkPoset(
IS_DIVISOR, DEBUG, random(random, SCALE, SCALE * 3), true);
ok = true;
} finally {
if (!ok) {
System.out.println("Random seed: " + seed);
}
}
}
@Test public void testDivisorInverseSeries() {
checkPoset(IS_DIVISOR_INVERSE, DEBUG, range(1, SCALE * 3), false);
}
@Test public void testDivisorInverseRandom() {
boolean ok = false;
try {
checkPoset(
IS_DIVISOR_INVERSE, DEBUG, random(random, SCALE, SCALE * 3),
false);
ok = true;
} finally {
if (!ok) {
System.out.println("Random seed: " + seed);
}
}
}
@Test public void testDivisorInverseRandomWithRemoval() {
boolean ok = false;
try {
checkPoset(
IS_DIVISOR_INVERSE, DEBUG, random(random, SCALE, SCALE * 3),
true);
ok = true;
} finally {
if (!ok) {
System.out.println("Random seed: " + seed);
}
}
}
@Test public void testSubsetSeries() {
checkPoset(IS_BIT_SUBSET, DEBUG, range(1, SCALE / 2), false);
}
@Test public void testSubsetRandom() {
boolean ok = false;
try {
checkPoset(
IS_BIT_SUBSET, DEBUG, random(random, SCALE / 4, SCALE), false);
ok = true;
} finally {
if (!ok) {
System.out.println("Random seed: " + seed);
}
}
}
private <E> void printValidate(PartiallyOrderedSet<E> poset) {
if (DEBUG) {
dump(poset);
}
assertTrue(poset.isValid(DEBUG));
}
public void checkPoset(
PartiallyOrderedSet.Ordering<Integer> ordering,
boolean debug,
Iterable<Integer> generator,
boolean remove) {
final PartiallyOrderedSet<Integer> poset =
new PartiallyOrderedSet<Integer>(ordering);
int n = 0;
int z = 0;
if (debug) {
dump(poset);
}
for (int i : generator) {
if (remove && z++ % 2 == 0) {
if (debug) {
System.out.println("remove " + i);
}
poset.remove(i);
if (debug) {
dump(poset);
}
continue;
}
if (debug) {
System.out.println("add " + i);
}
poset.add(i);
if (debug) {
dump(poset);
}
assertEquals(++n, poset.size());
if (i < 100) {
if (!poset.isValid(false)) {
dump(poset);
}
assertTrue(poset.isValid(true));
}
}
assertTrue(poset.isValid(true));
final StringBuilder buf = new StringBuilder();
poset.out(buf);
assertTrue(buf.length() > 0);
}
private <E> void dump(PartiallyOrderedSet<E> poset) {
final StringBuilder buf = new StringBuilder();
poset.out(buf);
System.out.println(buf);
}
private static Collection<Integer> range(
final int start, final int end) {
return new AbstractList<Integer>() {
@Override
public Integer get(int index) {
return start + index;
}
@Override
public int size() {
return end - start;
}
};
}
private static Iterable<Integer> random(
Random random, final int size, final int max) {
final Set<Integer> set = new LinkedHashSet<Integer>();
while (set.size() < size) {
set.add(random.nextInt(max) + 1);
}
return set;
}
private static void assertEqualsList(String expected, List<String> ss) {
assertEquals(
expected,
new TreeSet<String>(ss).toString());
}
}
// End PartiallyOrderedSetTest.java