¿Cuál es la forma más elegante de limitar un número a un segmento?

127

Digamos x, ay bson números. Necesito limitarme xa los límites del segmento [a, b].

Puedo escribir Math.max(a, Math.min(x, b)), pero no creo que sea muy fácil de leer. ¿Alguien tiene una manera inteligente de escribir esto de una manera más legible?

Samuel Rossille
fuente

Respuestas:

197

La forma en que lo haces es bastante estándar. Puede definir una clampfunción de utilidad :

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

(Aunque la ampliación de las funciones de lenguaje generalmente está mal vista)

Otto Allmendinger
fuente
OOps, copia / pega mi error tipográfico (intercambiado máximo y mínimo)
Samuel Rossille
55
No, lo obtuve del sitio. El orden de anidamiento / ejecución de Math.miny Math.maxes irrelevante siempre que el parámetro de Math.minis maxy el parámetro de Math.maxis min.
Otto Allmendinger
23
La extensión de los prototipos nativos ( Number.prototypeen este caso) no es aconsejable por una variedad de razones. Consulte "Mala práctica: extensión de prototipos nativos" en developer.mozilla.org/en-US/docs/Web/JavaScript/…
broofa
68

un enfoque menos orientado a "Matemáticas", pero también debería funcionar, de esta manera, la prueba </> está expuesta (quizás más comprensible que el minimaxing) pero realmente depende de lo que quiere decir con "legible"

function clamp(num, min, max) {
  return num <= min ? min : num >= max ? max : num;
}
dweeves
fuente
44
Es mejor reemplazar <y> con <= y> = en este idioma, para que potencialmente se haga una comparación menos. Con esa corrección, esta es definitivamente mi respuesta preferida: un máximo de minutos son confusos y propensos a errores.
Don Hatch
Esta es la mejor respuesta. Mucho más rápido, mucho más simple, mucho más legible.
Tristan
55
No es mas rapido. La solución Math.min / max es la más rápida jsperf.com/math-clamp/9
Manuel Di Iorio
1
@ManuelDiIorio ¿eh? bajo la versión actual de Chrome, el ternario simple es dos veces más rápido que Math.min / max, y si elimina la sobrecarga de la Math.random()llamada mucho más costosa , este enfoque simple es 10 veces más rápido .
mindplay.dk
48

Actualización para ECMAScript 2017:

Math.clamp(x, lower, upper)

Pero tenga en cuenta que a partir de hoy, es una propuesta de Etapa 1 . Hasta que sea ampliamente compatible, puede usar un polyfill .

Tomás Nikodym
fuente
44
Si desea realizar un seguimiento de esta propuesta y otras, consulte: github.com/tc39/proposals
gfullam
55
No encontré esta propuesta en el repositorio tc39 / propuestas
Colin D
Para aquellos que buscan, la propuesta se enumera en la Etapa 1 propuestas como "Extensiones matemáticas". La propuesta actual está aquí: github.com/rwaldron/proposal-math-extensions
WickyNilliams
14
Math.clip = function(number, min, max) {
  return Math.max(min, Math.min(number, max));
}
CAFxX
fuente
2
Esta solución es en realidad mucho más rápida, aunque considero extender la prototypemuy elegante cuando se trata de usar la función.
MaxArt
11

Esto no quiere ser una respuesta "solo use una biblioteca", pero en caso de que esté usando Lodash, puede usar .clamp:

_.clamp(yourInput, lowerBound, upperBound);

Así que eso:

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

Aquí está su implementación, tomada de la fuente de 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;
}

Además, vale la pena señalar que Lodash pone a disposición métodos individuales como módulos independientes, por lo que en caso de que solo necesite este método, puede instalarlo sin el resto de la biblioteca:

npm i --save lodash.clamp
Nobita
fuente
6

Si puede usar las funciones de flecha es6, también puede usar un enfoque de aplicación parcial:

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
fuente
La creación de una función cada vez que desea fijar un número parece muy ineficiente
George
1
Probablemente depende de su caso de uso. Esta es solo una función de fábrica para crear la abrazadera con un rango establecido. Puede crear una vez y reutilizar en su aplicación lo que sea. no tiene que ser llamado varias veces. Es posible que tampoco sea la herramienta para todos los casos de uso en los que no es necesario bloquear en un rango establecido.
bingles
@George si no necesita mantener el rango, simplemente elimine la función de flecha interna y mueva el parámetro de valor a la función de flecha externa
Alisson Nunes
6

Si no desea definir ninguna función, escribirla como si Math.min(Math.max(x, a), b)no fuera tan malo.

Ricardo
fuente
0

En el espíritu de la sensualidad de la flecha, puede crear una micro abrazadera / restricción / puerta / & c. función utilizando parámetros de descanso

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

Luego solo pasa tres valores

clamp(100,-3,someVar);

Es decir, de nuevo, si por sexy, te refieres a 'corto'

avivar el motor
fuente
no deberías usar matrices y ordenar por un clamp, perf tendrá un gran impacto si tienes una lógica compleja (incluso si es "sexy")
Andrew McOlash
0

Esto expande la opción ternaria en if / else que minified es equivalente a la opción ternary pero no sacrifica la legibilidad.

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

Se reduce a 35b (o 43b si se usa function):

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

Además, dependiendo de qué herramienta de rendimiento o navegador utilice, obtendrá varios resultados de si la implementación basada en matemáticas o la implementación basada en ternario es más rápida. En el caso de aproximadamente el mismo rendimiento, optaría por la legibilidad.

Michael Stramel
fuente
-5

Mi favorito:

[min,x,max].sort()[1]
usuario947856
fuente
12
Votación negativa porque no funciona. [1,5,19].sort()[1]devuelve 19. Podría arreglarse así: [min,x,max].sort(function(a, b) { return a - b; })[1]pero esto ya no es sexy. Además de cuando se usa mucho, puede ser un problema de rendimiento crear una nueva matriz solo para comparar tres números.
kayahr
55
Dando este +1 de todos modos, porque sexy es lo que cuenta.
Don Hatch
3
En mi humilde opinión, esto es mucho más legible que cualquiera de las otras opciones, especialmente con las funciones de flecha:[min, x, max].sort((a,b) => a-b)[1]
zaius
44
La razón por la cual esto no siempre funciona es porque el método de clasificación no compara los valores numéricos correctamente. (Los trata como cuerdas)
John Leuenhagen
Creo que no hay nada más sexy que una función bien nombrada que haga el trabajo de manera eficiente y obviamente correcta. Pero eso puede ser una cuestión de gustos.
BallpointBen