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?

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)


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;
}

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


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 .


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));
}

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

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

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.


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”


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ść.


-5

Mój ulubiony:

[min,x,max].sort()[1]

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
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.