Instrukcja switch dla wartości większej niż / mniejszej niż

230

więc chcę użyć instrukcji switch takiej jak ta:

switch (scrollLeft) {
  case (<1000):
   //do stuff
   break;
  case (>1000 && <2000):
   //do stuff
   break;
}

Teraz wiem, że żadna z tych instrukcji ( <1000) lub ( >1000 && <2000) nie będzie działać (oczywiście z różnych powodów). Pytam o najbardziej efektywny sposób, aby to zrobić. Nienawidzę używać 30 ifinstrukcji, więc wolałbym używać składni przełącznika. Czy mogę coś zrobić?

switz
źródło
5
czy twoje kroki są prawidłowe? To znaczy, jeśli podzielisz scrollLeft przez 1000, możesz przełączyć 1, 2, 3 ...
IcanDivideBy0
Być może możesz utworzyć posortowaną tablicę, która odwzorowuje zakres warunków z odpowiednią operacją, i zastosować do niej wyszukiwanie binarne. Lub jeśli twoje warunki są wystarczająco regularne, możesz zadzwonić bezpośrednio your_mapper_object[scrollLeft / SOME_CONST], zakładając, że your_mapper_objectcoś takiego {1: some_func, 2: another_func, ...}. W tym przypadku możesz również użyć przełącznika.
Overmind Jiang,

Odpowiedzi:

731

Kiedy spojrzałem na rozwiązania w innych odpowiedziach, zobaczyłem pewne rzeczy, które, jak wiem, są złe dla wydajności. Zamierzałem zamieścić je w komentarzu, ale pomyślałem, że lepiej będzie go porównać i podzielić się wynikami. Możesz to przetestować sam . Poniżej znajdują się moje wyniki (ymmv) znormalizowane po najszybszej operacji w każdej przeglądarce (pomnóż czas 1,0 przez znormalizowaną wartość, aby uzyskać czas bezwzględny w ms).

                    Chrome Firefox Opera MSIE Safari Node
-------------------------------------------------- -----------------
1.0 czas 37ms 73ms 68ms 184ms 73ms 21ms
if-natychmiastowe 1,0 1,0 1,0 2,6 1,0 1,0
jeśli-pośredni 1,2 1,8 3,3 3,8 2,6 1,0
natychmiastowe przełączenie 2,0 1,1 2,0 1,0 2,8 1,3
zakres przełączania 38,1 10,6 2,6 7,3 20,9 10,4
zakres przełączania 2 31,9 8,3 2,0 4,5 9,5 6,9
tablica przełączająca-pośrednia 35,2 9,6 4,2 5,5 10,7 8,6
przełącznik liniowo-liniowy 3,6 4,1 4,5 10,0 4,7 2,7
binarny przełącznik tablicowy 7,8 6,7 9,5 16,0 15,0 4,9

Testuj w systemie Windows 7 32bit z następującymi wersjami: Chrome 21.0.1180.89m , Firefox 15.0 , Opera 12.02 , MSIE 9.0.8112 , Safari 5.1.7 . Węzeł został uruchomiony na 64-bitowym systemie Linux, ponieważ rozdzielczość czasomierza w Node.js dla Windows wynosiła 10 ms zamiast 1 ms.

jeśli-natychmiast

Jest to najszybszy we wszystkich testowanych środowiskach, z wyjątkiem ... drumroll MSIE! (niespodzianka niespodzianka). Jest to zalecany sposób na jego wdrożenie.

if (val < 1000) { /*do something */ } else
if (val < 2000) { /*do something */ } else
...
if (val < 30000) { /*do something */ } else

jeśli-pośredni

Jest to wariant switch-indirect-arrayz ifopcją -statements i działa znacznie szybciej niż switch-indirect-arrayw prawie wszystkich testowanych środowiskach.

values=[
   1000,  2000, ... 30000
];
if (val < values[0]) { /* do something */ } else
if (val < values[1]) { /* do something */ } else
...
if (val < values[29]) { /* do something */ } else

natychmiastowa zmiana

Jest to dość szybkie we wszystkich testowanych środowiskach i faktycznie najszybsze w MSIE. Działa, gdy można wykonać obliczenia w celu uzyskania indeksu.

switch (Math.floor(val/1000)) {
  case 0: /* do something */ break;
  case 1: /* do something */ break;
  ...
  case 29: /* do something */ break;
}

zakres przełączania

Jest to około 6 do 40 razy wolniejsze niż najszybsze we wszystkich testowanych środowiskach, z wyjątkiem Opery, gdzie zajmuje to około półtora raza dłużej. Jest powolny, ponieważ silnik musi porównać wartość dwukrotnie dla każdego przypadku. Zaskakujące jest to, że Chrome zajmuje prawie 40 razy dłużej, niż w przypadku najszybszej operacji w Chrome, podczas gdy MSIE zajmuje tylko 6 razy dłużej. Ale rzeczywista różnica czasu wynosiła tylko 74 ms na korzyść MSIE przy 1337 ms (!).

switch (true) {
  case (0 <= val &&  val < 1000): /* do something */ break;
  case (1000 <= val &&  val < 2000): /* do something */ break;
  ...
  case (29000 <= val &&  val < 30000): /* do something */ break;
}

zakres przełączania 2

Jest to wariant, switch-rangeale z jednym porównaniem na skrzynkę, a zatem szybszy, ale wciąż bardzo wolny, z wyjątkiem Opery. Kolejność instrukcji case jest ważna, ponieważ silnik przetestuje każdą sprawę w kolejności kodu źródłowego ECMAScript262: 5 12.11

switch (true) {
  case (val < 1000): /* do something */ break;
  case (val < 2000): /* do something */ break;
  ...
  case (val < 30000): /* do something */ break;
}

tablica przełączania pośredniego

W tym wariancie zakresy są przechowywane w tablicy. Jest to powolne we wszystkich testowanych środowiskach i bardzo wolno w Chrome.

values=[1000,  2000 ... 29000, 30000];

switch(true) {
  case (val < values[0]): /* do something */ break;
  case (val < values[1]): /* do something */ break;
  ...
  case (val < values[29]): /* do something */ break;
}

tablicowo-liniowe wyszukiwanie

Jest to kombinacja liniowego wyszukiwania wartości w tablicy i instrukcji switch ze stałymi wartościami. Powodem, dla którego warto to wykorzystać, jest fakt, że wartości nie są znane do czasu uruchomienia. Jest wolny w każdym testowanym środowisku i zajmuje prawie 10 razy więcej czasu w MSIE.

values=[1000,  2000 ... 29000, 30000];

for (sidx=0, slen=values.length; sidx < slen; ++sidx) {
  if (val < values[sidx]) break;
}

switch (sidx) {
  case 0: /* do something */ break;
  case 1: /* do something */ break;
  ...
  case 29: /* do something */ break;
}

switch-binary-switch

Jest to wariant array-linear-switchz wyszukiwaniem binarnym. Niestety jest wolniejszy niż wyszukiwanie liniowe. Nie wiem, czy to moja implementacja, czy też wyszukiwanie liniowe jest bardziej zoptymalizowane. Możliwe też, że przestrzeń na klucze jest za mała.

values=[0, 1000,  2000 ... 29000, 30000];

while(range) {
  range = Math.floor( (smax - smin) / 2 );
  sidx = smin + range;
  if ( val < values[sidx] ) { smax = sidx; } else { smin = sidx; }
}

switch (sidx) {
  case 0: /* do something */ break;
  ...
  case 29: /* do something */ break;
}

Wniosek

Jeśli wydajność jest ważna, użyj if-statements lub switchz natychmiastowymi wartościami.

trochę
źródło
128
Rzadko widuje się odpowiedź z taką ilością szczegółów i uporządkowaną strukturą. Duża +1
Rick Donohoe
10
Duża +1 za wyjaśnienie strony wydajności tego problemu!
Zoltán Schmidt
16
Z tego powodu przepływ stosów jest jednym z najlepszych miejsc na odpowiedzi. To „ponadczasowa” odpowiedź, świetna robota i podziękowania za jsfiddle!
Jessy
1
grt informacje i wyjaśnienia
JayKandari
3
Naprawdę chciałbym móc +2, taka szczegółowa odpowiedź!
Kaspar Lee
96

Alternatywa:

var scrollleft = 1000;
switch (true)
{
    case (scrollleft > 1000):
      alert('gt');
      break;
    case (scrollleft <= 1000):
      alert('lt');
      break; 
}

Demo: http://jsfiddle.net/UWYzr/

labue
źródło
4
jest to bardziej wartościowe rozwiązanie. +1
IcanDivideBy0
1
Czy to nie to samo, co if(...) else if(...)? Pozwala to uniknąć, ifale nie wydaje mi się, że jest to całkiem zamiennik.
pimvdb,
7
Chociaż jest elegancki w kodowaniu, obniża wydajność. Jest prawie 30 razy wolniejszy w Chrome niż przy użyciu if-statements. Zobacz moją odpowiedź tutaj
niektóre
1
Jednak taka kara za wydajność jest nieistotna, gdy przetwarzane dane nie są duże i być może jest to tylko stosowana funkcja, na przykład sprawdzanie poprawności danych wprowadzanych przez jednego użytkownika, wtedy wybierana jest czytelność, a nie wydajność.
Jesús Franco,
1
Właśnie tego szukałem. Dzięki!
Ami Schreiber,
23
switch (Math.floor(scrollLeft/1000)) {
  case 0: // (<1000)
   //do stuff
   break;
  case 1: // (>=1000 && <2000)
   //do stuff;
   break;
}

Działa tylko, jeśli masz regularne kroki ...

EDYCJA: ponieważ to rozwiązanie wciąż zyskuje na popularności, muszę doradzić, że rozwiązanie mofolo jest o wiele lepsze

IcanDivideBy0
źródło
1
Swoją Math.round(scrollLeft/1000)drogą.
switz
@Switz - Należy pamiętać, że 999 <1000 przypada na przypadek 0, ale Math.round (999/1000) przypada na przypadek 1. Również powyżej jest literówka, w tym przypadku 1 to> = 1000, a nie tylko> 1000 .
Igor,
Jedynym problemem związanym z rozwiązaniem mofolo jest to, że jest on około 30 razy wolniejszy w Chrome niż IcanDivideBy0. Zobacz moją odpowiedź poniżej.
jakieś
6

Możesz utworzyć niestandardowy obiekt z kryteriami i funkcją odpowiadającą tym kryteriom

var rules = [{ lowerLimit: 0,    upperLimit: 1000, action: function1 }, 
             { lowerLimit: 1000, upperLimit: 2000, action: function2 }, 
             { lowerLimit: 2000, upperLimit: 3000, action: function3 }];

Zdefiniuj funkcje dla tego, co chcesz zrobić w tych przypadkach (zdefiniuj funkcję 1, funkcję 2 itd.)

I „oceniaj” zasady

function applyRules(scrollLeft)
{
   for(var i=0; i>rules.length; i++)
   {
       var oneRule = rules[i];
       if(scrollLeft > oneRule.lowerLimit && scrollLeft < oneRule.upperLimit)
       {
          oneRule.action();
       }
   }
}

Uwaga

Nienawidzę używać 30 instrukcji if

Wiele razy, jeśli wyciągi są łatwiejsze do odczytania i utrzymania. Poleciłbym powyższe tylko wtedy, gdy masz wiele warunków i możliwość dużego wzrostu w przyszłości.

Aktualizacja
Jak zauważył @Brad w komentarzach, jeśli warunki wykluczają się wzajemnie (tylko jeden z nich może być spełniony jednocześnie), sprawdzenie górnej granicy powinno wystarczyć:

if(scrollLeft < oneRule.upperLimit)

pod warunkiem, że warunki są zdefiniowane w porządku rosnącym (najpierw najniższy 0 to 1000, a następnie 1000 to 2000na przykład)

Nivas
źródło
action=function1- czy to nie powinny być dwukropki? ;-) - Możesz również zmienić to tak, aby mieć tylko górną granicę, ponieważ ze względu na proces eliminacji nie możesz należeć do dwóch grup - chyba że taki był twój zamiar (aby możliwe było wiele działań).
Brad Christie
@Brad Christie Oczywiście
Nivas
@Brad, nie, to nie było moim zamiarem i masz rację, górna granica powinna wystarczyć. Dodam to jako aktualizację ...
Nivas,
Uważam, że ten zwięzły i czysty +1
pimvdb
3

Co dokładnie robisz //do stuff?

Możesz być w stanie zrobić coś takiego:

(scrollLeft < 1000) ? //do stuff
: (scrollLeft > 1000 && scrollLeft < 2000) ? //do stuff
: (scrollLeft > 2000) ? //do stuff
: //etc. 
Igor
źródło
3

Nie sprawdzone i niepewne, czy to zadziała, ale dlaczego nie zrobić kilku if statementswcześniej, aby ustawić zmienne dla switch statement.

var small, big;

if(scrollLeft < 1000){
    //add some token to the page
    //call it small
}


switch (//reference token/) {
  case (small):
   //do stuff
   break;
  case (big):
   //do stuff;
   break;
}
Jason Gennaro
źródło
2

To kolejna opcja:

     switch (true) {
         case (value > 100):
             //do stuff
             break;
         case (value <= 100)&&(value > 75):
             //do stuff
             break;
         case (value < 50):
            //do stuff
             break;
     }
Pablo Claus
źródło
1

Aktualizacja zaakceptowanej odpowiedzi (nie można jeszcze komentować). Począwszy od 1/12/16 przy użyciu demo jsfiddle w chrome, natychmiastowe przełączanie jest najszybszym rozwiązaniem.

Wyniki: Rozdzielczość czasowa: 1,33

   25ms "if-immediate" 150878146 
   29ms "if-indirect" 150878146
   24ms "switch-immediate" 150878146
   128ms "switch-range" 150878146
   45ms "switch-range2" 150878146
   47ms "switch-indirect-array" 150878146
   43ms "array-linear-switch" 150878146
   72ms "array-binary-switch" 150878146

Skończone

 1.04 (   25ms) if-immediate
 1.21 (   29ms) if-indirect
 1.00 (   24ms) switch-immediate
 5.33 (  128ms) switch-range
 1.88 (   45ms) switch-range2
 1.96 (   47ms) switch-indirect-array
 1.79 (   43ms) array-linear-switch
 3.00 (   72ms) array-binary-switch
jeffhale
źródło
to naprawdę zależy - 15ms „jeśli-natychmiastowy” 15ms ”jeśli-pośredni„ 15ms ”przełącznik-natychmiastowy„ 37ms ”zakres przełączania„ 28ms ”zakres przełączania2„ 35ms ”przełącznik-układ pośredni„ 29ms ”układ-przełącznik liniowy” 62 ms „macierzowy przełącznik binarny” Zakończony 1,00 (15ms) jeśli-natychmiastowy 1,00 (15ms) jeśli-pośredni 1,00 (15ms) natychmiastowy przełącznik 2,47 (37ms) zakres przełączenia 1,87 (28ms) zakres przełączenia2 2,33 (35ms) przełącznik- tablica pośrednia 1.93 (29ms) tablica-przełącznik liniowy 4.13 (62ms) tablica-przełącznik binarny chrom Wersja 48.0.2564.109 (64-bit) Mac OS X 10.11.3
RenaissanceProgrammer
ATM Safari 9.X na Mac OS X i Safari ios 9.3, wyraźnym zwycięzcą jest „if-instant”
RenaissanceProgrammer
1
Różnica 1 ms to za mało, aby się tym przejmować. Różni się bardziej niż w każdym przebiegu testowym. Chodzi o to: Użyj stylu kodowania, który ma sens, i nie próbuj mikrooptymalizować.
około
1

W moim przypadku (kodowanie kolorami w procentach, nic krytycznego dla wydajności) szybko napisałem:

function findColor(progress) {
    const thresholds = [30, 60];
    const colors = ["#90B451", "#F9A92F", "#90B451"];

    return colors.find((col, index) => {
        return index >= thresholds.length || progress < thresholds[index];
    });
}
grebenyuksv
źródło
1

Nienawidzę używać 30 instrukcji if

Ostatnio miałem tę samą sytuację, tak to rozwiązałem:

przed:

if(wind_speed >= 18) {
    scale = 5;
} else if(wind_speed >= 12) {
    scale = 4;
} else if(wind_speed >= 9) {
    scale = 3;
} else if(wind_speed >= 6) {
    scale = 2;
} else if(wind_speed >= 4) {
    scale = 1;
}

po:

var scales = [[4, 1], [6, 2], [9, 3], [12, 4], [18, 5]];
scales.forEach(function(el){if(wind_speed > el[0]) scale = el[1]});

A jeśli ustawisz „1, 2, 3, 4, 5”, może to być jeszcze prostsze:

var scales = [4, 6, 9, 12, 18];
scales.forEach(function(el){if(wind_speed >= el) scale++});
Jaskółka oknówka
źródło