/*
* Copyright (C) 2008 Google Inc.
*
* Licensed 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 com.google.uzaygezen.core;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.ArrayUtils;
import org.junit.Assert;
import org.junit.Test;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.primitives.Ints;
import com.google.uzaygezen.core.TestUtils.IntArrayCallback;
import com.google.uzaygezen.core.TestUtils.IntArrayComparator;
/**
* @author Daniel Aioanei
*/
public class BoundedRollupTest {
@Test
public void empty() {
BoundedRollup<Integer, LongContent> rollup =
BoundedRollup.create(TestUtils.ZERO_LONG_CONTENT, Integer.MAX_VALUE);
Assert.assertNull(rollup.finish());
}
@Test
public void rootOnly() {
BoundedRollup<Integer, LongContent> rollup =
BoundedRollup.create(TestUtils.ZERO_LONG_CONTENT, Integer.MAX_VALUE);
rollup.feedRow(Iterators.<Integer>emptyIterator(), TestUtils.ONE_LONG_CONTENT);
MapNode<Integer, LongContent> actual = rollup.finish();
MapNode<Integer, LongContent> expected = leaf();
Assert.assertEquals(expected, actual);
}
@Test
public void rootOnlyAddition() {
BoundedRollup<Integer, LongContent> rollup =
BoundedRollup.create(TestUtils.ZERO_LONG_CONTENT, Integer.MAX_VALUE);
int n = 10;
for (int i = 0; i < n; ++i) {
rollup.feedRow(Iterators.<Integer>emptyIterator(), TestUtils.ONE_LONG_CONTENT);
}
MapNode<Integer, LongContent> actual = rollup.finish();
MapNode<Integer, LongContent> expected = leaf(n);
Assert.assertEquals(expected, actual);
}
private MapNode<Integer, LongContent> leaf() {
MapNode<Integer, LongContent> expected = MapNode.create(
TestUtils.ONE_LONG_CONTENT, ImmutableMap.<Integer, MapNode<Integer, LongContent>>of());
return expected;
}
private MapNode<Integer, LongContent> leaf(int count) {
MapNode<Integer, LongContent> expected = MapNode.create(
newCountingArray(count), ImmutableMap.<Integer, MapNode<Integer, LongContent>>of());
return expected;
}
@Test
public void oneRealNode() {
BoundedRollup<Integer, LongContent> rollup =
BoundedRollup.create(TestUtils.ZERO_LONG_CONTENT, Integer.MAX_VALUE);
rollup.feedRow(ImmutableList.of(4).iterator(), TestUtils.ONE_LONG_CONTENT);
MapNode<Integer, LongContent> actual = rollup.finish();
MapNode<Integer, LongContent> expected = MapNode.create(TestUtils.ONE_LONG_CONTENT,
ImmutableMap.<Integer, MapNode<Integer, LongContent>>of(4, leaf()));
Assert.assertEquals(expected, actual);
}
@Test
public void twinPairAtLevel1() {
BoundedRollup<Integer, LongContent> rollup =
BoundedRollup.create(TestUtils.ZERO_LONG_CONTENT, Integer.MAX_VALUE);
rollup.feedRow(ImmutableList.of(5).iterator(), TestUtils.ONE_LONG_CONTENT);
rollup.feedRow(ImmutableList.of(10).iterator(), TestUtils.ONE_LONG_CONTENT);
MapNode<Integer, LongContent> actual = rollup.finish();
MapNode<Integer, LongContent> expected = MapNode.create(newCountingArray(2),
ImmutableMap.<Integer, MapNode<Integer, LongContent>>of(10, leaf(), 5, leaf()));
Assert.assertEquals(expected, actual);
}
@Test
public void twinPairAtLevel1RepeatLastNode() {
BoundedRollup<Integer, LongContent> rollup =
BoundedRollup.create(TestUtils.ZERO_LONG_CONTENT, Integer.MAX_VALUE);
rollup.feedRow(ImmutableList.of(5).iterator(), TestUtils.ONE_LONG_CONTENT);
rollup.feedRow(ImmutableList.of(10).iterator(), TestUtils.ONE_LONG_CONTENT);
rollup.feedRow(ImmutableList.of(10).iterator(), TestUtils.ONE_LONG_CONTENT);
MapNode<Integer, LongContent> actual = rollup.finish();
MapNode<Integer, LongContent> expected = MapNode.create(newCountingArray(3),
ImmutableMap.<Integer, MapNode<Integer, LongContent>>of(10, leaf(2), 5, leaf()));
Assert.assertEquals(expected, actual);
}
@Test
public void twinPairAtLevel1ButGoingBackToFirstChildFails() {
BoundedRollup<Integer, LongContent> rollup =
BoundedRollup.create(TestUtils.ZERO_LONG_CONTENT, Integer.MAX_VALUE);
rollup.feedRow(ImmutableList.of(5).iterator(), TestUtils.ONE_LONG_CONTENT);
rollup.feedRow(ImmutableList.of(10).iterator(), TestUtils.ONE_LONG_CONTENT);
try {
rollup.feedRow(ImmutableList.of(5).iterator(), TestUtils.ONE_LONG_CONTENT);
Assert.fail("IllegalArgumentException expected");
} catch (IllegalArgumentException ex) {
MoreAsserts.assertContainsRegex("Node already there.", ex.getMessage());
}
}
@Test
public void twinPairAtLevel2() {
BoundedRollup<Integer, LongContent> rollup =
BoundedRollup.create(TestUtils.ZERO_LONG_CONTENT, Integer.MAX_VALUE);
rollup.feedRow(ImmutableList.of(100, 5).iterator(), TestUtils.ONE_LONG_CONTENT);
rollup.feedRow(ImmutableList.of(100, 10).iterator(), TestUtils.ONE_LONG_CONTENT);
MapNode<Integer, LongContent> actual = rollup.finish();
MapNode<Integer, LongContent> expected = MapNode.create(newCountingArray(2),
ImmutableMap.<Integer, MapNode<Integer, LongContent>>of(
100, MapNode.create(newCountingArray(2), ImmutableMap
.<Integer, MapNode<Integer, LongContent>>of(10, leaf(), 5, leaf()))));
Assert.assertEquals(expected, actual);
}
@Test
public void nodeAndLeafCount() {
final List<int[]> list = Lists.newArrayList();
IntArrayCallback callback = new ListCollector(list);
for (int n = 0; n < 8; ++n) {
list.clear();
// Exact sum means that no array will be a prefix of another one.
TestUtils.generateSpecWithExactSum(n, 2 * n, callback);
Collections.sort(list, IntArrayComparator.INSTANCE);
BoundedRollup<Integer, LongContent> rollup =
BoundedRollup.create(TestUtils.ZERO_LONG_CONTENT, Integer.MAX_VALUE);
MapNode<Integer, LongContent> actual = createTree(list, rollup);
checkTreeIsComplete(list, n, actual);
}
}
private void checkTreeIsComplete(final List<int[]> list, int n,
MapNode<Integer, LongContent> actual) {
Assert.assertEquals(list.size(), actual.getValue().value());
int[] subtreeSizeAndLeafCount = actual.subtreeSizeAndLeafCount();
Assert.assertEquals(n == 0 ? 1 : 2 * list.size(), subtreeSizeAndLeafCount[0]);
Assert.assertEquals(list.size(), subtreeSizeAndLeafCount[1]);
}
@Test
public void oneNodeAtMost() {
final List<int[]> list = Lists.newArrayList();
IntArrayCallback callback = new ListCollector(list);
for (int n = 0; n < 8; ++n) {
list.clear();
// Exact sum means that no array will be a prefix of another one.
TestUtils.generateSpecWithExactSum(n, 2 * n, callback);
Collections.sort(list, IntArrayComparator.INSTANCE);
BoundedRollup<Integer, LongContent> rollup =
BoundedRollup.create(TestUtils.ZERO_LONG_CONTENT, 1);
MapNode<Integer, LongContent> actual = createTree(list, rollup);
Assert.assertEquals(MapNode.create(new LongContent(list.size()),
ImmutableMap.<Integer, MapNode<Integer, LongContent>>of()), actual);
}
}
@Test
public void subtreeIsOptimalWithinConstraints() {
final List<int[]> list = Lists.newArrayList();
IntArrayCallback callback = new ListCollector(list);
for (int n = 0; n < 3; ++n) {
list.clear();
// Exact sum means that no array will be a prefix of another one.
TestUtils.generateSpecWithExactSum(n, 2 * n, callback);
Collections.sort(list, IntArrayComparator.INSTANCE);
BoundedRollup<Integer, LongContent> rollup =
BoundedRollup.create(TestUtils.ZERO_LONG_CONTENT, Integer.MAX_VALUE);
MapNode<Integer, LongContent> fullTreeRoot = createTree(list, rollup);
checkTreeIsComplete(list, n, fullTreeRoot);
int fullTreeSize = fullTreeRoot.subtreeSizeAndLeafCount()[0];
for (int i = 1; i <= fullTreeSize + 5; ++i) {
List<List<MapNode<Integer, LongContent>>> allPossibleExpansions =
allPossibleExpansions(fullTreeRoot, i);
BoundedRollup<Integer, LongContent> constrainedRollup =
BoundedRollup.create(TestUtils.ZERO_LONG_CONTENT, i);
MapNode<Integer, LongContent> constrainedTreeRoot =
createTree(list, constrainedRollup);
List<MapNode<Integer, LongContent>> actual = constrainedTreeRoot.preorder();
List<List<MapNode<Integer, LongContent>>> all =
toSortedValueListList(allPossibleExpansions);
List<MapNode<Integer, LongContent>> constrained = toSortedValueList(actual);
List<List<MapNode<Integer, LongContent>>> allSameSize = Lists.newArrayList();
for (List<MapNode<Integer, LongContent>> row : all) {
Assert.assertTrue(row.size() <= constrained.size());
if (row.size() == constrained.size()) {
allSameSize.add(row);
}
}
Assert.assertEquals(Collections.max(allSameSize,
new ListComparator<MapNode<Integer, LongContent>>(
new MapNodeValueComparator<Integer, LongContent>())), constrained);
}
}
}
private MapNode<Integer, LongContent> createTree(
final List<int[]> list, BoundedRollup<Integer, LongContent> rollup) {
for (int[] array : list) {
rollup.feedRow(Ints.asList(array).iterator(), TestUtils.ONE_LONG_CONTENT);
}
MapNode<Integer, LongContent> root = rollup.finish();
return root;
}
/**
* Test the test helper method {@link #allPossibleExpansions}.
*/
@Test
public void allPossibleExpansionsForRootOnly() {
MapNode<Integer, String> root =
MapNode.create("x", ImmutableMap.<Integer, MapNode<Integer, String>>of());
Assert.assertTrue(allPossibleExpansions(root, 0).isEmpty());
for (int i = 1; i < 3; ++i) {
MoreAsserts.assertContentsAnyOrder(allPossibleExpansions(root, i), ImmutableList.of(root));
}
}
/**
* Test the test helper method {@link #allPossibleExpansions}.
*/
@Test
public void allPossibleExpansionsForThreeNodes() {
MapNode<Integer, String> rightGrandchild =
MapNode.create("d", ImmutableMap.<Integer, MapNode<Integer, String>>of());
MapNode<Integer, String> rightChild =
MapNode.create("c", ImmutableMap.<Integer, MapNode<Integer, String>>of(2, rightGrandchild));
MapNode<Integer, String> leftChild =
MapNode.create("b", ImmutableMap.<Integer, MapNode<Integer, String>>of());
MapNode<Integer, String> root = MapNode.create(
"a", ImmutableMap.<Integer, MapNode<Integer, String>>of(0, leftChild, 1, rightChild));
Assert.assertTrue(allPossibleExpansions(root, 0).isEmpty());
MoreAsserts.assertContentsAnyOrder(allPossibleExpansions(root, 1), ImmutableList.of(root));
MoreAsserts.assertContentsAnyOrder(allPossibleExpansions(root, 2), ImmutableList.of(root));
MoreAsserts.assertContentsAnyOrder(toIdentitySetList(allPossibleExpansions(root, 3)),
toIdentitySet(ImmutableList.of(root)),
toIdentitySet(ImmutableList.of(root, leftChild, rightChild)));
List<Set<MapNode<Integer, String>>> actual = toIdentitySetList(allPossibleExpansions(root, 4));
MoreAsserts.assertContentsAnyOrder(actual, toIdentitySet(ImmutableList.of(root)),
toIdentitySet(ImmutableList.of(root, leftChild, rightChild)),
toIdentitySet(ImmutableList.of(root, leftChild, rightChild, rightGrandchild)));
}
private static <K, V> List<Set<MapNode<K, V>>> toIdentitySetList(
List<List<MapNode<K, V>>> listList) {
List<Set<MapNode<K, V>>> setList = new ArrayList<Set<MapNode<K, V>>>(listList.size());
for (List<MapNode<K, V>> list : listList) {
Set<MapNode<K, V>> set = toIdentitySet(list);
setList.add(set);
}
return setList;
}
private static <V, K> Set<MapNode<K, V>> toIdentitySet(List<MapNode<K, V>> list) {
Set<MapNode<K, V>> set =
Collections.newSetFromMap(new IdentityHashMap<MapNode<K, V>, Boolean>());
set.addAll(list);
Assert.assertEquals(list.size(), set.size());
return set;
}
private static <K, V extends Comparable<V>> List<List<MapNode<K, V>>> toSortedValueListList(
List<List<MapNode<K, V>>> listList) {
List<List<MapNode<K, V>>> result = new ArrayList<List<MapNode<K, V>>>(listList.size());
for (List<MapNode<K, V>> list : listList) {
List<MapNode<K, V>> set = toSortedValueList(list);
result.add(set);
}
return result;
}
private static <K, V extends Comparable<V>>
List<MapNode<K, V>> toSortedValueList(List<MapNode<K, V>> list) {
List<MapNode<K, V>> result = new ArrayList<MapNode<K, V>>(list.size());
for (MapNode<K, V> node : list) {
result.add(MapNode.create(node.getValue(), ImmutableMap.<K, MapNode<K, V>>of()));
}
Collections.sort(result, new MapNodeValueComparator<K, V>());
return result;
}
private static class MapNodeValueComparator<K, V extends Comparable<V>>
implements Comparator<MapNode<K, V>> {
@Override
public int compare(MapNode<K, V> o1, MapNode<K, V> o2) {
return o1.getValue().compareTo(o2.getValue());
}
}
private static <K, V> List<List<MapNode<K, V>>> allPossibleExpansions(
final MapNode<K, V> node, int maxNodes) {
List<List<MapNode<K, V>>> result = Lists.newArrayList();
for (int i = 1; i <= maxNodes; ++i) {
List<List<MapNode<K, V>>> expansions = allExpansionsWithExactNodeCount(node, i);
result.addAll(expansions);
}
return result;
}
private static <K, V> List<List<MapNode<K, V>>> allExpansionsWithExactNodeCount(
final MapNode<K, V> node, final int exactNodes) {
if (exactNodes == 1) {
return ImmutableList.of((List<MapNode<K, V>>) ImmutableList.of(node));
}
List<List<MapNode<K, V>>> result = Lists.newArrayList();
assert exactNodes > 1;
if (exactNodes > node.getChildren().size()) {
final List<MapNode<K, V>> children = ImmutableList.copyOf(node.getChildren().values());
final List<List<List<List<MapNode<K, V>>>>> childrenExpansions = Lists.newArrayList();
IntArrayCallback callback = new IntArrayCallback() {
@Override
public void call(int[] m) {
assert m.length <= children.size();
if (m.length == children.size()) {
List<List<List<MapNode<K, V>>>> expansionSet = Lists.newArrayList();
for (int i = 0; i < m.length; ++i) {
List<List<MapNode<K, V>>> childExpansions =
allExpansionsWithExactNodeCount(children.get(i), m[i] + 1);
for (List<MapNode<K, V>> childExpansion : childExpansions) {
Assert.assertEquals(m[i] + 1, childExpansion.size());
}
if (childExpansions.isEmpty()) {
return;
} else {
expansionSet.add(childExpansions);
}
}
Assert.assertEquals(m.length, expansionSet.size());
childrenExpansions.add(expansionSet);
}
}
};
TestUtils.generateSpecWithExactSum(
node.getChildren().size(), exactNodes - 1 - children.size(), callback);
int[] childOffset = new int[node.getChildren().size()];
for (List<List<List<MapNode<K, V>>>> childrenExpansion : childrenExpansions) {
assert childrenExpansion.size() == children.size();
Arrays.fill(childOffset, -1);
int k = 0;
while (k >= 0) {
if (k == childrenExpansion.size()) {
List<MapNode<K, V>> expansion = Lists.newArrayList();
for (int i = 0; i < k; ++i) {
expansion.addAll(childrenExpansion.get(i).get(childOffset[i]));
}
assert expansion.size() == exactNodes - 1;
expansion.add(node);
result.add(expansion);
k--;
} else if (childOffset[k] < childrenExpansion.get(k).size() - 1) {
childOffset[k++]++;
} else {
childOffset[k--] = -1;
}
}
}
}
for (List<MapNode<K, V>> list : result) {
Assert.assertEquals(exactNodes, list.size());
}
return result;
}
private LongContent newCountingArray(long count) {
return new LongContent(count);
}
/**
* Will put all array callbacks which don't have zeroes into the supplied
* list.
*/
private static class ListCollector implements IntArrayCallback {
private final List<int[]> list;
public ListCollector(List<int[]> list) {
this.list = list;
}
@Override
public void call(int[] m) {
if (!ArrayUtils.contains(m, 0)) {
list.add(Arrays.copyOf(m, m.length));
}
}
}
private static class ListComparator<T> implements Comparator<Iterable<T>> {
private final Comparator<T> delegate;
public ListComparator(Comparator<T> delegate) {
this.delegate = delegate;
}
@Override
public int compare(Iterable<T> o1, Iterable<T> o2) {
Iterator<T> it1 = o1.iterator();
Iterator<T> it2 = o2.iterator();
int result = 0;
while (it1.hasNext()) {
if (!it2.hasNext()) {
result = +1;
break;
}
T t1 = it1.next();
T t2 = it2.next();
int cmp = delegate.compare(t1, t2);
if (cmp != 0) {
result = cmp;
break;
}
}
if (result == 0) {
result = it2.hasNext() ? -1 : 0;
}
return result;
}
}
}