Jaki jest najbardziej efektywny sposób przetestowania dwóch zakresów liczb całkowitych pod kątem nakładania się?


252

Biorąc pod uwagę dwa całkowite zakresy liczb całkowitych [x1: x2] i [y1: y2], gdzie x1 ≤ x2 i y1 ≤ y2, jaki jest najskuteczniejszy sposób sprawdzenia, czy oba zakresy się pokrywają?

Prosta implementacja wygląda następująco:

bool testOverlap(int x1, int x2, int y1, int y2) {
  return (x1 >= y1 && x1 <= y2) ||
         (x2 >= y1 && x2 <= y2) ||
         (y1 >= x1 && y1 <= x2) ||
         (y2 >= x1 && y2 <= x2);
}

Ale oczekuję, że istnieją bardziej wydajne sposoby na obliczenie tego.

Która metoda byłaby najbardziej wydajna pod względem najmniejszej liczby operacji.


Może być ciekawie powiązany z niektórymi - stackoverflow.com/q/17138760/104380
— vsync

Odpowiedzi:


454

Co to znaczy, że zakresy się pokrywają? Oznacza to, że istnieje pewna liczba C, która znajduje się w obu zakresach, tj

x1 <= C <= x2

i

y1 <= C <= y2

Teraz, jeśli możemy założyć, że zakresy są dobrze uformowane (tak, że x1 <= x2 i y1 <= y2), wystarczy przetestować

x1 <= y2 && y1 <= x2

1
Uważam, że tak powinno być x1 <= y2 && y1 >= x2, nie?
— David Beck

8
@DavidBeck: nie, jeśli y1> x2, to zakresy na pewno się nie pokrywają (np. Rozważ [1: 2] i [3: 4]: y1 = 3 i x2 = 2, więc y1> x2, ale nie ma nakładania się) .
— Simon Nickerson

8
to byłaby lepsza odpowiedź, gdybyś wyjaśnił nieco bardziej rozumowanie
— shoosh

2
@Vineet Deoraj - Jak myślisz, dlaczego to nie działa? x1 = 1, y1 = 1, x2 = 1, y2 = 1, więc x1 <= y2 && y1 <= x2 jest prawdą, więc zachodzi na siebie nakładanie się.
— dcp,

2
Wyjaśnienie jest tutaj: stackoverflow.com/questions/325933/…
— Alex

138

Biorąc pod uwagę dwa zakresy [x1, x2], [y1, y2]

def is_overlapping(x1,x2,y1,y2):
    return max(x1,y1) <= min(x2,y2)

4
@ uyuyuy99 - tylko nie tak wydajne, ponieważ gdy ta kontrola jest wykonywana wiele razy na sekundę, wywoływanie funkcji jest czymś, czego chciałbyś uniknąć i zrobić tyle samo matematyki, trzymaj ją na poziomie podstawowym
— vsync

7
@vsync Nowoczesne przeglądarki wbudują i zoptymalizują funkcje takie jak Math.max, nie powinno to mieć zauważalnego wpływu na wydajność.
— Ashton Six

1
@AshtonWar - ciekawe. czy masz artykuł wyjaśniający, co jest wstawiane, a co nie?
— vsync

@vsync Nie, ale jestem pewien, że możesz sam znaleźć informacje
— Ashton Six,

6
Ponadto należy pamiętać, że min(x2,y2) - max(x1,y1)zapewnia to nakładanie się w razie potrzeby.
— user1556435,

59

Może to łatwo wypaczyć normalny ludzki mózg, więc znalazłem wizualne podejście, które jest łatwiejsze do zrozumienia:

nakładać się na szaleństwo

le Wyjaśnienie

Jeśli dwa zakresy są „zbyt grube”, aby zmieścić się w szczelinie, która jest dokładnie sumą szerokości obu, wówczas się pokrywają.

Dla zakresów [a1, a2]i [b1, b2]to byłoby:

/**
 * we are testing for:
 *     max point - min point < w1 + w2    
 **/
if max(a2, b2) - min(a1, b1) < (a2 - a1) + (b2 - b1) {
  // too fat -- they overlap!
}

3
Na zdjęciach jest więcej przypadków niż pokazano. Np. Co jeśli w2 zaczyna się przed w1, a kończy po w1?
— WilliamKF,

7
@WilliamKF logika jest prawdziwa
— FloatingRock

2
Zgadzam się, ale myślę, że może to pomóc w uzyskaniu trzeciego obrazu.
— WilliamKF,

3
@WilliamKF, to potrzebujesz dużo więcej zdjęć, istnieje 16 różnych kombinacji, w których można umieścić 2 zakresy ...
— Peter

3
Zachowaj ostrożność, jeśli używasz tej metody, ponieważ suma a2 - a1 + b2 - b1może się przepełnić. Aby to naprawić, przestaw formułę na max(a2, b2) - a2 - b2 < min(a1, b1) - a1 - b1, co upraszcza max(a1, b1) < min(a2, b2), oszczędzając trochę arytmetyki i unikając ewentualnych przelewów (oto odpowiedź AX-Labs poniżej). W szczególnym przypadku, w którym wiesz b2-b1=a2-a1, kolejną przydatną zmianą formuły FloatingRock jest max(a2, b2) - min(a1, b1) - (b2 - b1) < a2-a1, która staje się abs(b1-a1) < a2 - a1.
— Paolo Bonzini

44

Świetna odpowiedź od Simona , ale dla mnie łatwiej było pomyśleć o odwrotnej sprawie.

Kiedy 2 zakresy się nie pokrywają? Nie nakładają się, gdy jeden z nich zaczyna się po zakończeniu drugiego:

dont_overlap = x2 < y1 || x1 > y2

Teraz łatwo wyrazić, kiedy się pokrywają:

overlap = !dont_overlap = !(x2 < y1 || x1 > y2) = (x2 >= y1 && x1 <= y2)

1
Dla mnie łatwiejszym do zrozumienia wyrażeniem jest: x2 <y1 || y2 <x1 // gdzie używam „mniej niż” zamiast „większy niż”.
— Park JongBum

26

Wydaje się, że odjęcie minimum końca przedziałów od maksimum początku jest wystarczające. Jeśli wynik jest mniejszy lub równy zeru, nakładamy się. To dobrze to wizualizuje:

wprowadź opis zdjęcia tutaj


2
Dotyczy to wszystkich przypadków
— użytkownik3290180,

10

Przypuszczam, że pytanie dotyczyło najszybszego, a nie najkrótszego kodu. Najszybsza wersja musi unikać gałęzi, więc możemy napisać coś takiego:

dla prostego przypadku:

static inline bool check_ov1(int x1, int x2, int y1, int y2){
    // insetead of x1 < y2 && y1 < x2
    return (bool)(((unsigned int)((y1-x2)&(x1-y2))) >> (sizeof(int)*8-1));
};

lub w tym przypadku:

static inline bool check_ov2(int x1, int x2, int y1, int y2){
    // insetead of x1 <= y2 && y1 <= x2
    return (bool)((((unsigned int)((x2-y1)|(y2-x1))) >> (sizeof(int)*8-1))^1);
};

7
Uwierz w swój kompilator. W wyrażeniu x1 <= y2 && y1 <= x2 nie ma też żadnych rozgałęzień , przy założeniu, że kompilator i architektura procesora są odpowiednio kompetentne (nawet w 2010 r.). W rzeczywistości na x86 wygenerowany kod jest zasadniczo identyczny dla prostego wyrażenia vs. kod w tej odpowiedzi.
— Søren Løvborg


4

Jeśli miałeś do czynienia z dwoma zakresami [x1:x2]i [y1:y2]naturalnymi / antynaturalnymi zakresami zamówień jednocześnie:

  • naturalny porządek: x1 <= x2 && y1 <= y2lub
  • porządek antynaturalny: x1 >= x2 && y1 >= y2

możesz użyć tego do sprawdzenia:

nakładają się na siebie <=> (y2 - x1) * (x2 - y1) >= 0

przy zaangażowaniu tylko czterech operacji:

  • dwa odejmowania
  • jedno pomnożenie
  • jedno porównanie

1

Jeśli ktoś szuka jednej linijki, która oblicza faktyczne nakładanie się:

int overlap = ( x2 > y1 || y2 < x1 ) ? 0 : (y2 >= y1 && x2 <= y1 ? y1 : y2) - ( x2 <= x1 && y2 >= x1 ? x1 : x2) + 1; //max 11 operations

Jeśli chcesz kilka operacji mniej, ale kilka innych zmiennych:

bool b1 = x2 <= y1;
bool b2 = y2 >= x1;
int overlap = ( !b1 || !b2 ) ? 0 : (y2 >= y1 && b1 ? y1 : y2) - ( x2 <= x1 && b2 ? x1 : x2) + 1; // max 9 operations

1

Pomyśl odwrotnie : jak sprawić, by 2 zakresy się nie nakładały ? Biorąc pod uwagę [x1, x2], [y1, y2]powinien być na zewnątrz [x1, x2] , tj. y1 < y2 < x1 or x2 < y1 < y2Co jest równoważne y2 < x1 or x2 < y1.

Dlatego warunek, aby 2 zakresy zachodziły na siebie:, not(y2 < x1 or x2 < y1)co jest równoważne y2 >= x1 and x2 >= y1(to samo z zaakceptowaną odpowiedzią Simona).


Wygląda tak samo, jak odpowiedział @damluar (2 marca 16 o 17:36)
— Nakilon

0

Masz już najbardziej wydajną reprezentację - to absolutne minimum, które należy sprawdzić, chyba że wiesz na pewno, że x1 <x2 itp., A następnie skorzystaj z rozwiązań dostarczonych przez innych.

Powinieneś prawdopodobnie zauważyć, że niektóre kompilatory faktycznie zoptymalizują to dla Ciebie - zwracając, gdy tylko jedno z tych 4 wyrażeń zwróci wartość true. Jeśli jedna zwróci wartość true, wynik będzie taki sam - a pozostałe kontrole można po prostu pominąć.


2
Wszystkie kompilatory będą. Wszystkie (według mojej wiedzy) obecnie używane języki ze składnią w stylu C (C, C ++, C #, Java itp.) Wykorzystują zwarte operatory logiczne i jest to część różnych standardów rządzących tymi językami. Jeśli wynik lewej wartości jest wystarczający do ustalenia wyniku operacji, wartość po prawej stronie nie jest obliczana.
— Jonathan Grynspan

1
Mark H - kompilator pominie drugą klauzulę, jeśli może: więc jeśli masz funkcję, która mówi: foo (int c) {int i = 0; if (c <3 || ++ i == argc) printf („Inside \ n”); printf („i to% d \ n”, i); Foo (2) wypisze: wewnątrz i ma wartość 0, a Foo (4) wypisze: i ma wartość 1 (testowane na gcc 4.4.3, ale polegałem również na tym zachowaniu w przypadku brzydkiego kodu w icc)
— J Teller

0

Moja sprawa jest inna. Chcę sprawdzić, czy dwa zakresy czasu się pokrywają. czas nie powinien nakładać się na siebie. oto implementacja Go.

    func CheckRange(as, ae, bs, be int) bool {
    return (as >= be) != (ae > bs)
    }

Przypadki testowe

if CheckRange(2, 8, 2, 4) != true {
        t.Error("Expected 2,8,2,4 to equal TRUE")
    }

    if CheckRange(2, 8, 2, 4) != true {
        t.Error("Expected 2,8,2,4 to equal TRUE")
    }

    if CheckRange(2, 8, 6, 9) != true {
        t.Error("Expected 2,8,6,9 to equal TRUE")
    }

    if CheckRange(2, 8, 8, 9) != false {
        t.Error("Expected 2,8,8,9 to equal FALSE")
    }

    if CheckRange(2, 8, 4, 6) != true {
        t.Error("Expected 2,8,4,6 to equal TRUE")
    }

    if CheckRange(2, 8, 1, 9) != true {
        t.Error("Expected 2,8,1,9 to equal TRUE")
    }

    if CheckRange(4, 8, 1, 3) != false {
        t.Error("Expected 4,8,1,3 to equal FALSE")
    }

    if CheckRange(4, 8, 1, 4) != false {
        t.Error("Expected 4,8,1,4 to equal FALSE")
    }

    if CheckRange(2, 5, 6, 9) != false {
        t.Error("Expected 2,5,6,9 to equal FALSE")
    }

    if CheckRange(2, 5, 5, 9) != false {
        t.Error("Expected 2,5,5,9 to equal FALSE")
    }

widać porównanie XOR w porównaniu granic


-10

Oto moja wersja:

int xmin = min(x1,x2)
  , xmax = max(x1,x2)
  , ymin = min(y1,y2)
  , ymax = max(y1,y2);

for (int i = xmin; i < xmax; ++i)
    if (ymin <= i && i <= ymax)
        return true;

return false;

Jeśli nie korzystasz z wysokowydajnego sprawdzania zasięgu na miliardach liczb całkowitych o dużej odległości, nasze wersje powinny działać podobnie. Chodzi mi o to, że jest to mikrooptymalizacja.


Myślę, że przejrzałeś tutaj specyfikację. Zakłada się, że od x1 do x2 rośnie / maleje (tak czy inaczej, jest posortowane) - nie ma potrzeby pętli, wystarczy sprawdzić elementy głowy i ogona. Wolę jednak rozwiązanie min / max - po prostu dlatego, że łatwiej jest odczytać, gdy wrócisz do kodu później.
— Mark H

12
-1: to nie jest mikrooptymalizacja; to wybiera odpowiedni algorytm. Algorytmem jest O (n), gdy istnieje prosty wybór O (1).
— Simon Nickerson

Dzieje się tak, gdy „przedwczesna optymalizacja jest korzeniem wszelkiego zła” staje się nietykalną zasadą religijną dla nieudolnych zamiast na wpół poważnej uwagi na temat sporadycznych zachowań.
— rghome
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.