*/
public class NCList<T extends ContiguousI>
{
+ /*
+ * the number of ranges represented
+ */
+ private int size;
/*
* a list, in start position order, of sublists of ranges ordered so
*/
public NCList(List<T> ranges)
{
+ this();
build(ranges);
}
List<SubList> sublists = findSubranges(ranges);
- subranges = new ArrayList<NCNode<T>>();
-
/*
* convert each subrange to an NCNode consisting of a range and
* (possibly) its contained NCList
subranges.add(new NCNode<T>(ranges.subList(sublist.startIndex,
sublist.endIndex + 1)));
}
- sublists.clear();
+
+ size = ranges.size();
}
public NCList(T entry)
{
+ this();
List<T> ranges = new ArrayList<T>();
ranges.add(entry);
build(ranges);
}
+ public NCList()
+ {
+ subranges = new ArrayList<NCNode<T>>();
+ }
+
/**
* Traverses the sorted ranges to identify sublists, within which each
* interval contains the one that follows it
*/
public synchronized void add(T entry)
{
+ size++;
long start = entry.getBegin();
long end = entry.getEnd();
* search for maximal span of subranges i-k that the new entry
* encloses; or a subrange that encloses the new entry
*/
- int i = candidateIndex;
boolean enclosing = false;
+ int firstEnclosed = 0;
+ int lastEnclosed = 0;
boolean overlapping = false;
for (int j = candidateIndex; j < subranges.size(); j++)
{
NCNode<T> subrange = subranges.get(j);
- if (end < subrange.getStart() && !overlapping)
+ if (end < subrange.getStart() && !overlapping && !enclosing)
{
/*
* new entry lies between subranges j-1 j
* new entry encloses this subrange (and possibly preceding ones);
* continue to find the maximal list it encloses
*/
+ if (!enclosing)
+ {
+ firstEnclosed = j;
+ }
+ lastEnclosed = j;
enclosing = true;
continue;
}
/*
* entry encloses one or more preceding subranges
*/
- addEnclosingRange(entry, i, j - 1);
+ addEnclosingRange(entry, firstEnclosed, lastEnclosed);
return;
}
else
}
}
}
+ else
+ {
+ overlapping = true;
+ }
+ }
+
+ /*
+ * drops through to here if new range encloses all others
+ */
+ if (enclosing)
+ {
+ addEnclosingRange(entry, firstEnclosed, lastEnclosed);
}
}
/**
* Update the tree so that the range of the new entry encloses subranges i to
- * j (inclusive). That is, replace subranges i-j with a new subrange that
- * contains them.
+ * j (inclusive). That is, replace subranges i-j (inclusive) with a new
+ * subrange that contains them.
*
* @param entry
* @param i
* @param j
*/
- protected synchronized void addEnclosingRange(T entry, int i, int j)
+ protected synchronized void addEnclosingRange(T entry, final int i,
+ final int j)
{
- // TODO how to rebuild the subranges as an NCList?
-
+ NCList<T> newNCList = new NCList<T>();
+ newNCList.subranges.addAll(subranges.subList(i, j + 1));
+ NCNode<T> newNode = new NCNode<T>(entry, newNCList);
+ for (int k = j; k >= i; k--)
+ {
+ subranges.remove(k);
+ }
+ subranges.add(i, newNode);
}
/**
{
return subranges.toString();
}
+
+ /**
+ * Returns a string representation of the data where containment is shown bgy
+ * indentation on new lines
+ *
+ * @return
+ */
+ public String prettyPrint()
+ {
+ StringBuilder sb = new StringBuilder(512);
+ int offset = 0;
+ int indent = 2;
+ prettyPrint(sb, offset, indent);
+ return sb.toString();
+ }
+
+ /**
+ * @param sb
+ * @param offset
+ * @param indent
+ */
+ void prettyPrint(StringBuilder sb, int offset, int indent)
+ {
+ boolean first = true;
+ for (NCNode<T> subrange : subranges)
+ {
+ if (!first)
+ {
+ sb.append(System.lineSeparator());
+ }
+ first = false;
+ subrange.prettyPrint(sb, offset, indent);
+ }
+ }
+
+ /**
+ * Answers true if the data held satisfy the rules of construction of an
+ * NCList, else false.
+ *
+ * @return
+ */
+ public boolean isValid()
+ {
+ return isValid(Integer.MIN_VALUE, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Answers true if the data held satisfy the rules of construction of an
+ * NCList bounded within the given start-end range, else false.
+ * <p>
+ * Each subrange must lie within start-end (inclusive). Subranges must be
+ * ordered by start position ascending.
+ * <p>
+ *
+ * @param start
+ * @param end
+ * @return
+ */
+ boolean isValid(final int start, final int end)
+ {
+ int lastStart = start;
+ for (NCNode<T> subrange : subranges)
+ {
+ if (subrange.getStart() < lastStart)
+ {
+ System.err.println("error in NCList: range " + subrange.toString()
+ + " starts before " + lastStart);
+ return false;
+ }
+ if (subrange.getEnd() > end)
+ {
+ System.err.println("error in NCList: range " + subrange.toString()
+ + " ends after " + end);
+ return false;
+ }
+ lastStart = subrange.getStart();
+
+ if (!subrange.isValid())
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Answers the lowest start position enclosed by the ranges
+ *
+ * @return
+ */
+ public int getStart()
+ {
+ return subranges.isEmpty() ? 0 : subranges.get(0).getStart();
+ }
+
+ /**
+ * Returns the number of ranges held (deep count)
+ *
+ * @return
+ */
+ public int getSize()
+ {
+ return size;
+ }
}
private NCList<V> subregions;
/**
- * Constructor
+ * Constructor given a list of ranges
*
* @param ranges
*/
}
/**
- * Constructo
+ * Constructor given a single range
*
* @param range
*/
build(ranges);
}
+ NCNode(V entry, NCList<V> newNCList)
+ {
+ region = entry;
+ subregions = newNCList;
+ // size = 1 + newNCList.size();
+ }
+
/**
* @param ranges
*/
return region.getEnd();
}
+ /**
+ * Formats the node as a bracketed list e.g.
+ *
+ * <pre>
+ * [1-100 [10-30 [10-20]], 15-30 [20-20]]
+ * </pre>
+ */
@Override
public String toString() {
StringBuilder sb = new StringBuilder(10 * size);
return sb.toString();
}
+ void prettyPrint(StringBuilder sb, int offset, int indent) {
+ for (int i = 0 ; i < offset ; i++) {
+ sb.append(" ");
+ }
+ sb.append(region.getBegin()).append("-").append(region.getEnd());
+ if (subregions != null)
+ {
+ sb.append(System.lineSeparator());
+ subregions.prettyPrint(sb, offset + 2, indent);
+ }
+ }
/**
* Add any ranges that overlap the from-to range to the result list
*
*
* @param entry
*/
- public synchronized void add(V entry)
+ synchronized void add(V entry)
{
if (entry.getBegin() < region.getBegin() || entry.getEnd() > region.getEnd()) {
throw new IllegalArgumentException(String.format(
subregions.add(entry);
}
}
+
+ /**
+ * Answers true if the data held satisfy the rules of construction of an
+ * NCList, else false.
+ *
+ * @return
+ */
+ boolean isValid()
+ {
+ if (subregions == null)
+ {
+ return true;
+ }
+ return subregions.isValid(getStart(), getEnd());
+ }
}
package jalview.datamodel.features;
import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Random;
import org.testng.annotations.Test;
NCList<Range> ncl = new NCList<Range>(ranges);
String expected = "[1-100 [10-30 [10-20 [10-20 [11-19]]]], 15-30 [20-20]]";
assertEquals(ncl.toString(), expected);
+ assertTrue(ncl.isValid());
Collections.reverse(ranges);
ncl = new NCList<Range>(ranges);
assertEquals(ncl.toString(), expected);
+ assertTrue(ncl.isValid());
}
@Test(groups = "Functional")
ranges.add(new Range(20, 50));
NCList<Range> ncl = new NCList<Range>(ranges);
assertEquals(ncl.toString(), "[20-50]");
+ assertTrue(ncl.isValid());
ncl.add(new Range(60, 70));
assertEquals(ncl.toString(), "[20-50, 60-70]");
+ assertTrue(ncl.isValid());
}
@Test(groups = "Functional")
ranges.add(new Range(20, 50));
NCList<Range> ncl = new NCList<Range>(ranges);
assertEquals(ncl.toString(), "[20-50]");
+ assertTrue(ncl.isValid());
ncl.add(new Range(30, 40));
assertEquals(ncl.toString(), "[20-50 [30-40]]");
ranges.add(new Range(20, 50));
NCList<Range> ncl = new NCList<Range>(ranges);
assertEquals(ncl.toString(), "[20-50]");
+ assertTrue(ncl.isValid());
ncl.add(new Range(5, 15));
assertEquals(ncl.toString(), "[5-15, 20-50]");
+ assertTrue(ncl.isValid());
}
@Test(groups = "Functional")
ranges.add(new Range(30, 60));
NCList<Range> ncl = new NCList<Range>(ranges);
assertEquals(ncl.toString(), "[20-50, 30-60]");
+ assertTrue(ncl.isValid());
+ assertEquals(ncl.getStart(), 20);
ncl.add(new Range(10, 70));
- assertEquals(ncl.toString(), "[10-70 [20-50, 30-60]");
+ assertEquals(ncl.toString(), "[10-70 [20-50, 30-60]]");
+ assertTrue(ncl.isValid());
}
@Test(groups = "Functional")
ranges.add(new Range(60, 70));
NCList<Range> ncl = new NCList<Range>(ranges);
assertEquals(ncl.toString(), "[20-40, 60-70]");
+ assertTrue(ncl.isValid());
ncl.add(new Range(30, 50));
assertEquals(ncl.toString(), "[20-40, 30-50, 60-70]");
+ assertTrue(ncl.isValid());
ncl.add(new Range(40, 65));
assertEquals(ncl.toString(), "[20-40, 30-50, 40-65, 60-70]");
+ assertTrue(ncl.isValid());
+ }
+
+ /**
+ * Do a number of pseudo-random (reproducible) builds of an NCList, with
+ * checks for validity of the data structure, for greater (but not perfect!)
+ * confidence that corner cases are being handled correctly.
+ */
+ @Test(groups = "Functional")
+ public void testAdd_pseudoRandom()
+ {
+ Random r = new Random(108); // well why not?
+ int[] scales = new int[] { 10, 100, 1000 };
+
+ for (int scale : scales)
+ {
+ NCList<Range> ncl = new NCList<Range>();
+ int size = 0;
+ for (int i = 0; i < 100; i++)
+ {
+ int r1 = r.nextInt(scale);
+ int r2 = r.nextInt(scale);
+ int nextFrom = Math.min(r1, r2);
+ int nextTo = Math.max(r1, r2);
+ Range range = new Range(nextFrom, nextTo);
+ ncl.add(range);
+ assertTrue(ncl.isValid(),
+ String.format("Failed for scale = %d, i=%d", scale, i));
+ size++;
+ assertEquals(ncl.getSize(), size);
+ }
+ System.out.println(ncl.prettyPrint());
+ }
}
}