2024-06-10 13:09:06 +00:00
|
|
|
package nu.marginalia.sequence;
|
|
|
|
|
2024-07-30 10:01:53 +00:00
|
|
|
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
2024-06-10 13:09:06 +00:00
|
|
|
import it.unimi.dsi.fastutil.ints.IntIterator;
|
2024-07-30 10:01:53 +00:00
|
|
|
import it.unimi.dsi.fastutil.ints.IntList;
|
2024-06-10 13:09:06 +00:00
|
|
|
|
|
|
|
public class SequenceOperations {
|
|
|
|
|
|
|
|
/** Return true if the sequences intersect, false otherwise.
|
|
|
|
* */
|
|
|
|
public static boolean intersectSequences(IntIterator... sequences) {
|
|
|
|
|
|
|
|
if (sequences.length <= 1)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// Initialize values and find the maximum value
|
|
|
|
int[] values = new int[sequences.length];
|
|
|
|
|
|
|
|
for (int i = 0; i < sequences.length; i++) {
|
|
|
|
if (sequences[i].hasNext())
|
|
|
|
values[i] = sequences[i].nextInt();
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Intersect the sequences by advancing all values smaller than the maximum seen so far
|
|
|
|
// until they are equal to the maximum value, or until the end of the sequence is reached
|
|
|
|
int max = Integer.MIN_VALUE;
|
|
|
|
int successes = 0;
|
|
|
|
for (int i = 0; successes < sequences.length; i = (i + 1) % sequences.length)
|
|
|
|
{
|
|
|
|
if (values[i] == max) {
|
|
|
|
successes++;
|
|
|
|
} else {
|
2024-07-30 10:01:53 +00:00
|
|
|
successes = 1;
|
2024-06-10 13:09:06 +00:00
|
|
|
|
|
|
|
// Discard values until we reach the maximum value seen so far,
|
|
|
|
// or until the end of the sequence is reached
|
|
|
|
while (values[i] < max) {
|
|
|
|
if (sequences[i].hasNext())
|
|
|
|
values[i] = sequences[i].nextInt();
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the maximum value, if necessary
|
|
|
|
max = Math.max(max, values[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-08-26 10:02:37 +00:00
|
|
|
/** Find any intersections between the given positions lists, and return the list of intersections.
|
|
|
|
* If any of the lists are empty, return an empty list.
|
|
|
|
* <p></p>
|
|
|
|
*/
|
2024-08-25 11:16:31 +00:00
|
|
|
public static IntList findIntersections(IntList... positions) {
|
2024-08-26 10:02:37 +00:00
|
|
|
return findIntersections(positions, new int[positions.length]);
|
2024-08-25 10:23:09 +00:00
|
|
|
}
|
2024-08-25 11:19:37 +00:00
|
|
|
|
2024-08-26 10:02:37 +00:00
|
|
|
/** Find any intersections between the given positions lists, and return the list of intersections.
|
|
|
|
* If any of the lists are empty, return an empty list.
|
|
|
|
* <p></p>
|
|
|
|
* A constant offset can be applied to each position list by providing an array of offsets.
|
|
|
|
*
|
|
|
|
* @param positions the positions lists to compare - each list must be sorted in ascending order
|
|
|
|
* and contain unique values.
|
|
|
|
* @param offsets constant offsets to apply to each position
|
|
|
|
* */
|
|
|
|
public static IntList findIntersections(IntList[] positions, int[] offsets) {
|
2024-07-30 10:01:53 +00:00
|
|
|
|
2024-08-25 11:16:31 +00:00
|
|
|
if (positions.length < 1)
|
2024-07-30 10:01:53 +00:00
|
|
|
return IntList.of();
|
|
|
|
|
2024-08-25 11:16:31 +00:00
|
|
|
int[] indexes = new int[positions.length];
|
2024-07-30 10:01:53 +00:00
|
|
|
// Initialize values and find the maximum value
|
2024-08-25 11:16:31 +00:00
|
|
|
int[] values = new int[positions.length];
|
2024-07-30 10:01:53 +00:00
|
|
|
|
2024-08-25 11:16:31 +00:00
|
|
|
for (int i = 0; i < positions.length; i++) {
|
2024-08-25 11:17:38 +00:00
|
|
|
if (indexes[i] < positions[i].size())
|
2024-08-25 11:19:37 +00:00
|
|
|
values[i] = positions[i].getInt(indexes[i]++) + offsets[i];
|
2024-07-30 10:01:53 +00:00
|
|
|
else
|
|
|
|
return IntList.of();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Intersect the sequences by advancing all values smaller than the maximum seen so far
|
|
|
|
// until they are equal to the maximum value, or until the end of the sequence is reached
|
|
|
|
int max = Integer.MIN_VALUE;
|
|
|
|
int successes = 0;
|
|
|
|
|
|
|
|
IntList ret = new IntArrayList();
|
|
|
|
|
|
|
|
outer:
|
2024-08-25 11:16:31 +00:00
|
|
|
for (int i = 0;; i = (i + 1) % positions.length)
|
2024-07-30 10:01:53 +00:00
|
|
|
{
|
2024-08-25 11:16:31 +00:00
|
|
|
if (successes == positions.length) {
|
2024-07-30 10:01:53 +00:00
|
|
|
ret.add(max);
|
|
|
|
successes = 1;
|
|
|
|
|
2024-08-25 11:19:37 +00:00
|
|
|
if (indexes[i] < positions[i].size()) {
|
|
|
|
values[i] = positions[i].getInt(indexes[i]++) + offsets[i];
|
2024-08-25 12:54:17 +00:00
|
|
|
|
|
|
|
// Update the maximum value, if necessary
|
|
|
|
max = Math.max(max, values[i]);
|
2024-07-30 10:01:53 +00:00
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else if (values[i] == max) {
|
|
|
|
successes++;
|
|
|
|
} else {
|
|
|
|
successes = 1;
|
|
|
|
|
|
|
|
// Discard values until we reach the maximum value seen so far,
|
|
|
|
// or until the end of the sequence is reached
|
|
|
|
while (values[i] < max) {
|
2024-08-25 11:19:37 +00:00
|
|
|
if (indexes[i] < positions[i].size()) {
|
|
|
|
values[i] = positions[i].getInt(indexes[i]++) + offsets[i];
|
2024-07-30 10:01:53 +00:00
|
|
|
} else {
|
|
|
|
break outer;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the maximum value, if necessary
|
|
|
|
max = Math.max(max, values[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2024-08-03 10:04:23 +00:00
|
|
|
|
2024-08-26 10:02:37 +00:00
|
|
|
/** Given each set of positions, one from each list, find the set with the smallest distance between them
|
|
|
|
* and return that distance. If any of the lists are empty, return 0.
|
|
|
|
* */
|
2024-08-25 11:28:06 +00:00
|
|
|
public static int minDistance(IntList[] positions) {
|
|
|
|
return minDistance(positions, new int[positions.length]);
|
2024-08-25 10:23:09 +00:00
|
|
|
}
|
|
|
|
|
2024-08-26 10:02:37 +00:00
|
|
|
/** Given each set of positions, one from each list, find the set with the smallest distance between them
|
2024-09-29 15:21:17 +00:00
|
|
|
* and return that distance. If any of the lists are empty, return Integer.MAX_VALUE.
|
2024-08-26 10:02:37 +00:00
|
|
|
*
|
|
|
|
* @param positions the positions lists to compare - each list must be sorted in ascending order
|
|
|
|
* @param offsets the offsets to apply to each position
|
|
|
|
*/
|
2024-08-25 11:28:06 +00:00
|
|
|
public static int minDistance(IntList[] positions, int[] offsets) {
|
|
|
|
if (positions.length <= 1)
|
2024-08-03 10:04:23 +00:00
|
|
|
return 0;
|
|
|
|
|
2024-08-25 11:28:06 +00:00
|
|
|
int[] values = new int[positions.length];
|
|
|
|
int[] indexes = new int[positions.length];
|
2024-08-26 10:02:37 +00:00
|
|
|
|
2024-08-25 11:28:06 +00:00
|
|
|
for (int i = 0; i < positions.length; i++) {
|
2024-09-29 15:21:17 +00:00
|
|
|
// if any of the lists are empty, return MAX_VALUE
|
|
|
|
|
|
|
|
if (positions[i].isEmpty()) {
|
|
|
|
return Integer.MAX_VALUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
values[i] = positions[i].getInt(indexes[i]++) + offsets[i];
|
2024-08-03 10:04:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int minDist = Integer.MAX_VALUE;
|
|
|
|
int maxVal = Integer.MIN_VALUE;
|
|
|
|
|
2024-08-26 10:02:37 +00:00
|
|
|
int maxI = 0;
|
2024-08-03 10:04:23 +00:00
|
|
|
|
2024-08-26 10:02:37 +00:00
|
|
|
// Find the maximum value in values[] and its index in positions[]
|
|
|
|
for (int i = 0; i < positions.length; i++) {
|
|
|
|
if (values[i] > maxVal) {
|
|
|
|
maxVal = values[i];
|
|
|
|
maxI = i;
|
|
|
|
}
|
|
|
|
}
|
2024-08-03 10:04:23 +00:00
|
|
|
|
2024-08-25 12:54:17 +00:00
|
|
|
for (;;) {
|
2024-08-26 10:02:37 +00:00
|
|
|
// For all the other indexes except maxI, update values[] with the largest value smaller than maxVal
|
|
|
|
for (int idx = 0; idx < positions.length - 1; idx++) {
|
|
|
|
int i = (maxI + idx) % positions.length;
|
2024-08-25 12:54:17 +00:00
|
|
|
|
2024-09-29 15:21:17 +00:00
|
|
|
// Update values[i] to the largest value smaller than maxVal
|
2024-08-26 10:02:37 +00:00
|
|
|
|
|
|
|
int len = positions[i].size();
|
|
|
|
int offset = offsets[i];
|
|
|
|
int prevValue = values[i];
|
|
|
|
int value = prevValue;
|
|
|
|
|
2024-08-26 10:45:11 +00:00
|
|
|
while (indexes[i] < len) {
|
2024-08-26 10:02:37 +00:00
|
|
|
prevValue = value;
|
|
|
|
value = positions[i].getInt(indexes[i]++) + offset;
|
2024-08-26 10:45:11 +00:00
|
|
|
if (value >= maxVal) {
|
|
|
|
indexes[i]--; // correct for overshooting the largest value smaller than maxVal
|
|
|
|
break;
|
|
|
|
}
|
2024-08-03 10:04:23 +00:00
|
|
|
}
|
|
|
|
|
2024-08-26 10:02:37 +00:00
|
|
|
values[i] = prevValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate minVal and update minDist
|
|
|
|
int minVal = Integer.MAX_VALUE;
|
|
|
|
for (int val : values) {
|
|
|
|
minVal = Math.min(minVal, val);
|
|
|
|
}
|
|
|
|
minDist = Math.min(minDist, maxVal - minVal);
|
|
|
|
|
|
|
|
|
|
|
|
// Find the next maximum value and its index. We look for the largest value smaller than the current maxVal,
|
|
|
|
// which is the next target value
|
|
|
|
maxVal = Integer.MAX_VALUE;
|
|
|
|
|
|
|
|
for (int i = 0; i < positions.length; i++) {
|
|
|
|
int index = indexes[i];
|
|
|
|
if (index >= positions[i].size()) { // no more values in this list, skip
|
|
|
|
continue;
|
2024-08-03 10:04:23 +00:00
|
|
|
}
|
2024-08-25 12:54:17 +00:00
|
|
|
|
2024-08-26 10:02:37 +00:00
|
|
|
int value = positions[i].getInt(index) + offsets[i];
|
|
|
|
if (value < maxVal) {
|
|
|
|
maxVal = value;
|
|
|
|
maxI = i;
|
2024-08-03 10:04:23 +00:00
|
|
|
}
|
2024-08-26 10:02:37 +00:00
|
|
|
}
|
2024-08-03 10:04:23 +00:00
|
|
|
|
2024-08-26 10:02:37 +00:00
|
|
|
if (maxVal != Integer.MAX_VALUE) {
|
|
|
|
indexes[maxI]++;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return minDist;
|
2024-08-03 10:04:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-06-10 13:09:06 +00:00
|
|
|
}
|