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 Evento
s 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 Evento
s 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