Какой самый элегантный способ ограничить число сегментом?

127

Скажем x, aи bявляются числами. Мне нужно xограничить границы сегмента [a, b].

Я умею писать Math.max(a, Math.min(x, b)), но не думаю, что это очень легко читать. Есть ли у кого-нибудь умный способ написать это более читаемым способом?

Сэмюэл Россилль
источник

Ответы:

197

То, как вы это делаете, довольно стандартно. Вы можете определить служебную 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);
};

(Хотя расширение встроенных языков обычно не одобряется)

Отто Аллмендингер
источник
Упс, вы скопировали / вставили мою опечатку (поменяли местами максимум и минимум)
Сэмюэл Россилль
5
Неа, достал с сайта. Порядок вложения / выполнения Math.minи не Math.maxимеет значения, пока параметр Math.minis maxи параметр Math.maxis min.
Otto Allmendinger
23
Расширять собственные прототипы ( Number.prototypeв данном случае) не рекомендуется по ряду причин. См. «Плохая практика: расширение собственных прототипов» на developer.mozilla.org/en-US/docs/Web/JavaScript/…
broofa
68

менее «математический» подход, но он также должен работать, таким образом, тест </> выставлен (возможно, более понятен, чем минимаксинг), но это действительно зависит от того, что вы имеете в виду под «читаемым»

function clamp(num, min, max) {
  return num <= min ? min : num >= max ? max : num;
}
dweeves
источник
4
Лучше заменить <и> на <= и> = в этой идиоме, чтобы было возможно на одно сравнение меньше. С этим исправлением это определенно мой предпочтительный ответ: максимальное количество минут сбивает с толку и подвержено ошибкам.
Don Hatch
Это лучший ответ. Намного быстрее, проще, читабельнее.
tristan
5
Это не быстрее. Решение Math.min / max самое быстрое jsperf.com/math-clamp/9
Мануэль Ди
1
@ManuelDiIorio да? в текущей версии Chrome простой тернар в два раза быстрее, чем Math.min / max - и если вы уберете накладные расходы из гораздо более дорогостоящего Math.random()вызова, этот простой подход будет в 10 раз быстрее .
mindplay.dk
48

Обновление для ECMAScript 2017:

Math.clamp(x, lower, upper)

Но обратите внимание , что на сегодняшний день, это этап 1 предложение . Пока он не получит широкой поддержки, вы можете использовать полифил .

Томас Никодим
источник
4
Если вы хотите отслеживать это и другие предложения, см .: github.com/tc39/proposals
gfullam
5
Я не нашел этого предложения в репозитории tc39 / предложения
Colin D
Для тех, кто ищет, предложение указано в предложениях Этапа 1 как «Расширения по математике». Актуальное предложение находится здесь: github.com/rwaldron/proposal-math-extensions
WickyNilliams
14
Math.clip = function(number, min, max) {
  return Math.max(min, Math.min(number, max));
}
CAFxX
источник
2
Это решение на самом деле намного быстрее, хотя я считаю его prototypeочень элегантным, когда дело доходит до фактического использования функции.
MaxArt
11

Это не должно быть ответом типа «просто используйте библиотеку», но на всякий случай, если вы используете Lodash, вы можете использовать .clamp:

_.clamp(yourInput, lowerBound, upperBound);

Так что:

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

Вот его реализация, взятая из источника 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;
}

Также стоит отметить, что Lodash делает отдельные методы доступными в виде отдельных модулей, поэтому, если вам нужен только этот метод, вы можете просто установить его без остальной части библиотеки:

npm i --save lodash.clamp
Nobita
источник
6

Если вы можете использовать стрелочные функции es6, вы также можете использовать подход частичного приложения:

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
источник
Создание функции каждый раз, когда вы хотите зафиксировать число, выглядит очень неэффективным
Джордж
1
Вероятно, зависит от вашего варианта использования. Это просто заводская функция для создания зажима с заданным диапазоном. Вы можете создать один раз и использовать в своем приложении что угодно. не нужно вызывать несколько раз. Также не может быть инструментом для всех случаев использования, когда вам не нужно блокировать заданный диапазон.
bingles
@George, если вам не нужно сохранять диапазон, просто удалите функцию внутренней стрелки и переместите параметр значения во внешнюю функцию стрелки
Алиссон Нуньес,
6

Если вы не хотите определять какую-либо функцию, написать ее как Math.min(Math.max(x, a), b)не так уж плохо.

Рикардо
источник
0

В духе сексуальности стрелы вы можете создать микрозажим / ограничение / гейт / и т. Д. функция с использованием параметров отдыха

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

Затем просто передайте три значения

clamp(100,-3,someVar);

То есть, опять же, если под сексуальным вы имеете в виду «короткий»

сток мотор
источник
вам не следует использовать массивы и сортировку для a clamp, perf серьезно пострадает, если у вас сложная логика (даже если она «сексуальна»)
Эндрю МакОлаш
0

Это расширяет тернарный вариант до if / else, который минимизирован, эквивалентен тернарному варианту, но не жертвует удобочитаемостью.

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

Уменьшается до 35b (или 43b при использовании function):

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

Кроме того, в зависимости от того, какой инструмент perf или браузер вы используете, вы получите различные результаты, будь то реализация на основе математики или троичная реализация быстрее. В случае примерно такой же производительности я бы выбрал удобочитаемость.

Майкл Страмел
источник
-5

Моя любимая:

[min,x,max].sort()[1]
user947856
источник
12
Голосование против, потому что это не работает. [1,5,19].sort()[1]возвращает 19. Это можно исправить вот так: [min,x,max].sort(function(a, b) { return a - b; })[1]но это уже не сексуально. Помимо интенсивного использования, создание нового массива для сравнения трех чисел может вызвать проблемы с производительностью.
kayahr
5
В любом случае даю +1, потому что главное - сексуальность.
Don Hatch
3
ИМХО, это гораздо более читабельно, чем любой другой вариант, особенно со стрелочными функциями:[min, x, max].sort((a,b) => a-b)[1]
zaius
4
Причина, по которой это не всегда работает, заключается в том, что метод сортировки неправильно сравнивает числовые значения. (Он обращается с ними как с струнами)
Джон Леуэнхаген
Я думаю, что нет ничего более привлекательного, чем функция с удачным названием, которая выполняет свою работу эффективно и очевидно правильным образом. Но это может быть дело вкуса.
BallpointBen