/*
* IntervalTreeTest.java
*
* Copyright (C) 2014 Leo Osvald <leo.osvald@gmail.com>
*
* This file is part of SGLJ.
*
* SGLJ is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* SGLJ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sglj.util.struct;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
import org.junit.Test;
public class IntervalTreeTest {
static class Interval implements Comparable<Interval> {
Integer id;
Integer from, to;
static int curId = 0;
public Interval(Integer from, Integer to) {
this(from, to, curId++);
}
public Interval(Integer from, Integer to, Integer id) {
this.from = from;
this.to = to;
this.id = id;
}
@Override
public int compareTo(Interval o) {
return id.compareTo(o.id);
}
@Override
public String toString() {
return "(" + from + " " + to + " #" + id + ")";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Interval other = (Interval) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}
static class VerifiedIntervalTree extends IntervalTree<Interval, Integer> {
Set<Interval> intervals = new TreeSet<Interval>(traits.ascComparator);
public VerifiedIntervalTree() {
super(new IntervalTraits<Interval, Integer>() {
@Override
public Integer from(Interval interval) {
return interval.from;
}
@Override
public Integer to(Interval interval) {
return interval.to;
}
@Override
public Interval pointInterval(Integer endpoints) {
return new Interval(endpoints, endpoints, Integer.MIN_VALUE);
}
});
}
private static String repr(AvlNode<?> node) {
@SuppressWarnings("unchecked")
Node<Interval, Integer> n = (Node<Interval, Integer>)node;
return n.point.from + ": " + n.asc;
}
static void repr(AvlNode<?> node, StringBuilder sb) {
sb.append('(');
if (node != null) {
sb.append(repr(node));
sb.append(" <");
repr(node.left, sb);
sb.append(" >");
repr(node.right, sb);
}
sb.append(')');
}
String repr() {
StringBuilder sb = new StringBuilder();
repr(getRoot(), sb);
return sb.toString();
}
@Override
public boolean add(Interval e) {
intervals.add(e);
return super.add(e);
}
@Override
public boolean remove(Object o) {
intervals.remove(o);
return super.remove(o);
}
@Override
public void findOverlapping(Integer point, Collection<Interval> result) {
Set<Interval> expResult = new TreeSet<Interval>(result);
for (Interval interval : intervals) {
if (interval.from.compareTo(point) <= 0
&& point.compareTo(interval.to) <= 0)
expResult.add(interval);
}
super.findOverlapping(point, result);
Set<Interval> actResult = new TreeSet<Interval>(result);
assertEquals(expResult.toString(), actResult.toString());
}
Interval randomContained(Random random) {
if (intervals.isEmpty())
return null;
int ord = random.nextInt(intervals.size());
for (Interval interval : intervals)
if (ord-- == 0)
return interval;
return null;
}
}
static void assertRepr(VerifiedIntervalTree t, String expected) {
assertEquals(expected, t.repr());
}
private static void permute(String prefix, String str, List<int[]> perms) {
int n = str.length();
if (n == 0) {
int[] a = new int[prefix.length()];
for (int i = 0; i < a.length; ++i)
a[i] = prefix.charAt(i) - '0';
perms.add(a);
}
else {
for (int i = 0; i < n; i++)
permute(prefix + str.charAt(i),
str.substring(0, i) + str.substring(i+1, n),
perms);
}
}
static List<int[]> permute(int n) {
List<int[]> perms = new ArrayList<int[]>();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; ++i) {
sb.append((char)('0' + i));
permute("", sb.toString(), perms);
}
return perms;
}
static final int[] permFrom = new int[]{0, 0, 0, 1, 1, 2};
static final int[] permTo = new int[]{1, 2, 3, 2, 3, 3};
@Test
public void testMoveAfterRotate() {
VerifiedIntervalTree vit = new VerifiedIntervalTree();
vit.add(new Interval(2, 3, 23));
vit.add(new Interval(2, 7, 27));
vit.add(new Interval(1, 1, 11));
vit.add(new Interval(5, 6, 56));
vit.add(new Interval(8, 9, 89));
vit.add(new Interval(4, 4, 44));
assertRepr(vit, "(2: [(2 7 #27), (2 3 #23)]"
+ " <(1: [(1 1 #11)] <() >())"
+ " >(5: [(5 6 #56)] <(4: [(4 4 #44)] <() >()) >(8: [(8 9 #89)] <() >())))");
vit.remove(new Interval(1, 1, 11));
assertRepr(vit, "(5: [(2 7 #27), (5 6 #56)]"
+ " <(2: [(2 3 #23)] <() >(4: [(4 4 #44)] <() >()))"
+ " >(8: [(8 9 #89)] <() >()))");
}
@Test
public void testFindOverlappingAllPermutations() {
for (int[] perm : permute(permFrom.length)) {
VerifiedIntervalTree vit = new VerifiedIntervalTree();
System.out.println(Arrays.toString(perm));
for (int id = 0; id < perm.length; ++id) {
Interval in = new Interval(permFrom[perm[id]], permTo[perm[id]],
id);
vit.add(in);
System.out.println("+ " + in);
}
System.out.println(vit.repr());
for (int point = 0; point < perm.length; ++point) {
TreeSet<Interval> overlapping = new TreeSet<Interval>();
System.out.print("> " + point);
vit.findOverlapping(point, overlapping);
//System.out.println(": " + overlapping);
}
}
}
@Test
public void testSameIntervalsAllPermutations() {
for (int[] perm : permute(4)) {
for (int toRemove : perm) {
VerifiedIntervalTree vit = new VerifiedIntervalTree();
for (int id : perm)
vit.add(new Interval(2, 2, id));
vit.remove(new Interval(2, 2, toRemove));
if (perm.length == 1) {
assertRepr(vit, "()");
continue;
}
StringBuilder sb = new StringBuilder("(2: [");
boolean first = true;
for (int id = perm.length - 1; id >= 0; --id)
if (id != toRemove) {
if (!first) sb.append(", ");
sb.append("(2 2 #" + id + ")");
first = false;
}
sb.append("] <() >())");
assertEquals(sb.toString(), vit.repr());
}
}
}
static Interval randomInterval(Random r, int coordMax) {
int a = r.nextInt(coordMax);
int b = r.nextInt(coordMax);
return a <= b ? new Interval(a, b) : new Interval(b, a);
}
@Test
public void testRandomOverlapping() {
final int sizeAvg = 100;
final int coordMax = 50;
final Random r = new Random(42);
VerifiedIntervalTree vit = new VerifiedIntervalTree();
Interval.curId = 0;
for (int i = 0; i < sizeAvg; ++i) {
vit.add(randomInterval(r, coordMax));
}
for (int itr = 0; itr < 1000; ++itr) {
//System.out.println(vit.repr());
boolean add;
int sizeDiff = vit.intervals.size() - sizeAvg;
if (sizeDiff > 10)
add = false;
else if (sizeDiff < -10)
add = true;
else
add = r.nextBoolean();
if (add) {
Interval in = randomInterval(r, coordMax);
//System.out.println("+ " + in);
vit.add(in);
} else {
Interval in = vit.randomContained(r);
//System.out.println("- " + in);
vit.remove(in);
}
for (int i = 0; i < 5; ++i) {
vit.findOverlapping(r.nextInt(coordMax), new TreeSet<Interval>());
}
}
}
}