Apa cara paling elegan untuk membatasi angka ke suatu segmen?

127

Katakanlah x, adan bangka. Saya perlu membatasi xbatasan segmen [a, b].

Saya bisa menulis Math.max(a, Math.min(x, b)), tetapi saya rasa itu tidak mudah dibaca. Apakah ada yang punya cara pintar untuk menulis ini dengan cara yang lebih mudah dibaca?

Samuel Rossille
sumber

Jawaban:

197

Cara Anda melakukannya cukup standar. Anda dapat mendefinisikan clampfungsi utilitas :

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

(Meskipun built-in bahasa tambahan umumnya disukai)

Otto Allmendinger
sumber
OOps Anda menyalin / menempelkan kesalahan ketik saya (maks dan minimum yang ditukar)
Samuel Rossille
5
Tidak, saya mendapatkannya dari situs. Urutan / eksekusi urutan Math.mindan Math.maxtidak relevan selama parameter Math.minis maxdan parameter Math.maxis min.
Otto Allmendinger
23
Memperluas prototipe asli ( Number.prototypedalam hal ini) keliru karena berbagai alasan. Lihat "Praktik buruk: Perpanjangan prototipe asli" di developer.mozilla.org/en-US/docs/Web/JavaScript/…
broofa
68

pendekatan yang kurang berorientasi "Matematika", tetapi juga harus bekerja, dengan cara ini, tes </> diekspos (mungkin lebih dimengerti daripada minimaxing) tetapi itu benar-benar tergantung pada apa yang Anda maksud dengan "dibaca"

function clamp(num, min, max) {
  return num <= min ? min : num >= max ? max : num;
}
dweeves
sumber
4
Lebih baik mengganti <dan> dengan <= dan> = dalam idiom ini, sehingga ada satu perbandingan yang berpotensi dilakukan. Dengan koreksi itu, ini jelas jawaban yang saya sukai: maks. Menit membingungkan dan rentan kesalahan.
Don Hatch
Ini jawaban terbaik. Jauh lebih cepat, lebih sederhana, lebih mudah dibaca.
tristan
5
Itu tidak lebih cepat. Solusi Math.min / max adalah jsperf.com/math-clamp/9
Manuel Di Iorio
1
@ManuelDiIorio ya? di bawah versi Chrome saat ini, terner sederhana dua kali lebih cepat dari Math.min / max - dan jika Anda menghapus overhead dari Math.random()panggilan yang jauh lebih mahal , pendekatan sederhana ini 10 x lebih cepat .
mindplay.dk
48

Pembaruan untuk ECMAScript 2017:

Math.clamp(x, lower, upper)

Tetapi perhatikan bahwa pada hari ini, ini adalah proposal Tahap 1 . Sampai mendapat banyak dukungan, Anda dapat menggunakan polyfill .

Tomas Nikodym
sumber
4
Jika Anda ingin melacak proposal ini dan yang lainnya, lihat: github.com/tc39/proposals
gfullam
5
Saya tidak menemukan proposal ini di tc39 / proposal repo
Colin D
Bagi mereka yang mencari, proposal terdaftar di bawah proposal Tahap 1 sebagai "Ekstensi Matematika". Proposal yang sebenarnya ada di sini: github.com/rwaldron/proposal-math-extensions
WickyNilliams
14
Math.clip = function(number, min, max) {
  return Math.max(min, Math.min(number, max));
}
CAFxX
sumber
2
Solusi ini sebenarnya jauh lebih cepat, meskipun saya menganggap memperluas prototypesangat elegan ketika datang untuk benar-benar menggunakan fungsinya.
MaxArt
11

Ini tidak ingin menjadi jawaban "just-use-a-library" tetapi jika Anda menggunakan Lodash Anda dapat menggunakan .clamp:

_.clamp(yourInput, lowerBound, upperBound);

Yang seperti itu:

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

Berikut ini implementasinya, diambil dari sumber 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;
}

Juga, perlu dicatat bahwa Lodash membuat metode tunggal tersedia sebagai modul mandiri, jadi jika Anda hanya membutuhkan metode ini, Anda bisa menginstalnya tanpa perpustakaan:

npm i --save lodash.clamp
Nobita
sumber
6

Jika Anda dapat menggunakan fungsi panah ES6, Anda juga bisa menggunakan pendekatan aplikasi parsial:

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
sumber
Penciptaan fungsi setiap kali Anda ingin menjepit suatu angka terlihat sangat tidak efisien
George
1
Mungkin tergantung pada kasus penggunaan Anda. Ini hanya fungsi pabrik untuk membuat penjepit dengan rentang yang ditetapkan. Anda dapat membuat sekali dan menggunakan kembali seluruh aplikasi Anda apa pun. tidak harus dipanggil beberapa kali. Juga mungkin bukan alat untuk semua kasus penggunaan di mana Anda tidak perlu mengunci dalam rentang yang ditetapkan.
bingles
@ George jika Anda tidak perlu menjaga jarak, cukup hapus fungsi panah dalam dan pindahkan parameter nilai ke fungsi panah luar
Alisson Nunes
6

Jika Anda tidak ingin mendefinisikan fungsi apa pun, menulis seperti Math.min(Math.max(x, a), b)itu tidak terlalu buruk.

Ricardo
sumber
0

Dalam semangat keseksian panah, Anda bisa membuat penjepit mikro / constrain / gate / & c. berfungsi menggunakan parameter istirahat

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

Lalu, berikan tiga nilai

clamp(100,-3,someVar);

Artinya, sekali lagi, jika secara seksi, maksud Anda 'pendek'

stoke motor
sumber
Anda tidak boleh menggunakan array dan menyortir untuk clamp, perf akan menerima pukulan serius jika Anda memiliki logika kompleks (bahkan jika itu "seksi")
Andrew McOlash
0

Ini memperluas opsi ternary ke jika / jika minified yang setara dengan opsi ternary tetapi tidak mengorbankan keterbacaan.

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

Diminimalkan ke 35b (atau 43b jika menggunakan function):

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

Juga, tergantung pada alat atau browser apa yang Anda gunakan, Anda mendapatkan berbagai hasil apakah implementasi berbasis matematika atau implementasi berbasis terner lebih cepat. Dalam hal kinerja yang kira-kira sama, saya akan memilih keterbacaan.

Michael Stramel
sumber
-5

Kesukaanku:

[min,x,max].sort()[1]
pengguna947856
sumber
12
Downvoting karena tidak berfungsi. [1,5,19].sort()[1]mengembalikan 19. Bisa diperbaiki seperti ini: [min,x,max].sort(function(a, b) { return a - b; })[1]tetapi ini tidak seksi lagi. Selain ketika banyak digunakan mungkin masalah kinerja untuk membuat array baru hanya untuk membandingkan tiga angka.
kayahr
5
Tetap memberi +1 ini, karena seksi adalah yang terpenting.
Don Hatch
3
IMHO ini jauh lebih mudah dibaca daripada opsi lain, terutama dengan fungsi panah:[min, x, max].sort((a,b) => a-b)[1]
zaius
4
Alasan mengapa ini tidak selalu berhasil adalah karena metode pengurutan tidak membandingkan nilai numerik dengan benar. (Ini memperlakukan mereka seperti string)
John Leuenhagen
Saya pikir tidak ada yang lebih seksi daripada fungsi yang tepat yang melakukan pekerjaan secara efisien dan dengan cara yang benar-benar benar. Tapi itu mungkin masalah selera.
BallpointBen