From 2989fded65d7eb892018bcf783425fe23bc679d9 Mon Sep 17 00:00:00 2001 From: James Procter Date: Mon, 21 Aug 2023 15:22:13 +0100 Subject: [PATCH] JAL-4062 appendResult(seq,start,end) called by structureSelectionManager sweep-searches on add to ensure minimal set of non-contiguous ranges are created. Uses a TreeSet to maintain a sorted list of intervals (may be unnecesary) --- src/jalview/datamodel/SearchResultMatchI.java | 9 ++ src/jalview/datamodel/SearchResults.java | 93 +++++++++++++++++++- src/jalview/datamodel/SearchResultsI.java | 13 +++ .../structure/StructureSelectionManager.java | 2 +- test/jalview/datamodel/SearchResultsTest.java | 43 +++++++++ 5 files changed, 156 insertions(+), 4 deletions(-) diff --git a/src/jalview/datamodel/SearchResultMatchI.java b/src/jalview/datamodel/SearchResultMatchI.java index 661ad6c..dedb960 100644 --- a/src/jalview/datamodel/SearchResultMatchI.java +++ b/src/jalview/datamodel/SearchResultMatchI.java @@ -57,4 +57,13 @@ public interface SearchResultMatchI * @return */ boolean contains(SequenceI seq, int start, int end); + + /** + * + * @param seq + * @param from - first position to highlight + * @param to - last position to highlight (assumed higher than from) + * @return true iff from-to intersects or marks positions either side of start/end + */ + boolean adjacent(SequenceI seq, int from, int to); } \ No newline at end of file diff --git a/src/jalview/datamodel/SearchResults.java b/src/jalview/datamodel/SearchResults.java index 909a0fe..8bca20d 100755 --- a/src/jalview/datamodel/SearchResults.java +++ b/src/jalview/datamodel/SearchResults.java @@ -22,7 +22,12 @@ package jalview.datamodel; import java.util.ArrayList; import java.util.BitSet; +import java.util.Collections; import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; + +import com.google.common.collect.Lists; /** * Holds a list of search result matches, where each match is a contiguous @@ -35,13 +40,13 @@ public class SearchResults implements SearchResultsI { private int count; - private List matches = new ArrayList<>(); + private SortedSet matches = new TreeSet<>(); /** * One match consists of a sequence reference, start and end positions. * Discontiguous ranges in a sequence require two or more Match objects. */ - public class Match implements SearchResultMatchI + public class Match implements SearchResultMatchI, Comparable { final SequenceI sequence; @@ -160,6 +165,39 @@ public class SearchResults implements SearchResultsI { return (sequence == seq && start <= from && end >= to); } + @Override + public boolean adjacent(SequenceI seq, int from, int to) + { + return (sequence == seq && ((start <= from && end >= to) || (from<=(end+1) && to >=(end+1)) || (from<=(start-1) && to>=(start-1)))); + } + + @Override + public int compareTo(SearchResultMatchI o) + { + if (start o.getStart()) + { + return +1; + } + if (end < o.getEnd()) + { + return -1; + } + if (end > o.getEnd()) + { + return +1; + } + if (sequence!=o.getSequence()) + { + int hashc =sequence.hashCode(),oseq=o.getSequence().hashCode(); + return (hashc < oseq) ? -1 : 1; + } + return 0; + } + } @Override @@ -191,8 +229,56 @@ public class SearchResults implements SearchResultsI count = beforeCount + 1; } } + @Override + public boolean appendResult(SequenceI sequence, int start, int end) + { + + Match m = new Match(sequence, start, end); + Match toAdd=null; + + if (matches.contains(m)) + { + return false; + } + boolean appending=false; + + // we dynamically maintain an interval to add as we test each range in the list + + int cstart=start,cend=end; + List toRemove=new ArrayList<>(); + for (SearchResultMatchI thatm:matches) + { + if (thatm.getSequence()==sequence) + { + if (thatm.adjacent(sequence, cstart, cend)) + { + // update the match to add with the adjacent start/end + start = Math.min(m.start, thatm.getStart()); + end = Math.max(m.end, thatm.getEnd()); + // and check if we keep or remove the old one + if (thatm.getStart()!=start || thatm.getEnd()!=end) + { + toRemove.add(thatm); + count--; + cstart = start; + cend = end; + appending=true; + } else { + return false; + } + } + } + } + matches.removeAll(toRemove); + { + matches.add(new Match(sequence,cstart,cend)); + count++; + } + return appending; + } + @Override public boolean involvesSequence(SequenceI sequence) { final int start = sequence.getStart(); @@ -320,7 +406,7 @@ public class SearchResults implements SearchResultsI @Override public List getResults() { - return matches; + return List.copyOf(matches); } /** @@ -393,4 +479,5 @@ public class SearchResults implements SearchResultsI } return seqs; } + } diff --git a/src/jalview/datamodel/SearchResultsI.java b/src/jalview/datamodel/SearchResultsI.java index 7946824..d682de1 100644 --- a/src/jalview/datamodel/SearchResultsI.java +++ b/src/jalview/datamodel/SearchResultsI.java @@ -51,6 +51,19 @@ public interface SearchResultsI */ void addResult(SequenceI seq, int[] positions); + + /** + * Adds the given start/end region to this search result. If sequence already + * has a search result and the range is adjacent to already highlighted + * positions, they will be merged + * + * @param sequence + * @param start + * @param end + * @return true if an existing range was updated with this one + */ + boolean appendResult(SequenceI sequence, int start, int end); + /** * adds all match results in the argument to this set * diff --git a/src/jalview/structure/StructureSelectionManager.java b/src/jalview/structure/StructureSelectionManager.java index 64c1547..9672290 100644 --- a/src/jalview/structure/StructureSelectionManager.java +++ b/src/jalview/structure/StructureSelectionManager.java @@ -1025,7 +1025,7 @@ public class StructureSelectionManager int indexpos = sm.getSeqPos(atom.getPdbResNum()); if (lastipos != indexpos || lastseq != sm.sequence) { - results.addResult(sm.sequence, indexpos, indexpos); + results.appendResult(sm.sequence, indexpos, indexpos); lastipos = indexpos; lastseq = sm.sequence; // construct highlighted sequence list diff --git a/test/jalview/datamodel/SearchResultsTest.java b/test/jalview/datamodel/SearchResultsTest.java index b1bb43c..d8c10ee 100644 --- a/test/jalview/datamodel/SearchResultsTest.java +++ b/test/jalview/datamodel/SearchResultsTest.java @@ -206,6 +206,32 @@ public class SearchResultsTest assertFalse(m.contains(null, 3, 3)); } + @Test(groups = { "Functional" }) + public void testMatchAdjacent() + { + SequenceI seq1 = new Sequence("", "abcdefghijklm"); + SequenceI seq2 = new Sequence("", "abcdefghijklm"); + SearchResultMatchI m = new SearchResults().new Match(seq1, 2, 5); + + assertTrue(m.adjacent(seq1, 2, 5)); + assertTrue(m.adjacent(seq1, 3, 5)); + assertTrue(m.adjacent(seq1, 2, 4)); + assertTrue(m.adjacent(seq1, 3, 3)); + + assertTrue(m.adjacent(seq1, 2, 6)); + assertTrue(m.adjacent(seq1, 1, 5)); + assertTrue(m.adjacent(seq1, 1, 8)); + assertFalse(m.adjacent(seq1, 0, 0)); + assertFalse(m.adjacent(seq1, 7, 8)); + assertTrue(m.adjacent(seq1, 6, 8)); + assertTrue(m.adjacent(seq1, 5, 8)); + assertTrue(m.adjacent(seq1, 0, 1)); + + + assertFalse(m.adjacent(seq2, 3, 3)); + assertFalse(m.adjacent(null, 3, 3)); + } + /** * test markColumns for creating column selections */ @@ -308,6 +334,23 @@ public class SearchResultsTest } /** + * Test to verify appending creates a minimal set of results + */ + @Test(groups = { "Functional" }) + public void testAppendResult() + { + SequenceI seq1 = new Sequence("", "abcdefghijklm"); + SearchResultsI sr = new SearchResults(); + sr.appendResult(seq1, 3, 5); + assertEquals(1, sr.getCount()); + sr.appendResult(seq1, 3, 6); + assertEquals(1, sr.getCount()); + sr.appendResult(seq1, 8, 8); + assertEquals(2, sr.getCount()); + sr.appendResult(seq1, 7, 7); + assertEquals(1, sr.getCount()); + } + /** * Test for method that checks if search results matches a sequence region */ @Test(groups = { "Functional" }) -- 1.7.10.2