JAL-3761 locateInFrom2 work in progress commit
[jalview.git] / src / jalview / util / MapList.java
index e095106..2e673e0 100644 (file)
@@ -562,7 +562,7 @@ public class MapList
           List<int[]> shiftFrom, int toRatio)
   {
     // TODO: javadoc; tests
-    int[] fromCount = countPos(shiftTo, pos);
+    int[] fromCount = countPositions(shiftTo, pos);
     if (fromCount == null)
     {
       return null;
@@ -572,27 +572,41 @@ public class MapList
     int[] toPos = traverseToPosition(shiftFrom, toCount);
     if (toPos == null)
     {
-      return null; // throw new Error("Bad Mapping!");
+      return null;
     }
-    // System.out.println(fromCount[0]+" "+fromCount[1]+" "+toCount);
     return new int[] { toPos[0], fromRemainder, toPos[1] };
   }
 
   /**
-   * count how many positions pos is along the series of intervals.
+   * Counts how many positions pos is along the series of intervals. Returns an
+   * array of two values:
+   * <ul>
+   * <li>the number of positions traversed (inclusive) to reach {@code pos}</li>
+   * <li>+1 if the last interval traversed is forward, -1 if in a negative
+   * direction</li>
+   * </ul>
+   * Returns null if {@code pos} does not lie in any of the given intervals.
    * 
-   * @param shiftTo
+   * @param intervals
+   *          a list of start-end intervals
    * @param pos
-   * @return number of positions or null if pos is not within intervals
+   *          a position that may lie in one (or more) of the intervals
+   * @return
    */
-  protected static int[] countPos(List<int[]> shiftTo, int pos)
+  protected static int[] countPositions(List<int[]> intervals, int pos)
   {
-    int count = 0, intv[], iv = 0, ivSize = shiftTo.size();
+    int count = 0;
+    int iv = 0;
+    int ivSize = intervals.size();
+
     while (iv < ivSize)
     {
-      intv = shiftTo.get(iv++);
+      int[] intv = intervals.get(iv++);
       if (intv[0] <= intv[1])
       {
+        /*
+         * forwards interval
+         */
         if (pos >= intv[0] && pos <= intv[1])
         {
           return new int[] { count + pos - intv[0] + 1, +1 };
@@ -604,6 +618,9 @@ public class MapList
       }
       else
       {
+        /*
+         * reverse interval
+         */
         if (pos >= intv[1] && pos <= intv[0])
         {
           return new int[] { count + intv[0] - pos + 1, -1 };
@@ -690,7 +707,7 @@ public class MapList
     int fromStart[] = shiftTo(start);
     // needs to be inclusive of end of symbol position
     int fromEnd[] = shiftTo(end);
-
+    System.out.println("locateInFrom");
     return getIntervals(fromShifts, fromStart, fromEnd, fromRatio);
   }
 
@@ -1225,4 +1242,231 @@ public class MapList
   {
     return fromShifts.size() == 1 && toShifts.size() == 1;
   }
+
+  /**
+   * Returns the [start, end, start, end, ...] ranges in the 'from' range that
+   * map to the given start-end in the 'to' range. Returns null if either
+   * {@code start} or {@code end} is not a mapped 'to' range position.
+   * 
+   * @param start
+   * @param end
+   * @return
+   */
+  public int[] locateInFrom2(int start, int end)
+  {
+    List<int[]> ranges = mapBetween(start, end, toShifts, fromShifts,
+            toRatio, fromRatio);
+
+    // TODO: or just return the List and adjust calling code to match
+    return ranges.isEmpty() ? null : MappingUtils.rangeListToArray(ranges);
+  }
+
+  /**
+   * Returns the [start, end, start, end, ...] ranges in the 'to' range that map
+   * to the given start-end in the 'from' range. Returns null if either
+   * {@code start} or {@code end} is not a mapped 'from' range position.
+   * 
+   * @param start
+   * @param end
+   * @return
+   */
+  public int[] locateInTo2(int start, int end)
+  {
+    List<int[]> ranges = mapBetween(start, end, fromShifts, toShifts,
+            fromRatio, toRatio);
+
+    return ranges.isEmpty() ? null : MappingUtils.rangeListToArray(ranges);
+  }
+
+  /**
+   * A helper method for navigating the mapping. Returns a (possibly empty) list
+   * of [start-end] positions in {@code ranges2} that map to positions in
+   * {@code ranges1} between {@code start} and {@code end}.
+   * 
+   * @param start
+   * @param end
+   * @param ranges1
+   * @param ranges2
+   * @param wordLength1
+   * @param wordLength2
+   * @return
+   */
+  final static List<int[]> mapBetween(int start, int end,
+          List<int[]> ranges1, List<int[]> ranges2, int wordLength1,
+          int wordLength2)
+  {
+    /*
+     * first traverse ranges1 and record count of mapped positions 
+     * to any that overlap start-end
+     */
+    List<int[]> overlaps = findOverlapPositions(ranges1, start, end);
+    if (overlaps.isEmpty())
+    {
+      return overlaps;
+    }
+
+    /*
+     * convert positions to equivalent 'word' positions in ranges
+     */
+    mapWords(overlaps, wordLength1, wordLength2);
+
+    /*
+     * walk ranges2 and record the values found at 
+     * the offsets in 'overlaps'
+     */
+    List<int[]> mapped = new ArrayList<>();
+    final int s1 = overlaps.size();
+    final int s2 = ranges2.size();
+    int rangeIndex = 0;
+    int rangeOffset = 0;
+    int mappedCount = 0;
+    
+    for (int i = 0 ; i < s1 ; i++)
+    {
+      /*
+       * for each range in overlaps, walk ranges2 and record the values 
+       * at the offsets, advancing rangeIndex / Offset
+       */
+      int [] mappedRange = ranges2.get(rangeIndex);
+      int [] overlap = overlaps.get(s1);
+      while (mappedCount < overlap[1])
+      {
+        
+      }
+    }
+    
+    return mapped;
+  }
+
+  /**
+   * Converts the start-end positions (counted from zero) in the {@code ranges}
+   * list from one word length to another. Start-end positions are expanded if
+   * necessary to cover a whole word of length {@code wordLength1}. Positions
+   * are then divided by {@code wordLength1} and multiplied by
+   * {@code wordLength2} to give equivalent mapped words.
+   * <p>
+   * Put simply, this converts peptide residue positions to the corresponding
+   * codon ranges, and codons - including partial codons - to the corresponding
+   * peptide positions; for example
+   * 
+   * <pre>
+   * [1, 10] with word lengths 3:1 converts (as if bases [0-11]) to [1, 4]
+   * </pre>
+   * 
+   * @param ranges
+   * @param wordLength1
+   * @param wordLength2
+   * @return
+   */
+  final static void mapWords(List<int[]> ranges, int wordLength1,
+          int wordLength2)
+  {
+    if (wordLength1 == 1 && wordLength2 == 1)
+    {
+      return; // nothing to do here
+    }
+    int s = ranges.size();
+    for (int i = 0; i < s; i++)
+    {
+      int[] range = ranges.get(i);
+
+      /*
+       * expand range start to the start of a word, 
+       * and convert to wordLength2
+       */
+      range[0] -= range[0] % wordLength1;
+      range[0] = range[0] / wordLength1 * wordLength2;
+
+      /*
+       * similar calculation for range end, adding 
+       * (wordLength2 - 1) for end of mapped word
+       */
+      range[1] -= range[1] % wordLength1;
+      range[1] = range[1] / wordLength1 * wordLength2;
+      range[1] += wordLength2 - 1;
+    }
+  }
+
+  /**
+   * Helper method that returns a (possibly empty) list of offsets in
+   * {@code ranges} to subranges that overlap {@code start-end}. The list
+   * returned holds counts of the number of positions traversed (inclusive) to
+   * reach the overlapping positions, not the overlapping values. Returns null
+   * if there are no overlaps.
+   * 
+   * @param ranges
+   * @param start
+   * @param end
+   * @return
+   */
+  final static List<int[]> findOverlapPositions(List<int[]> ranges,
+          int start, int end)
+  {
+    List<int[]> positions = new ArrayList<>();
+    int pos = 0;
+    int s = ranges.size();
+    for (int i = 0; i < s; i++)
+    {
+      int[] range = ranges.get(i);
+      addOverlap(positions, pos, range, start, end);
+      pos += 1 + Math.abs(range[1] - range[0]);
+    }
+    return positions;
+  }
+
+  /**
+   * A helper method that checks whether {@code range} overlaps
+   * {@code start-end}, and if so adds the positional offset of the overlap to
+   * {@code positions}.
+   * 
+   * @param positions
+   *          a list of map offsets to add to
+   * @param pos
+   *          the number of mapped positions already visited
+   * @param range
+   *          a from-to range (may be forward or reverse)
+   * @param start
+   *          position to test for overlap in range
+   * @param end
+   *          position to test for overlap in range
+   * @return
+   */
+  final static void addOverlap(List<int[]> positions, int pos, int[] range,
+          int start, int end)
+  {
+    if (range[1] >= range[0])
+    {
+      /*
+       * forward direction range
+       */
+      if (start <= range[1] && end >= range[0])
+      {
+        /*
+         * overlap
+         */
+        int overlapStart = Math.max(start, range[0]);
+        int overlapEnd = Math.min(end, range[1]);
+        positions
+                .add(new int[]
+                { 1 + overlapStart - range[0], 1 + overlapEnd - range[0] });
+      }
+    }
+    else
+    {
+      /*
+       * reverse direction range
+       */
+      if (start <= range[0] && end >= range[1])
+      {
+        /*
+         * overlap
+         */
+        int overlapStart = Math.max(start, range[1]);
+        int overlapEnd = Math.min(end, range[0]);
+        positions
+                .add(new int[]
+                { 1 + range[0] - overlapStart, 1 + range[0] - overlapEnd });
+      }
+    }
+  }
 }