The problem is caused because implementations have limits to the number of parameters accepted. This results in an exception being raised when too many parameters (over ~128k in this case) are supplied to the String.fromCodePoint
functions via the spread operator.
One way to solve this problem relatively efficiently, albeit with slightly more code, is to batch the operation across multiple calls. Here is my proposed implementation, which fixes what I perceive as issues relating to scaling performance and the handling of surrogate pairs (that's incorrect: fromCodePoint
doesn't care about surrogates, making it preferable to fromCharCode
in such cases).
let N = 500 * 1000;
let A = [...Array(N)].map((x,i) => i); // start with "an array".
function codePointsToString(cps) {
let rs = [];
let batch = 32767; // Supported 'all' browsers
for (let i = 0; i < cps.length; ){
let e = i + batch;
// Build batch section, defer to Array.join.
rs.push(String.fromCodePoint.apply(null, cps.slice(i, e)));
i = e;
}
return rs.join('');
}
var result = codePointsToString(A);
console.log(result.length);
Also, I wanted a trophy. The code above should run in O(n) time and minimize the amount of objects allocated. No guarantees on this being the 'best' approach. A benefit of the batching approach, and why the cost of apply
(or spread invocation) is subsumed, is that there are significantly less calls to String.fromCodePoint
and intermediate strings. YMMV - especially across environments.
Here is an online benchmark. All tests have access to, and use, the same generated "A" array of 500k elements.
