Bir rakamı bir segmente sınırlamanın en zarif yolu nedir?


127

Diyelim ki x, ave bsayılardır. xSegmentin sınırlarına uymam gerekiyor [a, b].

Yazabilirim Math.max(a, Math.min(x, b))ama okumanın çok kolay olduğunu düşünmüyorum. Bunu daha okunaklı bir şekilde yazmanın akıllıca bir yolu olan var mı?

Yanıtlar:


197

Bunu yapma şekliniz oldukça standart. Bir yardımcı program clampişlevi tanımlayabilirsiniz :

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

(Dil yerleşiklerini genişletmek genellikle hoş karşılanmasa da)


OOps kopyalamak / yapıştırılan benim yazım hatası (maks takas ve dakika)
Samuel Rossille

5
Hayır, siteden aldım. Yuvalanma / yürütme düzeni Math.minve Math.maxuzun parametre olarak alakasız Math.minolan maxve parametresi Math.maxolan min.
Otto Allmendinger

23
Yerli prototiplerin genişletilmesi ( Number.prototypebu durumda) çeşitli nedenlerden ötürü tavsiye edilmez. En: "yerli prototip Uzatma Kötü örnek" Bkz developer.mozilla.org/en-US/docs/Web/JavaScript/...
broofa

68

daha az "Matematik" odaklı bir yaklaşım, ancak aynı zamanda çalışmalıdır, bu şekilde </> testi teşhir edilir (belki minimum eksiltmeden daha anlaşılabilir), ancak gerçekten "okunabilir" ile ne demek istediğinize bağlıdır

function clamp(num, min, max) {
  return num <= min ? min : num >= max ? max : num;
}

4
Bu deyimde <ve> 'yi <= ve> = ile değiştirmek daha iyidir, böylece potansiyel olarak bir tane daha az karşılaştırma yapılır. Bu düzeltmeyle, bu kesinlikle benim tercih ettiğim yanıt: maksimum dakika sayısı kafa karıştırıcı ve hataya meyillidir.
Don Hatch

Bu en iyi cevap. Çok daha hızlı, çok daha basit, çok daha okunaklı.
tristan

5
Daha hızlı değil. Math.min / max çözümü en hızlı jsperf.com/math-clamp/9
Manuel Di Iorio

1
@ManuelDiIorio ha? Chrome'un mevcut sürümünde, basit üçlü, Math.min / max'dan iki kat daha hızlıdır - ve çok daha pahalı Math.random()aramadan ek yükü kaldırırsanız , bu basit yaklaşım 10 kat daha hızlıdır .
mindplay.dk


48

ECMAScript 2017 Güncellemesi:

Math.clamp(x, lower, upper)

Ancak bugün itibariyle bunun bir Aşama 1 önerisi olduğunu unutmayın . Yaygın bir şekilde desteklenene kadar bir çoklu dolgu kullanabilirsiniz .


4
Bu teklifi ve diğerlerini takip etmek istiyorsanız, bkz: github.com/tc39/proposals
gfullam

5
Bu teklifi tc39 / teklifler deposunda bulamadım
Colin D

Arayanlar için, teklif Aşama 1 teklifleri altında "Matematik Uzantıları" olarak listelenir . Asıl teklif burada: github.com/rwaldron/proposal-math-extensions
WickyNilliams

14
Math.clip = function(number, min, max) {
  return Math.max(min, Math.min(number, max));
}

2
Bu çözüm aslında prototypeçok daha hızlı, ancak işlevi gerçekten kullanmak söz konusu olduğunda çok zarif olanı genişletmeyi düşünmeme rağmen .
MaxArt

11

Bu bir "sadece kitaplık kullan" cevabı olmak istemez, ancak Lodash kullanıyorsanız, şunu kullanabilirsiniz .clamp:

_.clamp(yourInput, lowerBound, upperBound);

Böylece:

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

İşte Lodash kaynağından alınan uygulaması :

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

Ayrıca, Lodash'ın tekli yöntemleri bağımsız modüller olarak kullanıma sunduğunu da belirtmek gerekir, bu nedenle yalnızca bu yönteme ihtiyacınız olması durumunda, kütüphanenin geri kalanı olmadan onu kurabilirsiniz:

npm i --save lodash.clamp

6

Es6 ok işlevlerini kullanabiliyorsanız, kısmi bir uygulama yaklaşımı da kullanabilirsiniz:

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

Bir sayıyı her kelepçelemek istediğinizde bir işlev oluşturmak çok verimsiz görünüyor
George

1
Muhtemelen kullanım durumunuza bağlıdır. Bu, kelepçeyi belirli bir aralıkta oluşturmak için bir fabrika işlevidir. Bir kez oluşturabilir ve uygulamanızın her yerinde yeniden kullanabilirsiniz. birden çok kez çağrılması gerekmez. Ayrıca, belirli bir aralıkta kilitlemeniz gerekmeyen tüm kullanım durumları için bir araç olmayabilir.
bingles

@George, aralığı korumanız gerekmiyorsa, sadece iç ok işlevini kaldırın ve değer parametresini dış ok işlevine taşıyın
Alisson Nunes

6

Herhangi bir işlevi tanımlamak istemiyorsan, onu böyle yazmak o Math.min(Math.max(x, a), b)kadar da kötü değil.


0

Ok seksiliği ruhu içinde bir mikro kelepçe / kısıtlama / geçit / & c oluşturabilirsiniz. dinlenme parametrelerini kullanan işlev

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

Sonra sadece üç değeri iletin

clamp(100,-3,someVar);

Yani yine, eğer seksiyse, 'kısa' demek istiyorsun


dizileri kullanmamalısınız ve a clamp, perf için sıralama , karmaşık mantığınız varsa ("seksi" olsa bile) ciddi bir darbe alacaktır
Andrew McOlash

0

Bu, üçlü seçeneği, üçlü seçeneğe eşdeğer olan ancak okunabilirlikten ödün vermeyen if / else şeklinde genişletir.

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

35b'ye kadar minifiler (veya kullanılıyorsa 43b function):

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

Ayrıca, kullandığınız mükemmel araç veya tarayıcıya bağlı olarak, Matematik tabanlı uygulamanın mı yoksa üçlü tabanlı uygulamanın mı daha hızlı olduğu konusunda çeşitli sonuçlar elde edersiniz. Aşağı yukarı aynı performans durumunda, okunabilirliği tercih ederim.


-5

Benim favorim:

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

12
Olumsuz oylama, çünkü işe yaramıyor. [1,5,19].sort()[1]19 döndürür. Şu şekilde düzeltilebilir: [min,x,max].sort(function(a, b) { return a - b; })[1]ama bu artık seksi değil. Ayrıca yoğun şekilde kullanıldığında, sadece üç sayıyı karşılaştırmak için yeni bir dizi oluşturmak bir performans sorunu olabilir.
kayahr

5
Bu + 1'i yine de vermek, çünkü önemli olan seksi.
Don Hatch

3
IMHO bu, diğer seçeneklerden çok daha okunabilir, özellikle de ok işlevleriyle:[min, x, max].sort((a,b) => a-b)[1]
zaius

4
Bunun her zaman işe yaramamasının nedeni, sıralama yönteminin sayısal değerleri doğru şekilde karşılaştırmamasıdır. (Onlara dizeler gibi davranır)
John Leuenhagen

İşi verimli bir şekilde ve açıkça doğru bir şekilde yapan, uygun şekilde adlandırılmış bir işlevden daha seksi bir şey olmadığını düşünüyorum. Ama bu bir zevk meselesi olabilir.
BallpointBen
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.