Qual é a maneira mais elegante de limitar um número a um segmento?


127

Digamos x, ae bsão números. Eu preciso limitar xos limites do segmento [a, b].

Eu sei escrever Math.max(a, Math.min(x, b)), mas não acho que seja muito fácil de ler. Alguém tem uma maneira inteligente de escrever isso de uma maneira mais legível?

Respostas:


197

A maneira como você faz isso é bastante padrão. Você pode definir uma clampfunção de utilitário :

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

(Embora a extensão de built-ins de idiomas geralmente seja mal vista)


OOps copiar / colar o meu erro de digitação (trocado máximo e mínimo)
Samuel Rossille

5
Não, eu peguei no site. A ordem de aninhamento / execução de Math.mine Math.maxé irrelevante, desde que o parâmetro de Math.miné maxe o parâmetro de Math.maxseja min.
Otto Allmendinger

23
A extensão de protótipos nativos ( Number.prototypeneste caso) é desaconselhada por vários motivos. Consulte "Má prática: extensão de protótipos nativos" em developer.mozilla.org/en-US/docs/Web/JavaScript/…
broofa

68

uma abordagem menos orientada à "matemática", mas também deve funcionar, dessa forma, o teste </> é exposto (talvez mais compreensível que o minimax), mas realmente depende do que você quer dizer com "legível"

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

4
É melhor substituir <e> por <= e> = neste idioma, para que haja potencialmente menos uma comparação feita. Com essa correção, esta é definitivamente a minha resposta preferida: o máximo de minutos é confuso e propenso a erros.
Don escotilha

Esta é a melhor resposta. Muito mais rápido, muito mais simples, muito mais legível.
Tristan

5
Não é mais rápido. Math.min / solução max é mais rápido jsperf.com/math-clamp/9
Manuel Di Iorio

1
@ManuelDiIorio huh? na versão atual do Chrome, o ternário simples é duas vezes mais rápido que Math.min / max - e se você remover a sobrecarga da Math.random()chamada muito mais cara , essa abordagem simples é 10 vezes mais rápida .
mindplay.dk


48

Atualização para o ECMAScript 2017:

Math.clamp(x, lower, upper)

Mas observe que, atualmente, é uma proposta do Estágio 1 . Até que ele seja amplamente suportado, você pode usar um polyfill .


4
Se você deseja acompanhar esta proposta e outras, consulte: github.com/tc39/proposals
gfullam

5
Não encontrei essa proposta no repositório tc39 / propostas
Colin D

Para quem procura, a proposta está listada nas propostas do Estágio 1 como "Extensões matemáticas". A proposta real está aqui: github.com/rwaldron/proposal-math-extensions
WickyNilliams

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

2
Esta solução é realmente muito mais rápida, embora eu considere estender o prototypemuito elegante quando se trata de realmente usar a função.
MaxArt

11

Isso não quer ser uma resposta "apenas use uma biblioteca", mas caso você esteja usando o Lodash, você pode usar .clamp:

_.clamp(yourInput, lowerBound, upperBound);

De modo a:

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

Aqui está sua implementação, extraída da 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;
}

Além disso, vale a pena notar que o Lodash disponibiliza métodos únicos como módulos autônomos; portanto, caso você precise apenas desse método, basta instalá-lo sem o restante da biblioteca:

npm i --save lodash.clamp

6

Se você conseguir usar as funções de seta es6, também poderá usar uma abordagem de aplicativo 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

A criação de uma função toda vez que você deseja fixar um número parece muito ineficiente
George

1
Provavelmente depende do seu caso de uso. Esta é apenas uma função de fábrica para criar o grampo com uma faixa definida. Você pode criar uma vez e reutilizar em todo o aplicativo. não precisa ser chamado várias vezes. Também pode não ser a ferramenta para todos os casos de uso em que você não precisa bloquear em um intervalo definido.
bingles 20/04

@ George, se você não precisar manter o intervalo, basta remover a função de seta interna e mover o parâmetro de valor para a função de seta externa
Alisson Nunes

6

Se você não deseja definir nenhuma função, escrevê-la como Math.min(Math.max(x, a), b)essa não é ruim.


0

No espírito da sensualidade das flechas, você pode criar um micro grampo / restrição / portão / & c. função usando parâmetros de descanso

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

Depois é só passar em três valores

clamp(100,-3,someVar);

Ou seja, novamente, se por sexy, você quer dizer 'curto'


você não deve usar matrizes e classificação por um clamp, perf vai tomar uma batida grave se você tem lógica complexa (mesmo que seja "sexy")
Andrew McOlash

0

Isso expande a opção ternária para if / else que minified é equivalente à opção ternária, mas não sacrifica a legibilidade.

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

Minimize para 35b (ou 43b se estiver usando function):

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

Além disso, dependendo de quais ferramentas de perf ou navegador você usa, você obtém vários resultados, se a implementação baseada em matemática ou a implementação ternária é mais rápida. No caso de aproximadamente o mesmo desempenho, eu optaria pela legibilidade.


-5

Meu favorito:

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

12
Voto negativo porque não funciona. [1,5,19].sort()[1]retorna 19. Poderia ser corrigido assim: [min,x,max].sort(function(a, b) { return a - b; })[1]mas isso não é mais sexy. Além de ser muito usado, pode ser um problema de desempenho criar uma nova matriz apenas para comparar três números.
kayahr

5
Dar +1 de qualquer maneira, porque sexy é o que conta.
Don escotilha

3
IMHO isso é muito mais legível do que qualquer uma das outras opções, especialmente com funções de seta:[min, x, max].sort((a,b) => a-b)[1]
Zaius

4
A razão pela qual isso nem sempre funciona é porque o método de classificação não compara valores numéricos corretamente. (Trata-los como cordas)
John Leuenhagen

Acho que não há nada mais sexy do que uma função com nome apropriado que faz o trabalho de forma eficiente e de uma maneira obviamente correta. Mas isso pode ser uma questão de gosto.
BallpointBen
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.