Why has closure compiler changed 'this'? - javascript

In my compressed code, under advanced compilation, the compiler has changed the calling context of my function. I'm after some reasoning why and how, so I can figure out how to fix it.
Back story
I've generated my code into modules and for the past few days I've been converting the react js material ui library into a closure style provide/require syntax. I couldn't get CommonJS to play nicely with my modular approach and I couldn't get goog.module to work with the debug tool I use 'plovr'. Almost there but I'm stumbling with this.
My compiled code has sourcemaps so I can see where it's going wrong and it doesn't seem to make any sense to me.
The error throws here. Note that this is compressed code but you are seeing it mapped to the original code via sourcemaps. decomposeColor doesn't exist because this is equal to the window object.
If I type this into the console.
I then go one level up the stack and type this into the console and it's the correct object I would expect to see one level down.
Here's the same thing but what the actual code looks like compressed
Any idea what can cause the compiler to do this?
UPDATE:
After some pointers in the comments (Thanks Jan) it made sense what I should be looking for, it seems the compiler has converted from my object method
goog.provide('mui.utils.colorManipulator');
mui.utils.colorManipulator = {
//...
/**
* #this {mui.utils.colorManipulator}
*/
fade: function fade(color, amount) {
color = this._decomposeColor(color);
if (color.type === 'rgb' || color.type === 'hsl') color.type += 'a';
return this._convertColorToString(color, amount);
}
//...
}
into a function declared at the global scope.
function kc(f, a) {
f = this.nd(f);
if ("rgb" === f.type || "hsl" === f.type)
f.type += "a";
return this.md(f, a)
}
So the 'this' contexts will be different, I just need to figure out why the compiler would do that.
Update:
Here's all the code for the colorManipulator. It's pretty much ported from this this
goog.provide('mui.utils.colorManipulator')
mui.utils.colorManipulator = {
/**
* The relative brightness of any point in a colorspace, normalized to 0 for
* darkest black and 1 for lightest white. RGB colors only. Does not take
* into account alpha values.
*
* TODO:
* - Take into account alpha values.
* - Identify why there are minor discrepancies for some use cases
* (i.e. #F0F & #FFF). Note that these cases rarely occur.
*
* Formula: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
*/
_luminance(color) {
color = this._decomposeColor(color);
if (color.type.indexOf('rgb') > -1) {
let rgb = color.values.map((val) => {
val /= 255; // normalized
return val <= 0.03928 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4);
});
return 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2];
}
else {
let message = 'Calculating the relative luminance is not available for ' +
'HSL and HSLA.';
console.error(message);
return -1;
}
},
/**
* #params:
* additionalValue = An extra value that has been calculated but not included
* with the original color object, such as an alpha value.
*/
_convertColorToString(color, additonalValue) {
let str = color.type + '(' +
parseInt(color.values[0]) + ',' +
parseInt(color.values[1]) + ',' +
parseInt(color.values[2]);
if (additonalValue !== undefined) {
str += ',' + additonalValue + ')';
}
else if (color.values.length === 4) {
str += ',' + color.values[3] + ')';
}
else {
str += ')';
}
return str;
},
// Converts a color from hex format to rgb format.
_convertHexToRGB(color) {
if (color.length === 4) {
let extendedColor = '#';
for (let i = 1; i < color.length; i++) {
extendedColor += color.charAt(i) + color.charAt(i);
}
color = extendedColor;
}
let values = {
r: parseInt(color.substr(1,2), 16),
g: parseInt(color.substr(3,2), 16),
b: parseInt(color.substr(5,2), 16),
};
return 'rgb(' + values.r + ',' +
values.g + ',' +
values.b + ')';
},
// Returns the type and values of a color of any given type.
_decomposeColor(color) {
if (color.charAt(0) === '#') {
return this._decomposeColor(this._convertHexToRGB(color));
}
let marker = color.indexOf('(');
let type = color.substring(0, marker);
let values = color.substring(marker + 1, color.length - 1).split(',');
return {type: type, values: values};
},
// Set the absolute transparency of a color.
// Any existing alpha values are overwritten.
/**
* #this {mui.utils.colorManipulator}
*/
fade(color, amount) {
color = this._decomposeColor(color);
if (color.type === 'rgb' || color.type === 'hsl') color.type += 'a';
return this._convertColorToString(color, amount);
},
// Desaturates rgb and sets opacity to 0.15
lighten(color, amount) {
color = this._decomposeColor(color);
if (color.type.indexOf('hsl') > -1) {
color.values[2] += amount;
return this._decomposeColor(this._convertColorToString(color));
}
else if (color.type.indexOf('rgb') > -1) {
for (let i = 0; i < 3; i++) {
color.values[i] *= 1 + amount;
if (color.values[i] > 255) color.values[i] = 255;
}
}
if (color.type.indexOf('a') <= -1) color.type += 'a';
return this._convertColorToString(color, '0.15');
},
darken(color, amount) {
color = this._decomposeColor(color);
if (color.type.indexOf('hsl') > -1) {
color.values[2] += amount;
return this._decomposeColor(this._convertColorToString(color));
}
else if (color.type.indexOf('rgb') > -1) {
for (let i = 0; i < 3; i++) {
color.values[i] *= 1 - amount;
if (color.values[i] < 0) color.values[i] = 0;
}
}
return this._convertColorToString(color);
},
// Calculates the contrast ratio between two colors.
//
// Formula: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
contrastRatio(background, foreground) {
let lumA = this._luminance(background);
let lumB = this._luminance(foreground);
if (lumA >= lumB) {
return ((lumA + 0.05) / (lumB + 0.05)).toFixed(2);
}
else {
return ((lumB + 0.05) / (lumA + 0.05)).toFixed(2);
}
},
/**
* Determines how readable a color combination is based on its level.
* Levels are defined from #LeaVerou:
* https://github.com/LeaVerou/contrast-ratio/blob/gh-pages/contrast-ratio.js
*/
contrastRatioLevel(background, foreground) {
let levels = {
'fail': {
range: [0, 3],
color: 'hsl(0, 100%, 40%)',
},
'aa-large': {
range: [3, 4.5],
color: 'hsl(40, 100%, 45%)',
},
'aa': {
range: [4.5, 7],
color: 'hsl(80, 60%, 45%)',
},
'aaa': {
range: [7, 22],
color: 'hsl(95, 60%, 41%)',
},
};
let ratio = this.contrastRatio(background, foreground);
for (let level in levels) {
let range = levels[level].range;
if (ratio >= range[0] && ratio <= range[1]) return level;
}
},
};

Implications of object property flattening
In Advanced mode the Compiler collapses object properties to prepare
for name shortening. For example, the Compiler transforms this:
var foo = {}; foo.bar = function (a) { alert(a) };
foo.bar("hello"); into this:
var foo$bar = function (a) { alert(a) }; foo$bar("hello"); This
property flattening allows the later renaming pass to rename more
efficiently. The Compiler can replace foo$bar with a single character,
for example.
But property flattening also makes the following practice dangerous:
Using this outside of constructors and prototype methods:
Property flattening can change meaning of the keyword this within a
function. For example:
var foo = {}; foo.bar = function (a) { this.bad = a; }; // BAD
foo.bar("hello"); becomes:
var foo$bar = function (a) { this.bad = a; }; foo$bar("hello");
Before the transformation, the this within foo.bar refers to foo.
After the transformation, this refers to the global this. In cases
like this one the Compiler produces this warning:
"WARNING - dangerous use of this in static method foo.bar" To prevent
property flattening from breaking your references to this, only use
this within constructors and prototype methods. The meaning of this is
unambiguous when you call a constructor with the new keyword, or
within a function that is a property of a prototype.
Source: https://developers.google.com/closure/compiler/docs/limitations?hl=en#implications-of-object-property-flattening]
In other words, the closure compiler does not support the use of this in object literals because of property flattening.
Therefore, a simple solution is to reference the full namespace of the object whose property you are trying to access.
mui.utils.colorManipulator.name_of_function(args);

this shouldn't be really used with object literals as they can be considered like static functions off a namespace. this should be used with functions that are invoked with .bind, .call, .apply or an instance created with a constructor and new. Supposedly it is supported, but closure must not fully understand what is going on: How does "this" keyword work within a function?
I would change the code to refer to the other functions off of mui.utils directly.

fade in your code is a method on an object, and in the compressed version it's not, hence the reference to this is changed from the method's object to the global object.
An alternative to solve this would be using the new function singleton pattern to instantiate your object.
mui.utils.colorManipulator = new function() {
this.fade = function() { /*...*/ }
};
Either that or like jfriend00 suggested, specify the full namespace inside your methods so that the reference isn't lost on the compiler.

Related

Is there a way to add or subtract object in JavaScript? [duplicate]

I've been working with JavaScript for a few days now and have got to a point where I want to overload operators for my defined objects.
After a stint on google searching for this it seems you can't officially do this, yet there are a few people out there claiming some long-winded way of performing this action.
Basically I've made a Vector2 class and want to be able to do the following:
var x = new Vector2(10,10);
var y = new Vector2(10,10);
x += y; //This does not result in x being a vector with 20,20 as its x & y values.
Instead I'm having to do this:
var x = new Vector2(10,10);
var y = new Vector2(10,10);
x = x.add(y); //This results in x being a vector with 20,20 as its x & y values.
Is there an approach I can take to overload operators in my Vector2 class? As this just looks plain ugly.
As you've found, JavaScript doesn't support operator overloading. The closest you can come is to implement toString (which will get called when the instance needs to be coerced to being a string) and valueOf (which will get called to coerce it to a number, for instance when using + for addition, or in many cases when using it for concatenation because + tries to do addition before concatenation), which is pretty limited. Neither lets you create a Vector2 object as a result. Similarly, Proxy (added in ES2015) lets you intercept various object operations (including property access), but again won't let you control the result of += on Vector instances.
For people coming to this question who want a string or number as a result (instead of a Vector2), though, here are examples of valueOf and toString. These examples do not demonstrate operator overloading, just taking advantage of JavaScript's built-in handling converting to primitives:
valueOf
This example doubles the value of an object's val property in response to being coerced to a primitive, for instance via +:
function Thing(val) {
this.val = val;
}
Thing.prototype.valueOf = function() {
// Here I'm just doubling it; you'd actually do your longAdd thing
return this.val * 2;
};
var a = new Thing(1);
var b = new Thing(2);
console.log(a + b); // 6 (1 * 2 + 2 * 2)
Or with ES2015's class:
class Thing {
constructor(val) {
this.val = val;
}
valueOf() {
return this.val * 2;
}
}
const a = new Thing(1);
const b = new Thing(2);
console.log(a + b); // 6 (1 * 2 + 2 * 2)
Or just with objects, no constructors:
var thingPrototype = {
valueOf: function() {
return this.val * 2;
}
};
var a = Object.create(thingPrototype);
a.val = 1;
var b = Object.create(thingPrototype);
b.val = 2;
console.log(a + b); // 6 (1 * 2 + 2 * 2)
toString
This example converts the value of an object's val property to upper case in response to being coerced to a primitive, for instance via +:
function Thing(val) {
this.val = val;
}
Thing.prototype.toString = function() {
return this.val.toUpperCase();
};
var a = new Thing("a");
var b = new Thing("b");
console.log(a + b); // AB
Or with ES2015's class:
class Thing {
constructor(val) {
this.val = val;
}
toString() {
return this.val.toUpperCase();
}
}
const a = new Thing("a");
const b = new Thing("b");
console.log(a + b); // AB
Or just with objects, no constructors:
var thingPrototype = {
toString: function() {
return this.val.toUpperCase();
}
};
var a = Object.create(thingPrototype);
a.val = "a";
var b = Object.create(thingPrototype);
b.val = "b";
console.log(a + b); // AB
As T.J. said, you cannot overload operators in JavaScript. However you can take advantage of the valueOf function to write a hack which looks better than using functions like add every time, but imposes the constraints on the vector that the x and y are between 0 and MAX_VALUE. Here is the code:
var MAX_VALUE = 1000000;
var Vector = function(a, b) {
var self = this;
//initialize the vector based on parameters
if (typeof(b) == "undefined") {
//if the b value is not passed in, assume a is the hash of a vector
self.y = a % MAX_VALUE;
self.x = (a - self.y) / MAX_VALUE;
} else {
//if b value is passed in, assume the x and the y coordinates are the constructors
self.x = a;
self.y = b;
}
//return a hash of the vector
this.valueOf = function() {
return self.x * MAX_VALUE + self.y;
};
};
var V = function(a, b) {
return new Vector(a, b);
};
Then you can write equations like this:
var a = V(1, 2); //a -> [1, 2]
var b = V(2, 4); //b -> [2, 4]
var c = V((2 * a + b) / 2); //c -> [2, 4]
It's possible to do vector math with two numbers packed into one. Let me first show an example before I explain how it works:
let a = vec_pack([2,4]);
let b = vec_pack([1,2]);
let c = a+b; // Vector addition
let d = c-b; // Vector subtraction
let e = d*2; // Scalar multiplication
let f = e/2; // Scalar division
console.log(vec_unpack(c)); // [3, 6]
console.log(vec_unpack(d)); // [2, 4]
console.log(vec_unpack(e)); // [4, 8]
console.log(vec_unpack(f)); // [2, 4]
if(a === f) console.log("Equality works");
if(a > b) console.log("Y value takes priority");
I am using the fact that if you bit shift two numbers X times and then add or subtract them before shifting them back, you will get the same result as if you hadn't shifted them to begin with. Similarly scalar multiplication and division works symmetrically for shifted values.
A JavaScript number has 52 bits of integer precision (64 bit floats), so I will pack one number into he higher available 26 bits, and one into the lower. The code is made a bit more messy because I wanted to support signed numbers.
function vec_pack(vec){
return vec[1] * 67108864 + (vec[0] < 0 ? 33554432 | vec[0] : vec[0]);
}
function vec_unpack(number){
switch(((number & 33554432) !== 0) * 1 + (number < 0) * 2){
case(0):
return [(number % 33554432),Math.trunc(number / 67108864)];
break;
case(1):
return [(number % 33554432)-33554432,Math.trunc(number / 67108864)+1];
break;
case(2):
return [(((number+33554432) % 33554432) + 33554432) % 33554432,Math.round(number / 67108864)];
break;
case(3):
return [(number % 33554432),Math.trunc(number / 67108864)];
break;
}
}
The only downside I can see with this is that the x and y has to be in the range +-33 million, since they have to fit within 26 bits each.
Actually, there is one variant of JavaScript that does support operator overloading. ExtendScript, the scripting language used by Adobe applications such as Photoshop and Illustrator, does have operator overloading. In it, you can write:
Vector2.prototype["+"] = function( b )
{
return new Vector2( this.x + b.x, this.y + b.y );
}
var a = new Vector2(1,1);
var b = new Vector2(2,2);
var c = a + b;
This is described in more detail in the "Adobe Extendscript JavaScript tools guide" (current link here). The syntax was apparently based on a (now long abandoned) draft of the ECMAScript standard.
FYI paper.js solves this issue by creating PaperScript, a self-contained, scoped javascript with operator overloading of vectors, which it then processing back into javascript.
But the paperscript files need to be specifically specified and processed as such.
We can use React-like Hooks to evaluate arrow function with different values from valueOf method on each iteration.
const a = Vector2(1, 2) // [1, 2]
const b = Vector2(2, 4) // [2, 4]
const c = Vector2(() => (2 * a + b) / 2) // [2, 4]
// There arrow function will iterate twice
// 1 iteration: method valueOf return X component
// 2 iteration: method valueOf return Y component
const Vector2 = (function() {
let index = -1
return function(x, y) {
if (typeof x === 'function') {
const calc = x
index = 0, x = calc()
index = 1, y = calc()
index = -1
}
return Object.assign([x, y], {
valueOf() {
return index == -1 ? this.toString() : this[index]
},
toString() {
return `[${this[0]}, ${this[1]}]`
},
len() {
return Math.sqrt(this[0] ** 2 + this[1] ** 2)
}
})
}
})()
const a = Vector2(1, 2)
const b = Vector2(2, 4)
console.log('a = ' + a) // a = [1, 2]
console.log(`b = ${b}`) // b = [2, 4]
const c = Vector2(() => (2 * a + b) / 2) // [2, 4]
a[0] = 12
const d = Vector2(() => (2 * a + b) / 2) // [13, 4]
const normalized = Vector2(() => d / d.len()) // [0.955..., 0.294...]
console.log(c, d, normalized)
Library #js-basics/vector uses the same idea for Vector3.
I wrote a library that exploits a bunch of evil hacks to do it in raw JS. It allows expressions like these.
Complex numbers:
>> Complex()({r: 2, i: 0} / {r: 1, i: 1} + {r: -3, i: 2}))
<- {r: -2, i: 1}
Automatic differentiation:
Let f(x) = x^3 - 5x:
>> var f = x => Dual()(x * x * x - {x:5, dx:0} * x);
Now map it over some values:
>> [-2,-1,0,1,2].map(a=>({x:a,dx:1})).map(f).map(a=>a.dx)
<- [ 7, -2, -5, -2, 7 ]
i.e. f'(x) = 3x^2 - 5.
Polynomials:
>> Poly()([1,-2,3,-4]*[5,-6]).map((c,p)=>''+c+'x^'+p).join(' + ')
<- "5x^0 + -16x^1 + 27x^2 + -38x^3 + 24x^4"
For your particular problem, you would define a Vector2 function (or maybe something shorter) using the library, then write x = Vector2()(x + y);
https://gist.github.com/pyrocto/5a068100abd5ff6dfbe69a73bbc510d7
Whilst not an exact answer to the question, it is possible to implement some of the python __magic__ methods using ES6 Symbols
A [Symbol.toPrimitive]() method doesn't let you imply a call Vector.add(), but will let you use syntax such as Decimal() + int.
class AnswerToLifeAndUniverseAndEverything {
[Symbol.toPrimitive](hint) {
if (hint === 'string') {
return 'Like, 42, man';
} else if (hint === 'number') {
return 42;
} else {
// when pushed, most classes (except Date)
// default to returning a number primitive
return 42;
}
}
}
https://www.keithcirkel.co.uk/metaprogramming-in-es6-symbols/
Interesting is also experimental library operator-overloading-js . It does overloading in a defined context (callback function) only.

Alternative way to padStart [duplicate]

I am in need of a JavaScript function which can take a value and pad it to a given length (I need spaces, but anything would do). I found this, but I have no idea what the heck it is doing and it doesn't seem to work for me.
String.prototype.pad = function(l, s, t) {
return s || (s = " "),
(l -= this.length) > 0 ?
(s = new Array(Math.ceil(l / s.length) + 1).join(s))
.substr(0, t = !t ? l : t == 1 ?
0 :
Math.ceil(l / 2)) + this + s.substr(0, l - t) :
this;
};
var s = "Jonas";
document.write(
'<h2>S = '.bold(), s, "</h2>",
'S.pad(20, "[]", 0) = '.bold(), s.pad(20, "[]", 0), "<br />",
'S.pad(20, "[====]", 1) = '.bold(), s.pad(20, "[====]", 1), "<br />",
'S.pad(20, "~", 2) = '.bold(), s.pad(20, "~", 2)
);
ECMAScript 2017 (ES8) added String.padStart (along with String.padEnd) for just this purpose:
"Jonas".padStart(10); // Default pad string is a space
"42".padStart(6, "0"); // Pad with "0"
"*".padStart(8, "-/|\\"); // produces '-/|\\-/|*'
If not present in the JavaScript host, String.padStart can be added as a polyfill.
Pre ES8
I found this solution here and this is for me much much simpler:
var n = 123
String("00000" + n).slice(-5); // returns 00123
("00000" + n).slice(-5); // returns 00123
(" " + n).slice(-5); // returns " 123" (with two spaces)
And here I made an extension to the string object:
String.prototype.paddingLeft = function (paddingValue) {
return String(paddingValue + this).slice(-paddingValue.length);
};
An example to use it:
function getFormattedTime(date) {
var hours = date.getHours();
var minutes = date.getMinutes();
hours = hours.toString().paddingLeft("00");
minutes = minutes.toString().paddingLeft("00");
return "{0}:{1}".format(hours, minutes);
};
String.prototype.format = function () {
var args = arguments;
return this.replace(/{(\d+)}/g, function (match, number) {
return typeof args[number] != 'undefined' ? args[number] : match;
});
};
This will return a time in the format "15:30".
A faster method
If you are doing this repeatedly, for example to pad values in an array, and performance is a factor, the following approach can give you nearly a 100x advantage in speed (jsPerf) over other solution that are currently discussed on the inter webs. The basic idea is that you are providing the pad function with a fully padded empty string to use as a buffer. The pad function just appends to string to be added to this pre-padded string (one string concat) and then slices or trims the result to the desired length.
function pad(pad, str, padLeft) {
if (typeof str === 'undefined')
return pad;
if (padLeft) {
return (pad + str).slice(-pad.length);
} else {
return (str + pad).substring(0, pad.length);
}
}
For example, to zero pad a number to a length of 10 digits,
pad('0000000000',123,true);
To pad a string with whitespace, so the entire string is 255 characters,
var padding = Array(256).join(' '), // make a string of 255 spaces
pad(padding,123,true);
Performance Test
See the jsPerf test here.
And this is faster than ES6 string.repeat by 2x as well, as shown by the revised JsPerf here
Please note that jsPerf is no longer online
Please note that the jsPerf site that we originally used to benchmark the various methods is no longer online. Unfortunately, this means we can't get to those test results. Sad but true.
String.prototype.padStart() and String.prototype.padEnd() are currently TC39 candidate proposals: see github.com/tc39/proposal-string-pad-start-end (only available in Firefox as of April 2016; a polyfill is available).
http://www.webtoolkit.info/javascript_pad.html
/**
*
* JavaScript string pad
* http://www.webtoolkit.info/
*
**/
var STR_PAD_LEFT = 1;
var STR_PAD_RIGHT = 2;
var STR_PAD_BOTH = 3;
function pad(str, len, pad, dir) {
if (typeof(len) == "undefined") { var len = 0; }
if (typeof(pad) == "undefined") { var pad = ' '; }
if (typeof(dir) == "undefined") { var dir = STR_PAD_RIGHT; }
if (len + 1 >= str.length) {
switch (dir){
case STR_PAD_LEFT:
str = Array(len + 1 - str.length).join(pad) + str;
break;
case STR_PAD_BOTH:
var padlen = len - str.length;
var right = Math.ceil( padlen / 2 );
var left = padlen - right;
str = Array(left+1).join(pad) + str + Array(right+1).join(pad);
break;
default:
str = str + Array(len + 1 - str.length).join(pad);
break;
} // switch
}
return str;
}
It's a lot more readable.
Here's a recursive approach to it.
function pad(width, string, padding) {
return (width <= string.length) ? string : pad(width, padding + string, padding)
}
An example...
pad(5, 'hi', '0')
=> "000hi"
ECMAScript 2017 adds a padStart method to the String prototype. This method will pad a string with spaces to a given length. This method also takes an optional string that will be used instead of spaces for padding.
'abc'.padStart(10); // " abc"
'abc'.padStart(10, "foo"); // "foofoofabc"
'abc'.padStart(6,"123465"); // "123abc"
'abc'.padStart(8, "0"); // "00000abc"
'abc'.padStart(1); // "abc"
A padEnd method was also added that works in the same manner.
For browser compatibility (and a useful polyfill) see this link.
Using the ECMAScript 6 method String#repeat, a pad function is as simple as:
String.prototype.padLeft = function(char, length) {
return char.repeat(Math.max(0, length - this.length)) + this;
}
String#repeat is currently supported in Firefox and Chrome only. for other implementation, one might consider the following simple polyfill:
String.prototype.repeat = String.prototype.repeat || function(n){
return n<=1 ? this : (this + this.repeat(n-1));
}
Using the ECMAScript 6 method String#repeat and Arrow functions, a pad function is as simple as:
var leftPad = (s, c, n) => c.repeat(n - s.length) + s;
leftPad("foo", "0", 5); //returns "00foo"
jsfiddle
edit:
suggestion from the comments:
const leftPad = (s, c, n) => n - s.length > 0 ? c.repeat(n - s.length) + s : s;
this way, it wont throw an error when s.lengthis greater than n
edit2:
suggestion from the comments:
const leftPad = (s, c, n) =>{ s = s.toString(); c = c.toString(); return s.length > n ? s : c.repeat(n - s.length) + s; }
this way, you can use the function for strings and non-strings alike.
The key trick in both those solutions is to create an array instance with a given size (one more than the desired length), and then to immediately call the join() method to make a string. The join() method is passed the padding string (spaces probably). Since the array is empty, the empty cells will be rendered as empty strings during the process of joining the array into one result string, and only the padding will remain. It's a really nice technique.
With ES8, there are two options for padding.
You can check them in the documentation.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
Taking up Samuel's ideas, upward here. And remember an old SQL script, I tried with this:
a=1234;
'0000'.slice(a.toString().length)+a;
It works in all the cases I could imagine:
a= 1 result 0001
a= 12 result 0012
a= 123 result 0123
a= 1234 result 1234
a= 12345 result 12345
a= '12' result 0012
Pad with default values
I noticed that I mostly need the padLeft for time conversion / number padding.
So I wrote this function:
function padL(a, b, c) { // string/number, length=2, char=0
return (new Array(b || 2).join(c || 0) + a).slice(-b)
}
This simple function supports Number or String as input.
The default pad is two characters.
The default char is 0.
So I can simply write:
padL(1);
// 01
If I add the second argument (pad width):
padL(1, 3);
// 001
The third parameter (pad character)
padL('zzz', 10, 'x');
// xxxxxxxzzz
#BananaAcid: If you pass a undefined value or a 0 length string, you get 0undefined, so:
As suggested
function padL(a, b, c) { // string/number, length=2, char=0
return (new Array((b || 1) + 1).join(c || 0) + (a || '')).slice(-(b || 2))
}
But this can also be achieved in a shorter way.
function padL(a, b, c) { // string/number, length=2, char=0
return (new Array(b || 2).join(c || 0) + (a || c || 0)).slice(-b)
}
It also works with:
padL(0)
padL(NaN)
padL('')
padL(undefined)
padL(false)
And if you want to be able to pad in both ways:
function pad(a, b, c, d) { // string/number, length=2, char=0, 0/false=Left-1/true=Right
return a = (a || c || 0), c = new Array(b || 2).join(c || 0), d ? (a + c).slice(0, b) : (c + a).slice(-b)
}
which can be written in a shorter way without using slice.
function pad(a, b, c, d) {
return a = (a || c || 0) + '', b = new Array((++b || 3) - a.length).join(c || 0), d ? a+b : b+a
}
/*
Usage:
pad(
input // (int or string) or undefined, NaN, false, empty string
// default:0 or PadCharacter
// Optional
,PadLength // (int) default:2
,PadCharacter // (string or int) default:'0'
,PadDirection // (bolean) default:0 (padLeft) - (true or 1) is padRight
)
*/
Now if you try to pad 'averylongword' with 2... that’s not my problem.
I said that I would give you a tip.
Most of the time, if you pad, you do it for the same value N times.
Using any type of function inside a loop slows down the loop!!!
So if you just want to pad left some numbers inside a long list, don't use functions to do this simple thing.
Use something like this:
var arrayOfNumbers = [1, 2, 3, 4, 5, 6, 7],
paddedArray = [],
len = arrayOfNumbers.length;
while(len--) {
paddedArray[len] = ('0000' + arrayOfNumbers[len]).slice(-4);
}
If you don't know how the maximum padding size based on the numbers inside the array.
var arrayOfNumbers = [1, 2, 3, 4, 5, 6, 7, 49095],
paddedArray = [],
len = arrayOfNumbers.length;
// Search the highest number
var arrayMax = Function.prototype.apply.bind(Math.max, null),
// Get that string length
padSize = (arrayMax(arrayOfNumbers) + '').length,
// Create a Padding string
padStr = new Array(padSize).join(0);
// And after you have all this static values cached start the loop.
while(len--) {
paddedArray[len] = (padStr + arrayOfNumbers[len]).slice(-padSize); // substr(-padSize)
}
console.log(paddedArray);
/*
0: "00001"
1: "00002"
2: "00003"
3: "00004"
4: "00005"
5: "00006"
6: "00007"
7: "49095"
*/
padding string has been inplemented in new javascript version.
str.padStart(targetLength [, padString])
https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Objetos_globales/String/padStart
If you want your own function check this example:
const myString = 'Welcome to my house';
String.prototype.padLeft = function(times = 0, str = ' ') {
return (Array(times).join(str) + this);
}
console.log(myString.padLeft(12, ':'));
//:::::::::::Welcome to my house
Here is a build in method you can use -
str1.padStart(2, '0')
Here's a simple function that I use.
var pad=function(num,field){
var n = '' + num;
var w = n.length;
var l = field.length;
var pad = w < l ? l-w : 0;
return field.substr(0,pad) + n;
};
For example:
pad (20,' '); // 20
pad (321,' '); // 321
pad (12345,' '); //12345
pad ( 15,'00000'); //00015
pad ( 999,'*****'); //**999
pad ('cat','_____'); //__cat
A short way:
(x=>(new Array(int-x.length+1)).join(char)+x)(String)
Example:
(x=>(new Array(6-x.length+1)).join("0")+x)("1234")
return: "001234"
Here is a simple answer in basically one line of code.
var value = 35 // the numerical value
var x = 5 // the minimum length of the string
var padded = ("00000" + value).substr(-x);
Make sure the number of characters in you padding, zeros here, is at least as many as your intended minimum length. So really, to put it into one line, to get a result of "00035" in this case is:
var padded = ("00000" + 35).substr(-5);
ES7 is just drafts and proposals right now, but if you wanted to track compatibility with the specification, your pad functions need:
Multi-character pad support.
Don't truncate the input string
Pad defaults to space
From my polyfill library, but apply your own due diligence for prototype extensions.
// Tests
'hello'.lpad(4) === 'hello'
'hello'.rpad(4) === 'hello'
'hello'.lpad(10) === ' hello'
'hello'.rpad(10) === 'hello '
'hello'.lpad(10, '1234') === '41234hello'
'hello'.rpad(10, '1234') === 'hello12341'
String.prototype.lpad || (String.prototype.lpad = function(length, pad)
{
if(length < this.length)
return this;
pad = pad || ' ';
let str = this;
while(str.length < length)
{
str = pad + str;
}
return str.substr( -length );
});
String.prototype.rpad || (String.prototype.rpad = function(length, pad)
{
if(length < this.length)
return this;
pad = pad || ' ';
let str = this;
while(str.length < length)
{
str += pad;
}
return str.substr(0, length);
});
Array manipulations are really slow compared to simple string concat. Of course, benchmark for your use case.
function(string, length, pad_char, append) {
string = string.toString();
length = parseInt(length) || 1;
pad_char = pad_char || ' ';
while (string.length < length) {
string = append ? string+pad_char : pad_char+string;
}
return string;
};
A variant of #Daniel LaFavers' answer.
var mask = function (background, foreground) {
bg = (new String(background));
fg = (new String(foreground));
bgl = bg.length;
fgl = fg.length;
bgs = bg.substring(0, Math.max(0, bgl - fgl));
fgs = fg.substring(Math.max(0, fgl - bgl));
return bgs + fgs;
};
For example:
mask('00000', 11 ); // '00011'
mask('00011','00' ); // '00000'
mask( 2 , 3 ); // '3'
mask('0' ,'111'); // '1'
mask('fork' ,'***'); // 'f***'
mask('_____','dog'); // '__dog'
If you don't mind including a utility library, lodash library has _.pad, _.padLeft and _.padRight functions.
I think its better to avoid recursion because its costly.
function padLeft(str,size,padwith) {
if(size <= str.length) {
// not padding is required.
return str;
} else {
// 1- take array of size equal to number of padding char + 1. suppose if string is 55 and we want 00055 it means we have 3 padding char so array size should be 3 + 1 (+1 will explain below)
// 2- now join this array with provided padding char (padwith) or default one ('0'). so it will produce '000'
// 3- now append '000' with orginal string (str = 55), will produce 00055
// why +1 in size of array?
// it is a trick, that we are joining an array of empty element with '0' (in our case)
// if we want to join items with '0' then we should have at least 2 items in the array to get joined (array with single item doesn't need to get joined).
// <item>0<item>0<item>0<item> to get 3 zero we need 4 (3+1) items in array
return Array(size-str.length+1).join(padwith||'0')+str
}
}
alert(padLeft("59",5) + "\n" +
padLeft("659",5) + "\n" +
padLeft("5919",5) + "\n" +
padLeft("59879",5) + "\n" +
padLeft("5437899",5));
It's 2014, and I suggest a JavaScript string-padding function. Ha!
Bare-bones: right-pad with spaces
function pad (str, length) {
var padding = (new Array(Math.max(length - str.length + 1, 0))).join(" ");
return str + padding;
}
Fancy: pad with options
/**
* #param {*} str Input string, or any other type (will be converted to string)
* #param {number} length Desired length to pad the string to
* #param {Object} [opts]
* #param {string} [opts.padWith=" "] Character to use for padding
* #param {boolean} [opts.padLeft=false] Whether to pad on the left
* #param {boolean} [opts.collapseEmpty=false] Whether to return an empty string if the input was empty
* #returns {string}
*/
function pad(str, length, opts) {
var padding = (new Array(Math.max(length - (str + "").length + 1, 0))).join(opts && opts.padWith || " "),
collapse = opts && opts.collapseEmpty && !(str + "").length;
return collapse ? "" : opts && opts.padLeft ? padding + str : str + padding;
}
Usage (fancy):
pad("123", 5);
// Returns "123 "
pad(123, 5);
// Returns "123 " - non-string input
pad("123", 5, { padWith: "0", padLeft: true });
// Returns "00123"
pad("", 5);
// Returns " "
pad("", 5, { collapseEmpty: true });
// Returns ""
pad("1234567", 5);
// Returns "1234567"
/**************************************************************************************************
Pad a string to pad_length fillig it with pad_char.
By default the function performs a left pad, unless pad_right is set to true.
If the value of pad_length is negative, less than, or equal to the length of the input string, no padding takes place.
**************************************************************************************************/
if(!String.prototype.pad)
String.prototype.pad = function(pad_char, pad_length, pad_right)
{
var result = this;
if( (typeof pad_char === 'string') && (pad_char.length === 1) && (pad_length > this.length) )
{
var padding = new Array(pad_length - this.length + 1).join(pad_char); //thanks to http://stackoverflow.com/questions/202605/repeat-string-javascript/2433358#2433358
result = (pad_right ? result + padding : padding + result);
}
return result;
}
And then you can do:
alert( "3".pad("0", 3) ); //shows "003"
alert( "hi".pad(" ", 3) ); //shows " hi"
alert( "hi".pad(" ", 3, true) ); //shows "hi "
If you just want a very simple hacky one-liner to pad, just make a string of the desired padding character of the desired max padding length and then substring it to the length of what you want to pad.
Example: padding the string store in e with spaces to 25 characters long.
var e = "hello"; e = e + " ".substring(e.length)
Result: "hello "
If you want to do the same with a number as input just call .toString() on it before.
A friend asked about using a JavaScript function to pad left. It turned into a little bit of an endeavor between some of us in chat to code golf it. This was the result:
function l(p,t,v){
v+="";return v.length>=t?v:l(p,t,p+v);
}
It ensures that the value to be padded is a string, and then if it isn't the length of the total desired length it will pad it once and then recurse. Here is what it looks like with more logical naming and structure
function padLeft(pad, totalLength, value){
value = value.toString();
if( value.length >= totalLength ){
return value;
}else{
return padLeft(pad, totalLength, pad + value);
}
}
The example we were using was to ensure that numbers were padded with 0 to the left to make a max length of 6. Here is an example set:
function l(p,t,v){v+="";return v.length>=t?v:l(p,t,p+v);}
var vals = [6451,123,466750];
var pad = l(0,6,vals[0]);// pad with 0's, max length 6
var pads = vals.map(function(i){ return l(0,6,i) });
document.write(pads.join("<br />"));
A little late, but thought I might share anyway. I found it useful to add a prototype extension to Object. That way I can pad numbers and strings, left or right. I have a module with similar utilities I include in my scripts.
// include the module in your script, there is no need to export
var jsAddOns = require('<path to module>/jsAddOns');
~~~~~~~~~~~~ jsAddOns.js ~~~~~~~~~~~~
/*
* method prototype for any Object to pad it's toString()
* representation with additional characters to the specified length
*
* #param padToLength required int
* entire length of padded string (original + padding)
* #param padChar optional char
* character to use for padding, default is white space
* #param padLeft optional boolean
* if true padding added to left
* if omitted or false, padding added to right
*
* #return padded string or
* original string if length is >= padToLength
*/
Object.prototype.pad = function(padToLength, padChar, padLeft) {
// get the string value
s = this.toString()
// default padToLength to 0
// if omitted, original string is returned
padToLength = padToLength || 0;
// default padChar to empty space
padChar = padChar || ' ';
// ignore padding if string too long
if (s.length >= padToLength) {
return s;
}
// create the pad of appropriate length
var pad = Array(padToLength - s.length).join(padChar);
// add pad to right or left side
if (padLeft) {
return pad + s;
} else {
return s + pad;
}
};
Never insert data somewhere (especially not at beginning, like str = pad + str;), since the data will be reallocated everytime. Append always at end!
Don't pad your string in the loop. Leave it alone and build your pad string first. In the end concatenate it with your main string.
Don't assign padding string each time (like str += pad;). It is much faster to append the padding string to itself and extract first x-chars (the parser can do this efficiently if you extract from first char). This is exponential growth, which means that it wastes some memory temporarily (you should not do this with extremely huge texts).
if (!String.prototype.lpad) {
String.prototype.lpad = function(pad, len) {
while (pad.length < len) {
pad += pad;
}
return pad.substr(0, len-this.length) + this;
}
}
if (!String.prototype.rpad) {
String.prototype.rpad = function(pad, len) {
while (pad.length < len) {
pad += pad;
}
return this + pad.substr(0, len-this.length);
}
}
Here is a JavaScript function that adds a specified number of paddings with a custom symbol. The function takes three parameters.
padMe --> string or number to left pad
pads --> number of pads
padSymble --> custom symbol, default is "0"
function leftPad(padMe, pads, padSymble) {
if(typeof padMe === "undefined") {
padMe = "";
}
if (typeof pads === "undefined") {
pads = 0;
}
if (typeof padSymble === "undefined") {
padSymble = "0";
}
var symble = "";
var result = [];
for(var i=0; i < pads; i++) {
symble += padSymble;
}
var length = symble.length - padMe.toString().length;
result = symble.substring(0, length);
return result.concat(padMe.toString());
}
Here are some results:
> leftPad(1)
"1"
> leftPad(1, 4)
"0001"
> leftPad(1, 4, "0")
"0001"
> leftPad(1, 4, "#")
"###1"
Yet another take at with combination of a couple of solutions:
/**
* pad string on left
* #param {number} number of digits to pad, default is 2
* #param {string} string to use for padding, default is '0' *
* #returns {string} padded string
*/
String.prototype.paddingLeft = function (b, c) {
if (this.length > (b||2))
return this + '';
return (this || c || 0) + '', b = new Array((++b || 3) - this.length).join(c || 0), b + this
};
/**
* pad string on right
* #param {number} number of digits to pad, default is 2
* #param {string} string to use for padding, default is '0' *
* #returns {string} padded string
*/
String.prototype.paddingRight = function (b, c) {
if (this.length > (b||2))
return this + '';
return (this||c||0) + '', b = new Array((++b || 3) - this.length).join(c || 0), this + b
};

How to get median and quartiles/percentiles of an array in JavaScript (or PHP)?

This question is turned into a Q&A, because I had struggle finding the answer, and think it can be useful for others
I have a JavaScript array of values and need to calculate in JavaScript its Q2 (50th percentile aka MEDIAN), Q1 (25th percentile) and Q3 (75th percentile) values.
I updated the JavaScript translation from the first answer to use arrow functions and a bit more concise notation. The functionality remains mostly the same, except for std, which now computes the sample standard deviation (dividing by arr.length - 1 instead of just arr.length)
// sort array ascending
const asc = arr => arr.sort((a, b) => a - b);
const sum = arr => arr.reduce((a, b) => a + b, 0);
const mean = arr => sum(arr) / arr.length;
// sample standard deviation
const std = (arr) => {
const mu = mean(arr);
const diffArr = arr.map(a => (a - mu) ** 2);
return Math.sqrt(sum(diffArr) / (arr.length - 1));
};
const quantile = (arr, q) => {
const sorted = asc(arr);
const pos = (sorted.length - 1) * q;
const base = Math.floor(pos);
const rest = pos - base;
if (sorted[base + 1] !== undefined) {
return sorted[base] + rest * (sorted[base + 1] - sorted[base]);
} else {
return sorted[base];
}
};
const q25 = arr => quantile(arr, .25);
const q50 = arr => quantile(arr, .50);
const q75 = arr => quantile(arr, .75);
const median = arr => q50(arr);
After searching for a long time, finding different versions that give different results, I found this nice snippet on Bastian Pöttner's web blog, but for PHP. For the same price, we get the average and standard deviation of the data (for normal distributions)...
PHP Version
//from https://blog.poettner.de/2011/06/09/simple-statistics-with-php/
function Median($Array) {
return Quartile_50($Array);
}
function Quartile_25($Array) {
return Quartile($Array, 0.25);
}
function Quartile_50($Array) {
return Quartile($Array, 0.5);
}
function Quartile_75($Array) {
return Quartile($Array, 0.75);
}
function Quartile($Array, $Quartile) {
sort($Array);
$pos = (count($Array) - 1) * $Quartile;
$base = floor($pos);
$rest = $pos - $base;
if( isset($Array[$base+1]) ) {
return $Array[$base] + $rest * ($Array[$base+1] - $Array[$base]);
} else {
return $Array[$base];
}
}
function Average($Array) {
return array_sum($Array) / count($Array);
}
function StdDev($Array) {
if( count($Array) < 2 ) {
return;
}
$avg = Average($Array);
$sum = 0;
foreach($Array as $value) {
$sum += pow($value - $avg, 2);
}
return sqrt((1 / (count($Array) - 1)) * $sum);
}
Based on the author's comments, I simply wrote a JavaScript translation that will certainly be useful, because surprisingly, it is nearly impossible to find a JavaScript equivalent on the web, and otherwise requires additional libraries like Math.js
JavaScript Version
//adapted from https://blog.poettner.de/2011/06/09/simple-statistics-with-php/
function Median(data) {
return Quartile_50(data);
}
function Quartile_25(data) {
return Quartile(data, 0.25);
}
function Quartile_50(data) {
return Quartile(data, 0.5);
}
function Quartile_75(data) {
return Quartile(data, 0.75);
}
function Quartile(data, q) {
data=Array_Sort_Numbers(data);
var pos = ((data.length) - 1) * q;
var base = Math.floor(pos);
var rest = pos - base;
if( (data[base+1]!==undefined) ) {
return data[base] + rest * (data[base+1] - data[base]);
} else {
return data[base];
}
}
function Array_Sort_Numbers(inputarray){
return inputarray.sort(function(a, b) {
return a - b;
});
}
function Array_Sum(t){
return t.reduce(function(a, b) { return a + b; }, 0);
}
function Array_Average(data) {
return Array_Sum(data) / data.length;
}
function Array_Stdev(tab){
var i,j,total = 0, mean = 0, diffSqredArr = [];
for(i=0;i<tab.length;i+=1){
total+=tab[i];
}
mean = total/tab.length;
for(j=0;j<tab.length;j+=1){
diffSqredArr.push(Math.pow((tab[j]-mean),2));
}
return (Math.sqrt(diffSqredArr.reduce(function(firstEl, nextEl){
return firstEl + nextEl;
})/tab.length));
}
TL;DR
The other answers appear to have solid implementations of the "R-7" version of computing quantiles. Below is some context and another JavaScript implementation borrowed from D3 using the same R-7 method, with the bonuses that this solution is es5 compliant (no JavaScript transpilation required) and probably covers a few more edge cases.
Existing solution from D3 (ported to es5/"vanilla JS")
The "Some Background" section, below, should convince you to grab an existing implementation instead of writing your own.
One good candidate is D3's d3.array package. It has a quantile function that's essentially BSD licensed:
https://github.com/d3/d3-array/blob/master/src/quantile.js
I've quickly created a pretty straight port from es6 into vanilla JavaScript of d3's quantileSorted function (the second function defined in that file) that requires the array of elements to have already been sorted. Here it is. I've tested it against d3's own results enough to feel it's a valid port, but your experience might differ (let me know in the comments if you find a difference, though!):
Again, remember that sorting must come before the call to this function, just as in D3's quantileSorted.
//Credit D3: https://github.com/d3/d3-array/blob/master/LICENSE
function quantileSorted(values, p, fnValueFrom) {
var n = values.length;
if (!n) {
return;
}
fnValueFrom =
Object.prototype.toString.call(fnValueFrom) == "[object Function]"
? fnValueFrom
: function (x) {
return x;
};
p = +p;
if (p <= 0 || n < 2) {
return +fnValueFrom(values[0], 0, values);
}
if (p >= 1) {
return +fnValueFrom(values[n - 1], n - 1, values);
}
var i = (n - 1) * p,
i0 = Math.floor(i),
value0 = +fnValueFrom(values[i0], i0, values),
value1 = +fnValueFrom(values[i0 + 1], i0 + 1, values);
return value0 + (value1 - value0) * (i - i0);
}
Note that fnValueFrom is a way to process a complex object into a value. You can see how that might work in a list of d3 usage examples here -- search down where .quantile is used.
The quick version is if the values are tortoises and you're sorting tortoise.age in every case, your fnValueFrom might be x => x.age. More complicated versions, including ones that might require accessing the index (parameter 2) and entire collection (parameter 3) during the value calculation, are left up to the reader.
I've added a quick check here so that if nothing is given for fnValueFrom or if what's given isn't a function the logic assumes the elements in values are the actual sorted values themselves.
Logical comparison to existing answers
I'm reasonably sure this reduces to the same version in the other two answers (see "The R-7 Method", below), but if you needed to justify why you're using this to a product manager or whatever maybe the below will help.
Quick comparison:
function Quartile(data, q) {
data=Array_Sort_Numbers(data); // we're assuming it's already sorted, above, vs. the function use here. same difference.
var pos = ((data.length) - 1) * q; // i = (n - 1) * p
var base = Math.floor(pos); // i0 = Math.floor(i)
var rest = pos - base; // (i - i0);
if( (data[base+1]!==undefined) ) {
// value0 + (i - i0) * (value1 which is values[i0+1] - value0 which is values[i0])
return data[base] + rest * (data[base+1] - data[base]);
} else {
// I think this is covered by if (p <= 0 || n < 2)
return data[base];
}
}
So that's logically close/appears to be exactly the same. I think d3's version that I ported covers a few more edge/invalid conditions and includes the fnValueFrom integration, both of which could be useful.
The R-7 Method vs. "Common Sense"
As mentioned in the TL;DR, the answers here, according to d3.array's readme, all use the "R-7 method".
This particular implementation [from d3] uses the R-7 method, which is the default for the R programming language and Excel.
Since the d3.array code matches the other answers here, we can safely say they're all using R-7.
Background
After a little sleuthing on some math and stats StackExchange sites (1, 2), I found that there are "common sensical" ways of calculating each quantile, but that those don't typically mesh up with the results of the nine generally recognized ways to calculate them.
The answer at that second link from stats.stackexchange says of the common-sensical method that...
Your textbook is confused. Very few people or software define quartiles this way. (It tends to make the first quartile too small and the third quartile too large.)
The quantile function in R implements nine different ways to compute quantiles!
I thought that last bit was interesting, and here's what I dug up on those nine methods...
Wikipedia's description of those nine methods here, nicely grouped in a table
An article from the Journal of Statistics Education titled "Quartiles in Elementary Statistics"
A blog post at SAS.com called "Sample quantiles: A comparison of 9 definitions"
The differences between d3's use of "method 7" (R-7) to determine quantiles versus the common sensical approach is demonstrated nicely in the SO question "d3.quantile seems to be calculating q1 incorrectly", and the why is described in good detail in this post that can be found in philippe's original source for the php version.
Here's a bit from Google Translate (original is in German):
In our example, this value is at the (n + 1) / 4 digit = 5.25, i.e. between the 5th value (= 5) and the 6th value (= 7). The fraction (0.25) indicates that in addition to the value of 5, ¼ of the distance between 5 and 6 is added. Q1 is therefore 5 + 0.25 * 2 = 5.5.
All together, that tells me I probably shouldn't try to code something based on my understanding of what quartiles represent and should borrow someone else's solution.
Based on buboh's answer, which I have used for over a year, I have noticed some weird things for calculating the Q1 and Q3 when there are 2 numbers in the middle.
I have no clue why there is a rest value and how it is used, but by my understanding if you and up having 2 numbers in the middle you need to take the average of them to calculate the median. With that in mind I edited the function:
const asc = (arr) => arr.sort((a, b) => a - b);
const quantile = (arr, q) => {
const sorted = asc(arr);
let pos = (sorted.length - 1) * q;
if (pos % 1 === 0) {
return sorted[pos];
}
pos = Math.floor(pos);
if (sorted[pos + 1] !== undefined) {
return (sorted[pos] + sorted[pos + 1]) / 2;
}
return sorted[pos];
};

Is there a way to override assignmenet operator in JS ES6 classes? [duplicate]

I've been working with JavaScript for a few days now and have got to a point where I want to overload operators for my defined objects.
After a stint on google searching for this it seems you can't officially do this, yet there are a few people out there claiming some long-winded way of performing this action.
Basically I've made a Vector2 class and want to be able to do the following:
var x = new Vector2(10,10);
var y = new Vector2(10,10);
x += y; //This does not result in x being a vector with 20,20 as its x & y values.
Instead I'm having to do this:
var x = new Vector2(10,10);
var y = new Vector2(10,10);
x = x.add(y); //This results in x being a vector with 20,20 as its x & y values.
Is there an approach I can take to overload operators in my Vector2 class? As this just looks plain ugly.
As you've found, JavaScript doesn't support operator overloading. The closest you can come is to implement toString (which will get called when the instance needs to be coerced to being a string) and valueOf (which will get called to coerce it to a number, for instance when using + for addition, or in many cases when using it for concatenation because + tries to do addition before concatenation), which is pretty limited. Neither lets you create a Vector2 object as a result. Similarly, Proxy (added in ES2015) lets you intercept various object operations (including property access), but again won't let you control the result of += on Vector instances.
For people coming to this question who want a string or number as a result (instead of a Vector2), though, here are examples of valueOf and toString. These examples do not demonstrate operator overloading, just taking advantage of JavaScript's built-in handling converting to primitives:
valueOf
This example doubles the value of an object's val property in response to being coerced to a primitive, for instance via +:
function Thing(val) {
this.val = val;
}
Thing.prototype.valueOf = function() {
// Here I'm just doubling it; you'd actually do your longAdd thing
return this.val * 2;
};
var a = new Thing(1);
var b = new Thing(2);
console.log(a + b); // 6 (1 * 2 + 2 * 2)
Or with ES2015's class:
class Thing {
constructor(val) {
this.val = val;
}
valueOf() {
return this.val * 2;
}
}
const a = new Thing(1);
const b = new Thing(2);
console.log(a + b); // 6 (1 * 2 + 2 * 2)
Or just with objects, no constructors:
var thingPrototype = {
valueOf: function() {
return this.val * 2;
}
};
var a = Object.create(thingPrototype);
a.val = 1;
var b = Object.create(thingPrototype);
b.val = 2;
console.log(a + b); // 6 (1 * 2 + 2 * 2)
toString
This example converts the value of an object's val property to upper case in response to being coerced to a primitive, for instance via +:
function Thing(val) {
this.val = val;
}
Thing.prototype.toString = function() {
return this.val.toUpperCase();
};
var a = new Thing("a");
var b = new Thing("b");
console.log(a + b); // AB
Or with ES2015's class:
class Thing {
constructor(val) {
this.val = val;
}
toString() {
return this.val.toUpperCase();
}
}
const a = new Thing("a");
const b = new Thing("b");
console.log(a + b); // AB
Or just with objects, no constructors:
var thingPrototype = {
toString: function() {
return this.val.toUpperCase();
}
};
var a = Object.create(thingPrototype);
a.val = "a";
var b = Object.create(thingPrototype);
b.val = "b";
console.log(a + b); // AB
As T.J. said, you cannot overload operators in JavaScript. However you can take advantage of the valueOf function to write a hack which looks better than using functions like add every time, but imposes the constraints on the vector that the x and y are between 0 and MAX_VALUE. Here is the code:
var MAX_VALUE = 1000000;
var Vector = function(a, b) {
var self = this;
//initialize the vector based on parameters
if (typeof(b) == "undefined") {
//if the b value is not passed in, assume a is the hash of a vector
self.y = a % MAX_VALUE;
self.x = (a - self.y) / MAX_VALUE;
} else {
//if b value is passed in, assume the x and the y coordinates are the constructors
self.x = a;
self.y = b;
}
//return a hash of the vector
this.valueOf = function() {
return self.x * MAX_VALUE + self.y;
};
};
var V = function(a, b) {
return new Vector(a, b);
};
Then you can write equations like this:
var a = V(1, 2); //a -> [1, 2]
var b = V(2, 4); //b -> [2, 4]
var c = V((2 * a + b) / 2); //c -> [2, 4]
It's possible to do vector math with two numbers packed into one. Let me first show an example before I explain how it works:
let a = vec_pack([2,4]);
let b = vec_pack([1,2]);
let c = a+b; // Vector addition
let d = c-b; // Vector subtraction
let e = d*2; // Scalar multiplication
let f = e/2; // Scalar division
console.log(vec_unpack(c)); // [3, 6]
console.log(vec_unpack(d)); // [2, 4]
console.log(vec_unpack(e)); // [4, 8]
console.log(vec_unpack(f)); // [2, 4]
if(a === f) console.log("Equality works");
if(a > b) console.log("Y value takes priority");
I am using the fact that if you bit shift two numbers X times and then add or subtract them before shifting them back, you will get the same result as if you hadn't shifted them to begin with. Similarly scalar multiplication and division works symmetrically for shifted values.
A JavaScript number has 52 bits of integer precision (64 bit floats), so I will pack one number into he higher available 26 bits, and one into the lower. The code is made a bit more messy because I wanted to support signed numbers.
function vec_pack(vec){
return vec[1] * 67108864 + (vec[0] < 0 ? 33554432 | vec[0] : vec[0]);
}
function vec_unpack(number){
switch(((number & 33554432) !== 0) * 1 + (number < 0) * 2){
case(0):
return [(number % 33554432),Math.trunc(number / 67108864)];
break;
case(1):
return [(number % 33554432)-33554432,Math.trunc(number / 67108864)+1];
break;
case(2):
return [(((number+33554432) % 33554432) + 33554432) % 33554432,Math.round(number / 67108864)];
break;
case(3):
return [(number % 33554432),Math.trunc(number / 67108864)];
break;
}
}
The only downside I can see with this is that the x and y has to be in the range +-33 million, since they have to fit within 26 bits each.
Actually, there is one variant of JavaScript that does support operator overloading. ExtendScript, the scripting language used by Adobe applications such as Photoshop and Illustrator, does have operator overloading. In it, you can write:
Vector2.prototype["+"] = function( b )
{
return new Vector2( this.x + b.x, this.y + b.y );
}
var a = new Vector2(1,1);
var b = new Vector2(2,2);
var c = a + b;
This is described in more detail in the "Adobe Extendscript JavaScript tools guide" (current link here). The syntax was apparently based on a (now long abandoned) draft of the ECMAScript standard.
FYI paper.js solves this issue by creating PaperScript, a self-contained, scoped javascript with operator overloading of vectors, which it then processing back into javascript.
But the paperscript files need to be specifically specified and processed as such.
We can use React-like Hooks to evaluate arrow function with different values from valueOf method on each iteration.
const a = Vector2(1, 2) // [1, 2]
const b = Vector2(2, 4) // [2, 4]
const c = Vector2(() => (2 * a + b) / 2) // [2, 4]
// There arrow function will iterate twice
// 1 iteration: method valueOf return X component
// 2 iteration: method valueOf return Y component
const Vector2 = (function() {
let index = -1
return function(x, y) {
if (typeof x === 'function') {
const calc = x
index = 0, x = calc()
index = 1, y = calc()
index = -1
}
return Object.assign([x, y], {
valueOf() {
return index == -1 ? this.toString() : this[index]
},
toString() {
return `[${this[0]}, ${this[1]}]`
},
len() {
return Math.sqrt(this[0] ** 2 + this[1] ** 2)
}
})
}
})()
const a = Vector2(1, 2)
const b = Vector2(2, 4)
console.log('a = ' + a) // a = [1, 2]
console.log(`b = ${b}`) // b = [2, 4]
const c = Vector2(() => (2 * a + b) / 2) // [2, 4]
a[0] = 12
const d = Vector2(() => (2 * a + b) / 2) // [13, 4]
const normalized = Vector2(() => d / d.len()) // [0.955..., 0.294...]
console.log(c, d, normalized)
Library #js-basics/vector uses the same idea for Vector3.
I wrote a library that exploits a bunch of evil hacks to do it in raw JS. It allows expressions like these.
Complex numbers:
>> Complex()({r: 2, i: 0} / {r: 1, i: 1} + {r: -3, i: 2}))
<- {r: -2, i: 1}
Automatic differentiation:
Let f(x) = x^3 - 5x:
>> var f = x => Dual()(x * x * x - {x:5, dx:0} * x);
Now map it over some values:
>> [-2,-1,0,1,2].map(a=>({x:a,dx:1})).map(f).map(a=>a.dx)
<- [ 7, -2, -5, -2, 7 ]
i.e. f'(x) = 3x^2 - 5.
Polynomials:
>> Poly()([1,-2,3,-4]*[5,-6]).map((c,p)=>''+c+'x^'+p).join(' + ')
<- "5x^0 + -16x^1 + 27x^2 + -38x^3 + 24x^4"
For your particular problem, you would define a Vector2 function (or maybe something shorter) using the library, then write x = Vector2()(x + y);
https://gist.github.com/pyrocto/5a068100abd5ff6dfbe69a73bbc510d7
Whilst not an exact answer to the question, it is possible to implement some of the python __magic__ methods using ES6 Symbols
A [Symbol.toPrimitive]() method doesn't let you imply a call Vector.add(), but will let you use syntax such as Decimal() + int.
class AnswerToLifeAndUniverseAndEverything {
[Symbol.toPrimitive](hint) {
if (hint === 'string') {
return 'Like, 42, man';
} else if (hint === 'number') {
return 42;
} else {
// when pushed, most classes (except Date)
// default to returning a number primitive
return 42;
}
}
}
https://www.keithcirkel.co.uk/metaprogramming-in-es6-symbols/
Interesting is also experimental library operator-overloading-js . It does overloading in a defined context (callback function) only.

Javascript: operator overloading

I've been working with JavaScript for a few days now and have got to a point where I want to overload operators for my defined objects.
After a stint on google searching for this it seems you can't officially do this, yet there are a few people out there claiming some long-winded way of performing this action.
Basically I've made a Vector2 class and want to be able to do the following:
var x = new Vector2(10,10);
var y = new Vector2(10,10);
x += y; //This does not result in x being a vector with 20,20 as its x & y values.
Instead I'm having to do this:
var x = new Vector2(10,10);
var y = new Vector2(10,10);
x = x.add(y); //This results in x being a vector with 20,20 as its x & y values.
Is there an approach I can take to overload operators in my Vector2 class? As this just looks plain ugly.
As you've found, JavaScript doesn't support operator overloading. The closest you can come is to implement toString (which will get called when the instance needs to be coerced to being a string) and valueOf (which will get called to coerce it to a number, for instance when using + for addition, or in many cases when using it for concatenation because + tries to do addition before concatenation), which is pretty limited. Neither lets you create a Vector2 object as a result. Similarly, Proxy (added in ES2015) lets you intercept various object operations (including property access), but again won't let you control the result of += on Vector instances.
For people coming to this question who want a string or number as a result (instead of a Vector2), though, here are examples of valueOf and toString. These examples do not demonstrate operator overloading, just taking advantage of JavaScript's built-in handling converting to primitives:
valueOf
This example doubles the value of an object's val property in response to being coerced to a primitive, for instance via +:
function Thing(val) {
this.val = val;
}
Thing.prototype.valueOf = function() {
// Here I'm just doubling it; you'd actually do your longAdd thing
return this.val * 2;
};
var a = new Thing(1);
var b = new Thing(2);
console.log(a + b); // 6 (1 * 2 + 2 * 2)
Or with ES2015's class:
class Thing {
constructor(val) {
this.val = val;
}
valueOf() {
return this.val * 2;
}
}
const a = new Thing(1);
const b = new Thing(2);
console.log(a + b); // 6 (1 * 2 + 2 * 2)
Or just with objects, no constructors:
var thingPrototype = {
valueOf: function() {
return this.val * 2;
}
};
var a = Object.create(thingPrototype);
a.val = 1;
var b = Object.create(thingPrototype);
b.val = 2;
console.log(a + b); // 6 (1 * 2 + 2 * 2)
toString
This example converts the value of an object's val property to upper case in response to being coerced to a primitive, for instance via +:
function Thing(val) {
this.val = val;
}
Thing.prototype.toString = function() {
return this.val.toUpperCase();
};
var a = new Thing("a");
var b = new Thing("b");
console.log(a + b); // AB
Or with ES2015's class:
class Thing {
constructor(val) {
this.val = val;
}
toString() {
return this.val.toUpperCase();
}
}
const a = new Thing("a");
const b = new Thing("b");
console.log(a + b); // AB
Or just with objects, no constructors:
var thingPrototype = {
toString: function() {
return this.val.toUpperCase();
}
};
var a = Object.create(thingPrototype);
a.val = "a";
var b = Object.create(thingPrototype);
b.val = "b";
console.log(a + b); // AB
As T.J. said, you cannot overload operators in JavaScript. However you can take advantage of the valueOf function to write a hack which looks better than using functions like add every time, but imposes the constraints on the vector that the x and y are between 0 and MAX_VALUE. Here is the code:
var MAX_VALUE = 1000000;
var Vector = function(a, b) {
var self = this;
//initialize the vector based on parameters
if (typeof(b) == "undefined") {
//if the b value is not passed in, assume a is the hash of a vector
self.y = a % MAX_VALUE;
self.x = (a - self.y) / MAX_VALUE;
} else {
//if b value is passed in, assume the x and the y coordinates are the constructors
self.x = a;
self.y = b;
}
//return a hash of the vector
this.valueOf = function() {
return self.x * MAX_VALUE + self.y;
};
};
var V = function(a, b) {
return new Vector(a, b);
};
Then you can write equations like this:
var a = V(1, 2); //a -> [1, 2]
var b = V(2, 4); //b -> [2, 4]
var c = V((2 * a + b) / 2); //c -> [2, 4]
It's possible to do vector math with two numbers packed into one. Let me first show an example before I explain how it works:
let a = vec_pack([2,4]);
let b = vec_pack([1,2]);
let c = a+b; // Vector addition
let d = c-b; // Vector subtraction
let e = d*2; // Scalar multiplication
let f = e/2; // Scalar division
console.log(vec_unpack(c)); // [3, 6]
console.log(vec_unpack(d)); // [2, 4]
console.log(vec_unpack(e)); // [4, 8]
console.log(vec_unpack(f)); // [2, 4]
if(a === f) console.log("Equality works");
if(a > b) console.log("Y value takes priority");
I am using the fact that if you bit shift two numbers X times and then add or subtract them before shifting them back, you will get the same result as if you hadn't shifted them to begin with. Similarly scalar multiplication and division works symmetrically for shifted values.
A JavaScript number has 52 bits of integer precision (64 bit floats), so I will pack one number into he higher available 26 bits, and one into the lower. The code is made a bit more messy because I wanted to support signed numbers.
function vec_pack(vec){
return vec[1] * 67108864 + (vec[0] < 0 ? 33554432 | vec[0] : vec[0]);
}
function vec_unpack(number){
switch(((number & 33554432) !== 0) * 1 + (number < 0) * 2){
case(0):
return [(number % 33554432),Math.trunc(number / 67108864)];
break;
case(1):
return [(number % 33554432)-33554432,Math.trunc(number / 67108864)+1];
break;
case(2):
return [(((number+33554432) % 33554432) + 33554432) % 33554432,Math.round(number / 67108864)];
break;
case(3):
return [(number % 33554432),Math.trunc(number / 67108864)];
break;
}
}
The only downside I can see with this is that the x and y has to be in the range +-33 million, since they have to fit within 26 bits each.
Actually, there is one variant of JavaScript that does support operator overloading. ExtendScript, the scripting language used by Adobe applications such as Photoshop and Illustrator, does have operator overloading. In it, you can write:
Vector2.prototype["+"] = function( b )
{
return new Vector2( this.x + b.x, this.y + b.y );
}
var a = new Vector2(1,1);
var b = new Vector2(2,2);
var c = a + b;
This is described in more detail in the "Adobe Extendscript JavaScript tools guide" (current link here). The syntax was apparently based on a (now long abandoned) draft of the ECMAScript standard.
FYI paper.js solves this issue by creating PaperScript, a self-contained, scoped javascript with operator overloading of vectors, which it then processing back into javascript.
But the paperscript files need to be specifically specified and processed as such.
We can use React-like Hooks to evaluate arrow function with different values from valueOf method on each iteration.
const a = Vector2(1, 2) // [1, 2]
const b = Vector2(2, 4) // [2, 4]
const c = Vector2(() => (2 * a + b) / 2) // [2, 4]
// There arrow function will iterate twice
// 1 iteration: method valueOf return X component
// 2 iteration: method valueOf return Y component
const Vector2 = (function() {
let index = -1
return function(x, y) {
if (typeof x === 'function') {
const calc = x
index = 0, x = calc()
index = 1, y = calc()
index = -1
}
return Object.assign([x, y], {
valueOf() {
return index == -1 ? this.toString() : this[index]
},
toString() {
return `[${this[0]}, ${this[1]}]`
},
len() {
return Math.sqrt(this[0] ** 2 + this[1] ** 2)
}
})
}
})()
const a = Vector2(1, 2)
const b = Vector2(2, 4)
console.log('a = ' + a) // a = [1, 2]
console.log(`b = ${b}`) // b = [2, 4]
const c = Vector2(() => (2 * a + b) / 2) // [2, 4]
a[0] = 12
const d = Vector2(() => (2 * a + b) / 2) // [13, 4]
const normalized = Vector2(() => d / d.len()) // [0.955..., 0.294...]
console.log(c, d, normalized)
Library #js-basics/vector uses the same idea for Vector3.
I wrote a library that exploits a bunch of evil hacks to do it in raw JS. It allows expressions like these.
Complex numbers:
>> Complex()({r: 2, i: 0} / {r: 1, i: 1} + {r: -3, i: 2}))
<- {r: -2, i: 1}
Automatic differentiation:
Let f(x) = x^3 - 5x:
>> var f = x => Dual()(x * x * x - {x:5, dx:0} * x);
Now map it over some values:
>> [-2,-1,0,1,2].map(a=>({x:a,dx:1})).map(f).map(a=>a.dx)
<- [ 7, -2, -5, -2, 7 ]
i.e. f'(x) = 3x^2 - 5.
Polynomials:
>> Poly()([1,-2,3,-4]*[5,-6]).map((c,p)=>''+c+'x^'+p).join(' + ')
<- "5x^0 + -16x^1 + 27x^2 + -38x^3 + 24x^4"
For your particular problem, you would define a Vector2 function (or maybe something shorter) using the library, then write x = Vector2()(x + y);
https://gist.github.com/pyrocto/5a068100abd5ff6dfbe69a73bbc510d7
Whilst not an exact answer to the question, it is possible to implement some of the python __magic__ methods using ES6 Symbols
A [Symbol.toPrimitive]() method doesn't let you imply a call Vector.add(), but will let you use syntax such as Decimal() + int.
class AnswerToLifeAndUniverseAndEverything {
[Symbol.toPrimitive](hint) {
if (hint === 'string') {
return 'Like, 42, man';
} else if (hint === 'number') {
return 42;
} else {
// when pushed, most classes (except Date)
// default to returning a number primitive
return 42;
}
}
}
https://www.keithcirkel.co.uk/metaprogramming-in-es6-symbols/
Interesting is also experimental library operator-overloading-js . It does overloading in a defined context (callback function) only.

Categories