The Java code I’ve built is as follows:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author Victor Williams Stafusa da Silva
*/
public class Main {
public static void main(String[] args) {
System.out.println(Intervalo.reduzir(new Intervalo(1, 3), new Intervalo(2, 6), new Intervalo(7, 10)));
System.out.println(Intervalo.reduzir(new Intervalo(1, 3), new Intervalo(2, 8), new Intervalo(7, 10)));
System.out.println(Intervalo.reduzir(new Intervalo(1, 3), new Intervalo(4, 7), new Intervalo(8, 10)));
System.out.println(Intervalo.reduzir(new Intervalo(1, 3), new Intervalo(3, 10)));
}
}
final class Intervalo {
private final double a;
private final double b;
public Intervalo(double a, double b) {
if (b < a) throw new IllegalArgumentException();
this.a = a;
this.b = b;
}
public double getA() {
return a;
}
public double getB() {
return b;
}
@Override
public String toString() {
return "[" + a + "<->" + b + "]";
}
public Evento getEventoA() {
return new Evento(true, a);
}
public Evento getEventoB() {
return new Evento(false, b);
}
@SafeVarargs
public static List<Intervalo> reduzir(Intervalo... lista) {
return reduzir(List.of(lista));
}
public static List<Intervalo> reduzir(List<Intervalo> lista) {
List<Evento> eventos = new ArrayList<>(lista.size() * 2);
for (Intervalo i : lista) {
eventos.add(i.getEventoA());
eventos.add(i.getEventoB());
}
Collections.sort(eventos);
List<Intervalo> saida = new ArrayList<>(lista.size());
int contagem = 0;
double inicio = Double.NEGATIVE_INFINITY;
for (Evento e : eventos) {
if (e.isInicioIntervalo()) {
if (contagem == 0) inicio = e.getPonto();
contagem++;
} else {
contagem--;
if (contagem == 0) saida.add(new Intervalo(inicio, e.getPonto()));
}
}
return saida;
}
}
final class Evento implements Comparable<Evento> {
private final boolean inicioIntervalo;
private final double ponto;
public Evento(boolean inicioIntervalo, double ponto) {
this.inicioIntervalo = inicioIntervalo;
this.ponto = ponto;
}
public boolean isInicioIntervalo() {
return inicioIntervalo;
}
public double getPonto() {
return ponto;
}
@Override
public int compareTo(Evento outro) {
return this.ponto < outro.ponto ? -1
: this.ponto > outro.ponto ? 1
: this.inicioIntervalo == outro.inicioIntervalo ? 0
: this.inicioIntervalo ? -1
: 1;
}
}
Let’s see what is the complexity of the static method reduzir:
List<Evento> eventos = new ArrayList<>(lista.size() * 2);
That is O(n). Note that I gave an exact size to the list (2n) enough that it never needs to be resized. The method size() is O(1).
for (Intervalo i : lista) {
eventos.add(i.getEventoA());
eventos.add(i.getEventoB());
}
The getters are O(1). The method add is O(1) if the list does not need to be resized (and it never will). The number of Eventos created in the list is 2n.
Collections.sort(eventos);
Ordination is O(Tn log n), where t is the complexity of the method compareTo class Evento. How this method has complexity O(1), then it boils down to O(n log n). This is the point that makes the algorithm have such complexity.
List<Intervalo> saida = new ArrayList<>(lista.size());
Again is O(n) and never need to be resized, because the worst case is precisely when none of the intervals overlaps with the others.
int contagem = 0;
double inicio = Double.NEGATIVE_INFINITY;
That’s right O(1).
for (Evento e : eventos) {
if (e.isInicioIntervalo()) {
if (contagem == 0) inicio = e.getPonto();
contagem++;
} else {
contagem--;
if (contagem == 0) saida.add(new Intervalo(inicio, e.getPonto()));
}
}
Whereas the number of events is 2n, so this one for is also O(n).
The methods isInicioIntervalo, getPonto, add (whereas saida will never need to be resized) and the Intervalo are also all O(1).
return saida;
That is O(1).
Basically the algorithm reduces itself to undoing the intervals and marking them as an ordered sequence of points, where each point corresponding to the beginning of an interval sum 1 in contagem and each point equivalent to the end subtracts 1 of contagem.
When contagem comes out of zero, it’s because we’re in some gap that opened.
While contagem is greater than zero, there are open intervals colliding with each other. And the number of contagem tells exactly how many.
When contagem is reduced to zero, means that all the open intervals have closed and we can then add a large interval corresponding to all of this, from the point where this variable left from zero to the point where it returned to it.
The complexity of the algorithm is dominated by ordering, which is the most complex operation (O(n log n)).
Since the elements in the list eventos are properly ordered, this ensures that the elements of saida will also be correct and properly ordered.
It is also worth paying attention to the method compareTo:
public int compareTo(Evento outro) {
return this.ponto < outro.ponto ? -1
: this.ponto > outro.ponto ? 1
: this.inicioIntervalo == outro.inicioIntervalo ? 0
: this.inicioIntervalo ? -1
: 1;
}
He’s like that to make sure Eventos with smaller numbers will always precede those with greater numbers. And in the event of a tie, that an interval opening event always takes precedence over a closing one. This serves to ensure that intervals that are only touching (e.g., [1-3] and [3-10]) are considered as a collision.
If it is desired that events that are only touching are not considered as a collision, simply change the ? -1 : 1; of the end by ? 1 : -1;.
Considering these entry intervals:
System.out.println(Intervalo.reduzir(new Intervalo(1, 3), new Intervalo(2, 6), new Intervalo(7, 10)));
System.out.println(Intervalo.reduzir(new Intervalo(1, 3), new Intervalo(2, 8), new Intervalo(7, 10)));
System.out.println(Intervalo.reduzir(new Intervalo(1, 3), new Intervalo(4, 7), new Intervalo(8, 10)));
System.out.println(Intervalo.reduzir(new Intervalo(1, 3), new Intervalo(3, 10)));
Here is the output produced:
[[1.0<->6.0], [7.0<->10.0]]
[[1.0<->10.0]]
[[1.0<->3.0], [4.0<->7.0], [8.0<->10.0]]
[[1.0<->10.0]]
See here working on IDEONE.
Look Jeff, I hope you’ll answer this yourself...
– Jéf Bueno
@Victorstafusa, the example was in Java, but the problem is language independent (although I need to solve this imperative, extra points for those who do in Lisp functional way proving the complexity). Thanks for noticing that
– Jefferson Quesado
@LINQ, actually I’m already coming up with a sort of binary search tree to make this merge function even =P Actually, since I don’t master balancing trees, it’s still going to be a kind of "medium case", but then it would be changing the operations of "merge" of nodes for some rotation removal of AVL or red-black
– Jefferson Quesado
@Jeffersonquesado I gave the answer in Java. Translate this to Lisp is for you (although it will probably be much simpler than in Java).
– Victor Stafusa
Ah, @Jeffersonquesado I just edited my answer because I had a special case that went wrong (when the intervals only leaned, as for example [1, 3] and [3, 10]). I edited her to fix it.
– Victor Stafusa
Yes, I realized that when I understood the
Evento, but the solution was trivial to not bother you with it (at most make an edit)– Jefferson Quesado