Який найелегантніший спосіб привласнити номер до сегмента?


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.minє maxта параметр Math.maxє 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
Краще замінити <і> на <= і> = в цій ідіомі, щоб потенційно було проведено порівняння менше. З цією корекцією, це, безумовно, моя краща відповідь: максимум хвилин є заплутаним та схильним до помилок.
Дон Хетч

Це найкраща відповідь. Набагато швидше, набагато простіше, набагато читабельніше.
Трістан

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)

Але зауважте, що на сьогоднішній день це пропозиція першого етапу . Поки вона не отримає широкої підтримки, ви можете використовувати полів .


4
Якщо ви хочете відслідковувати цю пропозицію та інші, дивіться: github.com/tc39/proposals
gfullam

5
Цю пропозицію я не знайшов у репортажі tc39 / offers
Colin D

Для тих, хто шукає, ця пропозиція занесена до пропозицій на етапі 1 як "Розширення математики". Актуальна пропозиція тут: github.com/rwaldron/proposed-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;
}

Крім того, варто зазначити, що Лодаш робить окремі методи доступними як окремі модулі, тому, якщо вам потрібен лише цей метод, ви можете просто встановити його без іншої частини бібліотеки:

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
Можливо, це залежить від вашого випадку використання. Це просто фабрична функція для створення затискача із заданим діапазоном. Ви можете створити один раз і повторно використовувати їх у всьому додатку. не потрібно викликати кілька разів. Також може бути не інструментом для всіх випадків використання, коли не потрібно фіксувати заданий діапазон.
бінглі

@George, якщо вам не потрібно зберігати діапазон, просто видаліть функцію внутрішньої стрілки та
переведіть

6

Якщо ви не хочете визначати жодну функцію, писати її як би Math.min(Math.max(x, a), b)не так вже й погано.


0

У дусі сексуальності стріли ви могли б створити мікро-затискач / обмеження / ворота / & c. функція за допомогою параметрів спокою

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

Потім просто переведіть три значення

clamp(100,-3,someVar);

Тобто, знову ж таки, якщо під сексуальним, ви маєте на увазі "короткий"


вам не слід використовувати масиви та сортування для a clamp, perf спричинить серйозний удар, якщо у вас складна логіка (навіть якщо це "сексуально")
Andrew McOlash

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;

Крім того, залежно від того, яким інструментом чи браузером ви користуєтеся, ви отримуєте різні результати, чи швидша реалізація на основі математики, так і потрійна реалізація. У випадку приблизно однакової продуктивності я б вирішив читати.


-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, бо важливо сексуальне.
Дон Хетч

3
IMHO це набагато читабельніше, ніж будь-який з інших варіантів, особливо з функціями стрілки:[min, x, max].sort((a,b) => a-b)[1]
zaius

4
Причина, чому це не завжди працює, полягає в тому, що метод сортування не порівнює чисельні значення належним чином. (Це трактує їх як струни)
Джон Левенгаген

Я думаю, що немає нічого сексуальнішого, ніж влучно названа функція, яка виконує роботу ефективно та очевидно правильно. Але це може бути справою смаку.
BallpointBen
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.