List<int[]> shiftFrom, int toRatio)
{
// TODO: javadoc; tests
- int[] fromCount = countPos(shiftTo, pos);
+ int[] fromCount = countPositions(shiftTo, pos);
if (fromCount == null)
{
return null;
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 };
}
else
{
+ /*
+ * reverse interval
+ */
if (pos >= intv[1] && pos <= intv[0])
{
return new int[] { count + intv[0] - pos + 1, -1 };
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);
}
{
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 });
+ }
+ }
+ }
}