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.