Top-Level Array filter with Function.prototype.call.bind

Top-level Array generics didn’t make it into EcmaScript. Here’s how to hand-roll them.

const myFilter = Function.prototype.call.bind(Array.prototype.filter);

And that gives is a function that can be called as:—

const isNotUpper =  e=>e>"Z"; // lexicographic comparison
myFilter("asdAxE", isNotUpper).join(""); // "asdx"

How does this filter “magic” work? Let’s look step-by-step with another example, Array.prototype.forEach. But first, let’s get some “basics” out of the way.

Function.prototype.call

Function.prototype.call calls the function it’s called on, passing its first argument as the this value to that function.

In the following example, window.prompt is called three times, passing the value of each character, followed by the index in which it occurs, followed by the this value (a String object).

[].forEach.call("asd", prompt);

The interpreter will execute the above as the following:

thisArg = new String("asd");

prompt("a", 0, thisArg);
prompt("s", 1, thisArg);
prompt("d", 2, thisArg);

(Function window.prompt ignores the last argument.)

We can do likewise with console.log, which prints any number of arguments.

[].forEach.call("asd", console.log);

image
In each call, the arguments passed are (1) the value at each index, (2) the index, and (3) the String object (promoted from a string value), used as the thisArg.

Function.prototype.call.call

We can further abstract the forEach call with:—

Function.prototype.call.call (func, thisArg, ...args )

— as:—

Function.prototype.call.
    call([].forEach, "asd", console.log)

— resulting:—

image

In the above code, the this value to call is [].forEach, the this value to forEach is "asd", promoted to a String object, and the callback function, the first argument to forEach, is console.log.

Function.prototype.call.bind

If the second call method is replaced with call to Function.prototype.bind, forEach will be bound as the this value to a function from call as:—

let arrayForEach = Function.prototype.call.bind([].forEach);

The steps by which this new function is created are a bit tricky, but it essentially creates a new function with a `[[BoundThis]] value assigned to the first argument (promoted to an object) (See: 10.4.1.3 BoundFunctionCreate ( targetFunctionboundThisboundArgs )).

The forEach method can now be called generically, without call.

arrayForEach("asd", console.log)

The bound function arrayForEach is passed with "asd", which is promoted to a string object with length=3, and used as the this arg for [].forEach. Function [].forEach is called with, console.log three times, such as:

console.log(thisArg[i], i, thisArg)
image

We can reuse this top-level Array.prototype.forEach:

const arrayForEach = 
Function.prototype.call.bind([].forEach);
arrayForEach("asd", console.log)
arrayForEach("qwe", console.log)
image

Function.prototype.call Shortcut

Just as Array.prototype.forEach is found on every array instance as [].forEach, so too is Function.prototype.call found on every function instance, such as (function(){}).call.

Base Object to Call and Arrow Functions

Arrow functions get their this value from the lexical environment and Bound functions have a bound thisArg (more on this later). This:—

(e=>e).call("foo")

— results undefined

For our intent, this doesn’t matter. We can still use call a layer of abstraction out, as call.call. We don’t access the Base object that far back. We can also use other functions or the built-in function constructor function, Function.

(function(){return this}).call({})

— which returns the object argument, {}.

Calling of forEach is stored by binding forEach to the call method:

let boundForEach = Function.prototype.call.bind([].forEach);

This can be later called with any thisArg and any callback.

boundForEach("asd", prompt);

The forEach method normally takes a callback as its first argument, but this bound call would look like this:

boundForEach.call("asd", console.log);

Bound Method for Array.prototype.filter

Back to the problem at hand, we want to invoke Array.prototype.filter when it’s called and with the arguments passed in:

const myFilter = Function.prototype.call.bind(Array.prototype.filter);

And that gives is a function that can be called as:—

const isNotUpper =  e=>e>"Z"; // lexicographic comparison
myFilter("asd", isNotUpper);

— results:—

['a', 's', 'd']

Function myFilter is called with array-like object to act as the this value for the actual function call to Array.prototype.filterArray.prototype.filter promotes its thisArg to a string object and calls the second parameter, isNotUpper (with that String as the thisArg).

Just as we get Array.prototype.forEach with [].forEach, so too can we get Function.prototype.call from any function (except arrow or bound functions).

Function.call is the same Function.prototype.call, but gets its this arg as Function if called directly. As mentioned, we’re not calling it directly.

From: https://leetcode.com/problems/filter-elements-from-array/discuss/5024144/Function.prototype.call.bind

Checking NaN Values

NaN is a global property that represents an IEEE 754 “not a number”. (There is also a static NaN property of the built-in Number object, Number.NaN for pointless duplication (mdn)).

In older versions of ECMAScript NaN and other global properties like undefined were writable. You shouldn’t be doing that and Brendan, et al made it illegal; even throwin errors in strict mode..

The value type of NaN is “number”, as can be seen by the literal NaN property or by NaN values.

typeof NaN; // "number"
typeof (1 / "foo"); // "number"

Any value compared to NaN using the comparison operators == or === results false.

NaN == NaN; // false
undefined == NaN; // false

Method isNaN almost seems to work:—

isNaN(NaN); // true

— until it does type conversion, even attempting to run the algorithm when no argument is supplied.

isNaN(); // true
isNaN(undefined); // true
isNaN("-."); // true
isNaN(""); // false
isNaN("-2."); // false

The answer to that is to use Number.isNaN, which only returns true for actual NaN values and does not do type conversion.

Number.isNaN(); // false
Number.isNaN("1"); // false
Number.isNaN(""); // false
Number.isNaN("-."); // false
Number.isNaN("0xf"); // false
Number.isNaN("-2."); // false

Object.is can also reliably check NaN values:

 Object.is(NaN, NaN); // true
 Object.is(NaN, undefined); // false

Methods isFinite and the newer non-type-converting version Number.isFinite can work in certain situations:—

 Number.isFinite(NaN); // false

— but do not check exclusively for NaN, as they also return true for Infinity and -Infinity:—

 Number.isFinite(-Infinity); // false

But these methods are useful for numeric validation. Especially Number.isFinite, which, unlike global isFinite, does not do type conversion:

Number.isFinite("2"); // false
Number.isFinite(new Date); // false
isFinite("2"); // true
isFinite(new Date); // true

LeetCode 54. Spiral Matrix in JavaScript

Problem

Given an m x n matrix, return all elements of the matrix in spiral order.

https://leetcode.com/problems/spiral-matrix/description/

Solution

https://leetcode.com/problems/spiral-matrix/solutions/2989475/four-pointer-javascript-video-demo/

Interactive Demo

https://mybanned.com//demo/spiral-matrix.html

Video

LeetCode 3. Longest Substring Without Repeating Characters — Interactive JavaScript

Problem

Given a string s, find the length of the longest substring without repeating characters.

https://leetcode.com/problems/longest-substring-without-repeating-characters/description/

Solution

Dynamic Programming Sliding Window with Set

Interactive Example using Promises

Explanation

Use a Sliding window. Go stepwise through characters of s to expand the longest unique string one character each step. Each time a new maximum size of characters in the Set is reached, it is stored in our variable, ans. The Set is used only to track the next possible longest unique set of characters.

If the next character in s is not in the Set, wordEnd is incremented, widening the window. Save ans = Max(new result, previous max).

If the next character is already in our Set, try a new window. We don’t know where in our word the next character exists, but we can try removing one char from the front of the set. This decreases the current running set of unique characters, but gives us a chance to possibly find a longer unique set.

MAX_LENGTH - wordStart > ans

If the length number of characters remaining are greater than our longest answer, continue the loop for a chance to get a longer answer.

Loop Terminal Condition

MAX_LENGTH - wordStart > ans
If the length number of characters remaining are greater than our longest answer, continue the loop (there is a chance to get a longer answer).

Time complexity

O(n)

Space complexity

O(2n)

JavaScript

function lengthOfLongestSubstring(s) {
    const MAX_LENGTH = s.length;
    const set = new Set();
    let ans = 0, wordStart = 0, wordEnd = 0;
    while (MAX_LENGTH - wordStart > ans) { // Loop terminal condition.
        if(!set.has(s[wordEnd])) {
            set.add(s[wordEnd++]);
            ans = Math.max(ans, wordEnd - wordStart);
        } else {
            set.delete(s[wordStart++]);
        }
    }
    return ans;
}

Changing while to do / while can improve performance. If the readability is good, why not use it?

Only one small tweak is needed to avoid an error. Can you spot the error?

function lengthOfLongestSubstring(s) {
    "use strict";
    const MAX_LENGTH = s.length;
    const testChars = new Set();

    let ans = 0, wordStart = 0, wordEnd = 0;
    if (MAX_LENGTH === 0) return 0;
    do { 
        if(!testChars.has(s[wordEnd])) {
            testChars.add(s[wordEnd++]);
            ans = Math.max(ans, wordEnd - wordStart);
        } else {
            testChars.delete(s[wordStart++]);
        }
    } while (ans < MAX_LENGTH - wordStart)
    return ans;
}

If s === "", its 0 property (wordEnd) is undefined.

We can avoid adding undefined to our map by doing a check for this special case before the loop.

if (MAX_LENGTH === 0) return 0;

Finally, adding "use strict" allows the script engine to make some of its own optimizations, more for sanity than performance. (This should mostly be done on the file level or by using modules or classes.) The result is readable and has phenomenal performance, beating 91.55 % of all javascript submissions.

Non-strict functions use a separate Environment Record for top-level lexical declarations. This is done so direct eval can determine whether any var-scoped declarations introduced by the eval code conflict with pre-existing top-level lexically scoped declarations. This is not needed for strict functions because strict direct eval places all declarations into a new Environment Record.

Performance: Beats 91.55%!