Jaki jest najbardziej elegancki sposób na ograniczenie liczby do segmentu?

127

Powiedzmy x, ai bsą liczbami. Muszę zmieścić xsię w granicach segmentu [a, b].

Potrafię pisać Math.max(a, Math.min(x, b)), ale nie wydaje mi się, żeby było łatwe do odczytania. Czy ktoś ma sprytny sposób, aby napisać to w bardziej czytelny sposób?

Samuel Rossille
źródło

Odpowiedzi:

197

Sposób, w jaki to robisz, jest dość standardowy. Możesz zdefiniować funkcję narzędziową clamp:

/**
 * Returns a number whose value is limited to the given range.
 *
 * Example: limit the output of this computation to between 0 and 255
 * (x * 255).clamp(0, 255)
 *
 * @param {Number} min The lower boundary of the output range
 * @param {Number} max The upper boundary of the output range
 * @returns A number in the range [min, max]
 * @type Number
 */
Number.prototype.clamp = function(min, max) {
  return Math.min(Math.max(this, min), max);
};

(Chociaż rozszerzenia wbudowanych języków są generalnie źle widziane)

Otto Allmendinger
źródło
OOps, skopiowałeś / wkleiłeś moją literówkę (zamieniono max i min)
Samuel Rossille.
5
Nie, mam to ze strony. Kolejność zagnieżdżania / wykonywania Math.mini Math.maxjest nieistotna, o ile parametr Math.minjest maxi parametr Math.maxjest min.
Otto Allmendinger
23
Rozszerzanie natywnych prototypów ( Number.prototypew tym przypadku) jest niewskazane z różnych powodów. Zobacz „Złe praktyki: rozszerzenie natywnych prototypów” na stronie developer.mozilla.org/en-US/docs/Web/JavaScript/…
broofa
68

podejście mniej zorientowane na matematykę, ale powinno również działać, w ten sposób test </> jest ujawniony (może być bardziej zrozumiały niż minimaksowanie), ale tak naprawdę zależy od tego, co rozumiesz przez „czytelny”

function clamp(num, min, max) {
  return num <= min ? min : num >= max ? max : num;
}
dweeves
źródło
4
Lepiej jest zamienić <i> na <= i> = w tym idiomie, aby potencjalnie zrobić jedno porównanie mniej. Z tą korektą jest to zdecydowanie moja preferowana odpowiedź: maksymalne wartości minut są mylące i podatne na błędy.
Don Hatch
To najlepsza odpowiedź. Dużo szybszy, dużo prostszy, dużo bardziej czytelny.
tristan
5
To nie jest szybsze. Rozwiązanie Math.min / max jest najszybsze jsperf.com/math-clamp/9
Manuel Di Iorio
1
@ManuelDiIorio huh? w obecnej wersji Chrome prosta trójskładnik jest dwa razy szybszy niż Math.min / max - a jeśli usuniesz narzut ze znacznie droższego Math.random()połączenia, to proste podejście jest 10 razy szybsze .
mindplay.dk
4
Usunięcie narzutu z wywołania Math.random (), rozwiązanie Math.min / max jest najszybsze . To trójskładnikowe rozwiązanie jest o 80% wolniejsze.
idbrii
48

Aktualizacja dla ECMAScript 2017:

Math.clamp(x, lower, upper)

Należy jednak pamiętać, że na dzień dzisiejszy jest to propozycja etapu 1 . Dopóki nie uzyska szerokiego wsparcia, możesz używać polyfill .

Tomas Nikodym
źródło
4
Jeśli chcesz śledzić tę i inne propozycje, zobacz: github.com/tc39/proposity
gfullam
5
Nie znalazłem tej propozycji w repozytorium tc39 / offers
Colin D
Dla szukających propozycja jest wymieniona w ramach propozycji etapu 1 jako „Rozszerzenia matematyczne”. Aktualna propozycja jest tutaj: github.com/rwaldron/proposal-math-extensions
WickyNilliams
14
Math.clip = function(number, min, max) {
  return Math.max(min, Math.min(number, max));
}
CAFxX
źródło
2
To rozwiązanie jest w rzeczywistości znacznie szybsze, chociaż rozważam rozszerzenie prototype bardzo eleganckiego, jeśli chodzi o faktyczne korzystanie z funkcji.
MaxArt,
11

To nie jest odpowiedź typu „po prostu użyj biblioteki”, ale na wypadek, gdybyś korzystał z Lodash, możesz użyć .clamp:

_.clamp(yourInput, lowerBound, upperBound);

Po to aby:

_.clamp(22, -10, 10); // => 10

Oto jego implementacja, zaczerpnięta ze źródła Lodash :

/**
 * The base implementation of `_.clamp` which doesn't coerce arguments.
 *
 * @private
 * @param {number} number The number to clamp.
 * @param {number} [lower] The lower bound.
 * @param {number} upper The upper bound.
 * @returns {number} Returns the clamped number.
 */
function baseClamp(number, lower, upper) {
  if (number === number) {
    if (upper !== undefined) {
      number = number <= upper ? number : upper;
    }
    if (lower !== undefined) {
      number = number >= lower ? number : lower;
    }
  }
  return number;
}

Warto również zauważyć, że Lodash udostępnia pojedyncze metody jako samodzielne moduły, więc jeśli potrzebujesz tylko tej metody, możesz po prostu zainstalować ją bez reszty biblioteki:

npm i --save lodash.clamp
Nobita
źródło
6

Jeśli jesteś w stanie używać funkcji strzałek es6, możesz również zastosować częściowe podejście do aplikacji:

const clamp = (min, max) => (value) =>
    value < min ? min : value > max ? max : value;

clamp(2, 9)(8); // 8
clamp(2, 9)(1); // 2
clamp(2, 9)(10); // 9

or

const clamp2to9 = clamp(2, 9);
clamp2to9(8); // 8
clamp2to9(1); // 2
clamp2to9(10); // 9
bingles
źródło
Tworzenie funkcji za każdym razem, gdy chcesz zacisnąć liczbę, wydaje się bardzo nieefektywne
George
1
Prawdopodobnie zależy to od twojego przypadku użycia. To tylko funkcja fabryczna do tworzenia zacisku z ustawionym zakresem. Możesz tworzyć raz i ponownie używać w całej aplikacji. nie trzeba dzwonić wiele razy. Może też nie być narzędziem do wszystkich przypadków użycia, w których nie trzeba blokować ustalonego zakresu.
bingles
@George, jeśli nie musisz utrzymywać zakresu, po prostu usuń funkcję strzałki wewnętrznej i przenieś parametr wartości do funkcji strzałki zewnętrznej
Alisson Nunes
6

Jeśli nie chcesz definiować żadnej funkcji, napisanie jej w Math.min(Math.max(x, a), b)ten sposób nie jest takie złe.

Ricardo
źródło
0

W duchu seksowności strzały możesz stworzyć mikro zacisk / ograniczenie / bramkę / i c. funkcja przy użyciu parametrów resztowych

var clamp = (...v) => v.sort((a,b) => a-b)[1];

Następnie podaj po prostu trzy wartości

clamp(100,-3,someVar);

To znaczy, znowu, jeśli mówiąc seksowny, masz na myśli „krótki”

silnik napędzany
źródło
nie powinieneś używać tablic i sortowania clamp, jeśli masz złożoną logikę (nawet jeśli jest „seksowna”), perf odniesie poważny sukces
Andrew McOlash
0

To rozszerza opcję trójskładnikową do if / else, która zminimalizowana jest równoważna opcji trójskładnikowej, ale nie powoduje utraty czytelności.

const clamp = (value, min, max) => {
  if (value < min) return min;
  if (value > max) return max;
  return value;
}

Zmniejsza do 35b (lub 43b, jeśli używasz function):

const clamp=(c,a,l)=>c<a?a:c>l?l:c;

Ponadto, w zależności od używanego narzędzia perf lub przeglądarki, można uzyskać różne wyniki, czy implementacja oparta na matematyce czy implementacja oparta na trzech elementach jest szybsza. W przypadku mniej więcej takiej samej wydajności wybrałbym czytelność.

Michael Stramel
źródło
-5

Mój ulubiony:

[min,x,max].sort()[1]
user947856
źródło
12
Głosowanie w dół, ponieważ to nie działa. [1,5,19].sort()[1]zwraca 19. Można to naprawić w ten sposób: [min,x,max].sort(function(a, b) { return a - b; })[1]ale to już nie jest seksowne. Oprócz tego, że jest intensywnie używany, może wystąpić problem z wydajnością, aby utworzyć nową tablicę tylko w celu porównania trzech liczb.
kayahr
5
Mimo wszystko dajesz +1, bo liczy się seks.
Don Hatch,
3
IMHO jest to o wiele bardziej czytelne niż jakakolwiek inna opcja, szczególnie z funkcjami strzałkowymi:[min, x, max].sort((a,b) => a-b)[1]
zaius
4
Powodem, dla którego to nie zawsze działa, jest to, że metoda sortowania nie porównuje poprawnie wartości liczbowych. (Traktuje je jak struny)
John Leuenhagen
Myślę, że nie ma nic bardziej seksownego niż trafnie nazwana funkcja, która wykonuje swoją pracę wydajnie i w oczywiście poprawny sposób. Ale to może być kwestia gustu.
BallpointBen