Was ist die eleganteste Art, eine Zahl einem Segment zuzuordnen?

127

Sagen wir x, aund bsind Zahlen. Ich muss mich xan die Grenzen des Segments halten [a, b].

Ich kann schreiben Math.max(a, Math.min(x, b)), aber ich denke nicht, dass es sehr einfach zu lesen ist. Hat jemand eine clevere Möglichkeit, dies besser lesbar zu schreiben?

Samuel Rossille
quelle

Antworten:

197

Die Art und Weise, wie Sie es tun, ist ziemlich normal. Sie können eine Dienstprogrammfunktion definieren clamp:

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

(Obwohl das Erweitern von Sprachintegrationen im Allgemeinen verpönt ist)

Otto Allmendinger
quelle
OOps Sie kopieren / fügten meinen Tippfehler ein (max und min getauscht)
Samuel Rossille
5
Nein, ich habe es von der Seite bekommen. Die Verschachtelung / Ausführungsreihenfolge Math.minund Math.maxist irrelevant , solange der Parameter Math.minist maxund der Parameter der Math.maxist min.
Otto Allmendinger
23
Die Erweiterung nativer Prototypen ( Number.prototypein diesem Fall) ist aus verschiedenen Gründen nicht ratsam. Siehe "Schlechte Praxis: Erweiterung nativer Prototypen" unter developer.mozilla.org/en-US/docs/Web/JavaScript/…
broofa
67

Ein weniger "mathematisch" orientierter Ansatz, der aber auch funktionieren sollte. Auf diese Weise wird der </> Test angezeigt (möglicherweise verständlicher als Minimaxing), aber es hängt wirklich davon ab, was Sie unter "lesbar" verstehen.

function clamp(num, min, max) {
  return num <= min ? min : num >= max ? max : num;
}
Dweeves
quelle
4
Es ist besser, <und> in dieser Redewendung durch <= und> = zu ersetzen, damit möglicherweise ein Vergleich weniger durchgeführt wird. Mit dieser Korrektur ist dies definitiv meine bevorzugte Antwort: Max. Minuten sind verwirrend und fehleranfällig.
Don Hatch
Dies ist die beste Antwort. Viel schneller, viel einfacher, viel lesbarer.
Tristan
5
Es ist nicht schneller. Math.min / max Lösung ist am schnellsten jsperf.com/math-clamp/9
Manuel Di Iorio
1
@ ManuelDiIorio nicht wahr? In der aktuellen Version von Chrome ist das einfache Ternär doppelt so schnell wie Math.min / max. Wenn Sie den Overhead aus dem weitaus teureren Math.random()Anruf entfernen , ist dieser einfache Ansatz zehnmal schneller .
mindplay.dk
47

Update für ECMAScript 2017:

Math.clamp(x, lower, upper)

Beachten Sie jedoch, dass es sich ab heute um einen Vorschlag der Stufe 1 handelt . Bis es weitgehend unterstützt wird, können Sie eine Polyfüllung verwenden .

Tomas Nikodym
quelle
4
Wenn Sie diesen Vorschlag und andere verfolgen möchten, sehen Sie: github.com/tc39/proposals
gfullam
5
Ich fand diesen Vorschlag nicht im tc39 / Vorschläge Repo
Colin D
Für diejenigen, die suchen, ist der Vorschlag unter Vorschläge der Stufe 1 als "Math Extensions" aufgeführt. Der eigentliche Vorschlag ist hier: github.com/rwaldron/proposal-math-extensions
WickyNilliams
13
Math.clip = function(number, min, max) {
  return Math.max(min, Math.min(number, max));
}
CAFxX
quelle
1
Diese Lösung ist tatsächlich viel schneller, obwohl ich erwäge, die prototypesehr elegante zu erweitern, wenn es darum geht, die Funktion tatsächlich zu nutzen.
MaxArt
11

Dies möchte keine "Just-Use-a-Library" -Antwort sein, aber nur für den Fall, dass Sie Lodash verwenden, können Sie Folgendes verwenden .clamp:

_.clamp(yourInput, lowerBound, upperBound);

Damit:

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

Hier ist die Implementierung aus der Lodash- Quelle :

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

Es ist auch erwähnenswert, dass Lodash einzelne Methoden als eigenständige Module zur Verfügung stellt. Wenn Sie also nur diese Methode benötigen, können Sie sie einfach ohne den Rest der Bibliothek installieren:

npm i --save lodash.clamp
Nobita
quelle
6

Wenn Sie es6-Pfeilfunktionen verwenden können, können Sie auch einen Teilanwendungsansatz verwenden:

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
quelle
Die Erstellung einer Funktion jedes Mal, wenn Sie eine Zahl klemmen möchten, sieht sehr ineffizient aus
George
1
Hängt wahrscheinlich von Ihrem Anwendungsfall ab. Dies ist nur eine Werksfunktion, um die Klemme mit einem festgelegten Bereich zu erstellen. Sie können alles einmal erstellen und in Ihrer App wiederverwenden. muss nicht mehrmals aufgerufen werden. Möglicherweise ist es auch nicht das Werkzeug für alle Anwendungsfälle, in denen Sie keinen festgelegten Bereich festlegen müssen.
Bingles
@George Wenn Sie den Bereich nicht einhalten müssen, entfernen Sie einfach die innere Pfeilfunktion und verschieben Sie den Wertparameter in die äußere Pfeilfunktion
Alisson Nunes
5

Wenn Sie keine Funktion definieren möchten, Math.min(Math.max(x, a), b)ist es gar nicht so schlecht , sie so zu schreiben .

Ricardo
quelle
0

Im Geiste der Pfeilsexiness könnten Sie eine Mikroklemme / Constrain / Gate / & c erstellen. Funktion mit Ruheparametern

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

Dann geben Sie einfach drei Werte ein

clamp(100,-3,someVar);

Das heißt, wenn mit sexy gemeint ist, meinst du "kurz"

Motor schüren
quelle
Sie sollten keine Arrays und Sortierungen für a verwenden clamp, perf wird einen ernsthaften Schlag
erleiden,
0

Dies erweitert die ternäre Option zu if / else, das minimiert der ternären Option entspricht, jedoch nicht die Lesbarkeit beeinträchtigt.

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

Minimiert auf 35b (oder 43b bei Verwendung function):

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

Abhängig davon, welches Perf-Tool oder welchen Browser Sie verwenden, erhalten Sie verschiedene Ergebnisse darüber, ob die mathematische oder die ternäre Implementierung schneller ist. Bei ungefähr gleicher Leistung würde ich mich für die Lesbarkeit entscheiden.

Michael Stramel
quelle
-5

Mein Favorit:

[min,x,max].sort()[1]
user947856
quelle
12
Downvoting, weil es nicht funktioniert. [1,5,19].sort()[1]gibt 19 zurück. Es könnte so behoben werden: [min,x,max].sort(function(a, b) { return a - b; })[1]aber das ist nicht mehr sexy. Abgesehen davon, dass es häufig verwendet wird, kann es ein Leistungsproblem sein, ein neues Array zu erstellen, um nur drei Zahlen zu vergleichen.
Kayahr
5
Geben Sie diese +1 trotzdem, denn sexy ist das, was zählt.
Don Hatch
3
IMHO ist dies weitaus besser lesbar als alle anderen Optionen, insbesondere mit [min, x, max].sort((a,b) => a-b)[1]
Pfeilfunktionen
4
Der Grund, warum dies nicht immer funktioniert, liegt darin, dass die Sortiermethode numerische Werte nicht richtig vergleicht. (Es behandelt sie wie Saiten)
John Leuenhagen
Ich denke, es gibt nichts Sexuelleres als eine treffend benannte Funktion, die die Arbeit effizient und auf offensichtlich korrekte Weise erledigt. Aber das kann Geschmackssache sein.
BallpointBen