Qual è il modo più elegante per limitare un numero a un segmento?


127

Diciamo x, ae bsono numeri. Devo limitare xi limiti del segmento [a, b].

Posso scrivere Math.max(a, Math.min(x, b)), ma non credo sia molto facile da leggere. Qualcuno ha un modo intelligente di scrivere questo in un modo più leggibile?

Risposte:


197

Il modo in cui lo fai è piuttosto standard. È possibile definire una clampfunzione di utilità :

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

(Sebbene l'estensione del linguaggio integrato sia generalmente disapprovata)


OOps you copy / paste my typo (swaped max and min)
Samuel Rossille

5
No, l'ho preso dal sito. L'ordine di nidificazione / esecuzione di Math.mine Math.maxè irrilevante purché il parametro di Math.minis maxe il parametro di Math.maxis min.
Otto Allmendinger,

23
L'estensione dei prototipi nativi ( Number.prototypein questo caso) è sconsigliata per una serie di motivi. Vedi "Cattiva pratica: estensione di prototipi nativi" su developer.mozilla.org/en-US/docs/Web/JavaScript/…
broofa

68

un approccio meno orientato alla "matematica", ma dovrebbe funzionare anche in questo modo, il test </> è esposto (forse più comprensibile del minimoxing) ma dipende davvero da cosa intendi per "leggibile"

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

4
Meglio sostituire <e> con <= e> = in questo idioma, in modo che ci sia potenzialmente un confronto in meno fatto. Con quella correzione, questa è sicuramente la mia risposta preferita: massimi di minuti sono confusi e soggetti a errori.
Don Hatch,

Questa è la risposta migliore Molto più veloce, molto più semplice, molto più leggibile.
tristan,

5
Non è più veloce La soluzione Math.min / max è la più veloce jsperf.com/math-clamp/9
Manuel Di Iorio

1
@ManuelDiIorio eh? nella versione corrente di Chrome, il semplice ternario è due volte più veloce di Math.min / max - e se rimuovi l'overhead dalla Math.random()chiamata molto più costosa , questo semplice approccio è 10 volte più veloce .
mindplay.dk,


48

Aggiornamento per ECMAScript 2017:

Math.clamp(x, lower, upper)

Ma nota che da oggi è una proposta Stage 1 . Fino a quando non viene ampiamente supportato, è possibile utilizzare un polyfill .


4
Se si desidera tenere traccia di questa proposta e di altri, consultare: github.com/tc39/proposals
gfullam

5
Non ho trovato questa proposta nel repository tc39 / proposte
Colin D,

Per coloro che cercano, la proposta è elencata nella sezione Stage 1 come "Estensioni matematiche". La proposta attuale è qui: github.com/rwaldron/proposal-math-extensions
WickyNilliams

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

2
Questa soluzione è in realtà molto più veloce, anche se considero estendere l' prototypeeleganza quando si tratta di utilizzare effettivamente la funzione.
MaxArt,

11

Questa non vuole essere una risposta "usa solo una biblioteca" ma nel caso in cui tu stia usando Lodash puoi usare .clamp:

_.clamp(yourInput, lowerBound, upperBound);

Così che:

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

Ecco la sua implementazione, presa dalla fonte 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;
}

Inoltre, vale la pena notare che Lodash rende disponibili singoli metodi come moduli autonomi, quindi nel caso in cui sia necessario solo questo metodo, è possibile installarlo senza il resto della libreria:

npm i --save lodash.clamp

6

Se sei in grado di utilizzare le funzioni della freccia es6, puoi anche utilizzare un approccio applicativo parziale:

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

La creazione di una funzione ogni volta che vuoi bloccare un numero sembra molto inefficiente
George,

1
Probabilmente dipende dal tuo caso d'uso. Questa è solo una funzione di fabbrica per creare il morsetto con un intervallo impostato. Puoi creare una volta e riutilizzare in qualsiasi parte della tua app. non deve essere chiamato più volte. Inoltre potrebbe non essere lo strumento per tutti i casi d'uso in cui non è necessario bloccare in un intervallo prestabilito.
formicolio il

@George se non hai bisogno di mantenere l'intervallo, basta rimuovere la funzione freccia interna e spostare il parametro value sulla funzione freccia esterna
Alisson Nunes

6

Se non vuoi definire alcuna funzione, scriverla come Math.min(Math.max(x, a), b)non è poi così male.


0

Nello spirito della sensualità delle frecce, potresti creare un micro clamp / vincolo / gate / ecc. funzione utilizzando i parametri di riposo

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

Quindi passa tre valori

clamp(100,-3,someVar);

Cioè, di nuovo, se per sexy intendi "breve"


non dovresti usare le matrici e l'ordinamento per un clamp, perf avrà un colpo serio se hai una logica complessa (anche se è "sexy")
Andrew McOlash,

0

Questo espande l'opzione ternaria in if / else che è minimizzata è equivalente all'opzione ternaria ma non sacrifica la leggibilità.

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

Riduce a 35b (o 43b se si utilizza function):

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

Inoltre, a seconda dello strumento perf o del browser in uso, si ottengono vari risultati se l'implementazione basata sulla matematica o l'implementazione basata sul ternario è più veloce. Nel caso della stessa prestazione, opterei per la leggibilità.


-5

Il mio preferito:

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

12
Downvoting perché non funziona. [1,5,19].sort()[1]restituisce 19. Potrebbe essere risolto in questo modo: [min,x,max].sort(function(a, b) { return a - b; })[1]ma questo non è più sexy. Oltre a quando usato pesantemente potrebbe essere un problema di prestazioni per creare un nuovo array solo per confrontare tre numeri.
Kayahr

5
Dare questo +1 comunque, perché sexy è ciò che conta.
Don Hatch,

3
IMHO questo è molto più leggibile di qualsiasi altra opzione, specialmente con le funzioni freccia:[min, x, max].sort((a,b) => a-b)[1]
zaius

4
Il motivo per cui questo non sempre funziona è perché il metodo di ordinamento non confronta correttamente i valori numerici. (Li tratta come stringhe)
John Leuenhagen,

Penso che non ci sia nulla di più sexy di una funzione con un nome appropriato che svolga il lavoro in modo efficiente e ovviamente corretto. Ma potrebbe essere una questione di gusti.
BallpointBen
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.