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


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

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

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

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

Создание функции каждый раз, когда вы хотите зафиксировать число, выглядит очень неэффективным
— Джордж

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]

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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.