import jalview.datamodel.SequenceFeature;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import intervalstore.impl.BinarySearcher;
import intervalstore.impl.IntervalStore;
+
/**
* A data store for a set of sequence features that supports efficient lookup of
* features overlapping a given range. Intended for (but not limited to) storage
float nonPositionalMaxScore;
- private SequenceFeature[] temp = new SequenceFeature[3];
-
- private boolean isTainted;
+ private ArrayList<SequenceFeature> featuresList;
/**
* Constructor
public FeatureStore()
{
features = new IntervalStore<>();
+ featuresList = new ArrayList<>();
positionalFeatureGroups = new HashSet<>();
nonPositionalFeatureGroups = new HashSet<>();
positionalMinScore = Float.NaN;
*
* @param feature
*/
+
public boolean addFeature(SequenceFeature feature)
{
if (contains(feature))
{
if (feature.isNonPositional())
{
- return nonPositionalFeatures == null ? false : nonPositionalFeatures
- .contains(feature);
+ return nonPositionalFeatures == null ? false
+ : nonPositionalFeatures.contains(feature);
}
if (feature.isContactFeature())
{
- return contactFeatureStarts == null ? false : listContains(
- contactFeatureStarts, feature);
+ return contactFeatureStarts == null ? false
+ : listContains(contactFeatureStarts, feature);
}
- return features == null ? false : features
- .contains(feature);
+ return features == null ? false : features.contains(feature);
}
/**
features = new IntervalStore<>();
}
features.add(feature);
- isTainted = true;
+ featuresList.add(feature);
}
/**
f -> f.getBegin() >= feature.getBegin());
contactFeatureStarts.add(insertPosition, feature);
-
/*
* insert into list sorted by end (second contact position):
* binary search the sorted list to find the insertion point
* end position of overlap range (inclusive)
* @return
*/
+
public List<SequenceFeature> findOverlappingFeatures(long start, long end)
{
List<SequenceFeature> result = new ArrayList<>();
SequenceFeature sf = contactFeatureEnds.get(index);
if (!sf.isContactFeature())
{
- System.err.println("Error! non-contact feature type "
- + sf.getType() + " in contact features list");
+ System.err.println("Error! non-contact feature type " + sf.getType()
+ + " in contact features list");
index++;
continue;
}
*
* @return
*/
+
public List<SequenceFeature> getPositionalFeatures()
{
List<SequenceFeature> result = new ArrayList<>();
*
* @return
*/
+
public List<SequenceFeature> getContactFeatures()
{
if (contactFeatureStarts == null)
*
* @return
*/
+
public List<SequenceFeature> getNonPositionalFeatures()
{
if (nonPositionalFeatures == null)
*
* @param sf
*/
+
public synchronized boolean delete(SequenceFeature sf)
{
boolean removed = false;
if (!removed && features != null)
{
removed = features.remove(sf);
+ featuresList.remove(sf);
}
if (removed)
positionalMaxScore = Float.NaN;
nonPositionalMinScore = Float.NaN;
nonPositionalMaxScore = Float.NaN;
- isTainted = true;
/*
* scan non-positional features for groups and scores
*/
*
* @return
*/
+
public boolean isEmpty()
{
boolean hasFeatures = (contactFeatureStarts != null
- && !contactFeatureStarts
- .isEmpty())
- || (nonPositionalFeatures != null && !nonPositionalFeatures
- .isEmpty())
+ && !contactFeatureStarts.isEmpty())
+ || (nonPositionalFeatures != null
+ && !nonPositionalFeatures.isEmpty())
|| (features != null && features.size() > 0);
return !hasFeatures;
* @param positionalFeatures
* @return
*/
+
public Set<String> getFeatureGroups(boolean positionalFeatures)
{
if (positionalFeatures)
}
else
{
- return nonPositionalFeatureGroups == null ? Collections
- .<String> emptySet() : Collections
- .unmodifiableSet(nonPositionalFeatureGroups);
+ return nonPositionalFeatureGroups == null
+ ? Collections.<String> emptySet()
+ : Collections.unmodifiableSet(nonPositionalFeatureGroups);
}
}
* @param positional
* @return
*/
+
public int getFeatureCount(boolean positional)
{
if (!positional)
{
- return nonPositionalFeatures == null ? 0 : nonPositionalFeatures
- .size();
+ return nonPositionalFeatures == null ? 0
+ : nonPositionalFeatures.size();
}
int size = 0;
*
* @return
*/
+
public int getTotalFeatureLength()
{
return totalExtent;
* @param positional
* @return
*/
+
public float getMinimumScore(boolean positional)
{
return positional ? positionalMinScore : nonPositionalMinScore;
* @param positional
* @return
*/
+
public float getMaximumScore(boolean positional)
{
return positional ? positionalMaxScore : nonPositionalMaxScore;
* @param group
* @return
*/
+
public List<SequenceFeature> getFeaturesForGroup(boolean positional,
String group)
{
for (SequenceFeature sf : sfs)
{
String featureGroup = sf.getFeatureGroup();
- if (group == null && featureGroup == null || group != null
- && group.equals(featureGroup))
+ if (group == null && featureGroup == null
+ || group != null && group.equals(featureGroup))
{
result.add(sf);
}
* @param shiftBy
* @return
*/
+
public synchronized boolean shiftFeatures(int fromPosition, int shiftBy)
{
/*
/**
* Find all features containing this position.
- * Uses isTainted field to know when to reconstruct its temporary array.
*
* @param pos
* @return list of SequenceFeatures
* @author Bob Hanson 2019.07.30
*/
- public void findOverlappingFeatures(int pos, List<SequenceFeature> result)
+
+ public List<SequenceFeature> findOverlappingFeatures(int pos,
+ List<SequenceFeature> result)
{
+ if (result == null)
+ {
+ result = new ArrayList<>();
+ }
if (contactFeatureStarts != null)
{
findContacts(contactFeatureStarts, pos, result, true);
findContacts(contactFeatureEnds, pos, result, false);
}
- if (features != null)
+ if (featuresList != null)
{
- int n = features.size();
- if (isTainted)
- {
- isTainted = false;
- if (temp.length < n)
- {
- temp = new SequenceFeature[n << 1];
- }
- features.toArray(temp);
- }
- findOverlaps(temp, n, pos, result);
+ findOverlaps(featuresList, pos, result);
}
+ return result;
}
/**
}
}
+ BitSet bs = new BitSet();
+
/**
- * Brute force point-interval overlap test
+ * Double binary sort with bitset correlation
+ *
*
* @param features
- * @param n
* @param pos
* @param result
*/
- private static void findOverlaps(SequenceFeature[] features, int n,
- int pos,
+ private void findOverlaps(List<SequenceFeature> features, int pos,
List<SequenceFeature> result)
{
- // BH I know, brute force. We need a single-position overlap
- // method for IntervalStore, I think.
- for (int i = n; --i >= 0;)
+ int n = featuresList.size();
+ if (n == 1)
+ {
+ checkOne(featuresList.get(0), pos, result);
+ return;
+ }
+ if (orderedFeatureStarts == null)
{
- SequenceFeature f = features[i];
- if (f.begin <= pos && f.end >= pos)
+ rebuildArrays(n);
+ }
+ SequenceFeature sf = findClosestFeature(orderedFeatureStarts, pos);
+ while (sf != null) {
+ if (sf.end >= pos)
{
- result.add(f);
+ result.add(sf);
}
+ sf = sf.containedBy;
}
}
+ private void linkFeatures(SequenceFeature[] intervals)
+ {
+ if (intervals.length < 2)
+ {
+ return;
+ }
+ int maxEnd = intervals[0].end;
+ for (int i = 1, n = intervals.length; i < n; i++)
+ {
+ SequenceFeature ithis = intervals[i];
+ if (ithis.begin <= maxEnd)
+ {
+ ithis.containedBy = getContainedBy(intervals[i - 1], ithis);
+ }
+ if (ithis.end > maxEnd)
+ {
+ maxEnd = ithis.end;
+ }
+ }
+ }
+
+ private SequenceFeature getContainedBy(SequenceFeature sf,
+ SequenceFeature sf0)
+ {
+ int begin = sf0.begin;
+ while (sf != null)
+ {
+ if (begin <= sf.end)
+ {
+ System.out.println("\nFS found " + sf0.index + ":" + sf0
+ + "\nFS in " + sf.index + ":" + sf);
+ return sf;
+ }
+ sf = sf.containedBy;
+ }
+ return null;
+ }
+
+ private SequenceFeature findClosestFeature(SequenceFeature[] l, int pos)
+ {
+ int low = 0;
+ int high = l.length - 1;
+ while (low <= high)
+ {
+ int mid = (low + high) >>> 1;
+ SequenceFeature f = l[mid];
+ switch (Long.signum(f.begin - pos))
+ {
+ case -1:
+ low = mid + 1;
+ continue;
+ case 1:
+ high = mid - 1;
+ continue;
+ case 0:
+
+ while (++mid <= high && l[mid].begin == pos)
+ {
+ ;
+ }
+ mid--;
+ return l[mid];
+ }
+ }
+ // -1 here?
+ return (high < 0 || low >= l.length ? null : l[high]);
+ }
+
+ private void checkOne(SequenceFeature sf, int pos,
+ List<SequenceFeature> result)
+ {
+ if (sf.begin <= pos && sf.end >= pos)
+ {
+ result.add(sf);
+ }
+ return;
+ }
+
+ /*
+ * contact features ordered by first contact position
+ */
+ private SequenceFeature[] orderedFeatureStarts;
+
+ private void rebuildArrays(int n)
+ {
+ if (startComp == null)
+ {
+ startComp = new StartComparator();
+ }
+ orderedFeatureStarts = new SequenceFeature[n];
+
+ for (int i = n; --i >= 0;)
+ {
+ SequenceFeature sf = featuresList.get(i);
+ sf.index = i;
+ orderedFeatureStarts[i] = sf;
+ }
+ Arrays.sort(orderedFeatureStarts, startComp);
+ linkFeatures(orderedFeatureStarts);
+ }
+
+ class StartComparator implements Comparator<SequenceFeature>
+ {
+
+ int pos;
+
+ @Override
+ public int compare(SequenceFeature o1, SequenceFeature o2)
+ {
+ int p1 = o1.begin;
+ int p2 = o2.begin;
+ return (p1 < p2 ? -1 : p1 > p2 ? 1 : 0);
+ }
+
+ }
+
+ static StartComparator startComp;
+
+ // class EndComparator implements Comparator<SequenceFeature>
+ // {
+ //
+ // int pos;
+ //
+ // @Override
+ // public int compare(SequenceFeature o1, SequenceFeature o2)
+ // {
+ // int p1 = o1.end;
+ // int p2 = o2.end;
+ // int val = (p1 < p2 ? 1 : p1 > p2 ? -1 : 0);
+ // return val;
+ // }
+ //
+ // }
+ //
+ // static EndComparator endComp;
+
}