JavaScript: Search and Don’t Replace (2008)
johnresig.com
johnresig.com
Now of course many of the tricks I used backfire in modern engines, and simple straightforward code is faster.
Perhaps a more important point is that this particular problem does not need to be optimized, and shouldn't be optimized! It should use the simplest and most understandable code possible. Even in the era of slow browsers from 10-15 years ago.
It's a query string, not a million-row database.
Find the code here: https://gist.github.com/rezonant/639c67db5bd6503e8f022291b91...
Results on my system (Core i7 7700, Node.js 10.15.3, Windows 10 2004):
---- Comparing 7 implementations, 1000000 repetitions each
short input:
resig: 2920ms
resigModernized: 2369ms
fullyFunctional: 2817ms
functionalHybrid: 2352ms
mapReduce: 5002ms
splitmap: 1490ms
compressURL: 6064ms
long input, few keys:
resig: 28664ms
resigModernized: 23087ms
fullyFunctional: 17509ms
functionalHybrid: 18431ms
mapReduce: 46959ms
splitmap: 14791ms
compressURL: 24485ms
long input, many keys:
resig: 17468ms
resigModernized: 15049ms
fullyFunctional: 15781ms
functionalHybrid: 13768ms
mapReduce: 30352ms
splitmap: 12959ms
compressURL: 34180ms
----The winner (according to this crude benchmark) is this implementation:
function splitmap(data){
let q = new Map();
for (let [key, value] of data.split(/&/g).map(x => x.split(/=/))) {
q.set(key, `${q.has(key) ? q.get(key) + ',' : ''}${value}`);
}
let ret = "";
for (let [ key, value ] of q)
ret = `${ret ? ret + '&' : ''}${key}=${value}`;
return ret;
}
...but I'm sure folks can come up with something fasterEDIT: The splitmap() implementation will fail on key=value=foo, cutting off the extra "=foo", though if you are expecting valid URL-encoded params then this might be an acceptable limitation.
I present to you:
function compress(data){
var s = {}, q = [];
data.replace(/([^=&]+)=([^&]*)/g, function(m, k, v) {
s[k] ? q[s[k] - 1] += "," + v : s[k] = q.push(m);
});
return q.join("&");
} function compress(data){
return data.replace(/(?<=(\w+)=[^&]*)&\1=/g,',');
}
I say: do replace after all!(Javascript didn't have zero-width look-behinds at the time)
foo=1&foo=2&foo=3&blah=a&blah=b&foo=4
correctly. You'd expect foo=1,2,3,4&blah=a,b
but get foo=1,2,3&blah=a,b&foo=4
I don't think it's possible to solve this just using a single search/replace. function compress(data){
return data.split`&`.sort().join`&`.replace(/(?<=(\w+)=[^&]*)&\1=/g,',');
}https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
[...paragraph.matchAll(regex)].reduce((q,[kv, key, value]) => { q[key] = (q[key] ? q[key] + `,`: ``) + value return q }, {})
With that said, there could easily be an array being iterated under the hood with the `replace` method anyway.
For...of has a bad rep because eslint usually is configured to show a warning, because the Babel transpile creates less optimal code if it targets old browsers; but is better here.
I know it's weird, but that's what $.each existed for. for ... of did not exist.
Originally, a jQuery object that you got from calls like $(foo) was not an array-like object as it is today. The jQuery object had an internal array of the matching DOM elements, but you were supposed to ignore it and instead use $(foo).each(...) to iterate through the elements, or $(foo).get(n) to access a specific element.
I thought it would be more convenient if you could just treat the jQuery object itself as an array, which turned out to be a simple change. So that's why you can now do $(foo)[n]. The .get(n) method was kept for compatibility with very old code.
At that point, $(foo).each(...) was not as useful as it had been, but it was also kept for compatibility. And $.each(...) was also kept around, as it was the helper function for $(foo).each(...) and related methods.
Another fun fact on the first version of the jQuery code: all of the methods on a jQuery object like $().each, $().html, $().css, etc. were not on a prototype object. Whenever you called $(foo) to create a jQuery object, it ran a loop to copy references to all those methods into the jQuery object.
Needless to say, that was a bit slow, and got slower as you added plugins. So my other minor architectural contribution was to use a prototype instead of copying all the methods.
We were all learning as we went along in those days! :-)
Object.entries(Array.from(new URLSearchParams("foo=1&foo=2&foo=3&blah=a&blah=b").entries()).reduce((a,[k,v]) => ({ ...a, [k]: [...a[k] ?? [], v] }), {})).map(([key, values]) => `${key}=${values.join(",")}`).join("&");
I was hoping other people to come up modern solutions to this same original problem.
Even my solution is questionable, because it relies on generating new URLSearchParams with strings, if one wants to be secure the reduce should take URLSearchParams as accumulator and add the items there.
Edit: Woah, made a hash of that before going in and fixing all the typos from my phone keyboard.
I enjoy reading people's off-the-cuff code golf stuff and usually learn something.
Note that this unfortunately common pattern is quadratic, which is usually not a good idea, and can have conflicts between entries and properties of Object.prototype. A similar implementation without those problems:
.reduce((a, [k, v]) => a.has(k) ? a.set(k, [v]) : (a.get(k).push(v), a), new Map())
And an alternate implementation: const input = new URLSearchParams("foo=1&foo=2&foo=3&blah=a&blah=b");
const result = new URLSearchParams();
for (const key of input.keys()) {
if (!result.has(key)) {
result.set(key, input.getAll(key).join(","));
}
}
return String(result);Of course, that could have been based on a misunderstanding on the part of the person talking with me, though I would guess it matters how many intermediates (or array elements) you're talking about.
Sort of a miracle that you mostly don't need to worry about this stuff today, even running on a $100 mobile device.
Jamie Zawinski
The alternative is what, a simple tokenizing parser? I think that's actually a step squarely into territory of making it more complex and less readable than a simple regular expression is.
https://stackoverflow.com/questions/1732348/regex-match-open...
Kipling
It's well-adopted (unless you have to support IE: <https://caniuse.com/?search=URLSearchParams>)
contains = hasCompare || rnative.test( docElem.contains ) ?
function( a, b ) {
var adown = a.nodeType === 9 ? a.documentElement : a,
bup = b && b.parentNode;
return a === bup || !!( bup && bup.nodeType === 1 && (
adown.contains ?
adown.contains( bup ) :
a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
));
} :
function( a, b ) {
if ( b ) {
while ( (b = b.parentNode) ) {
if ( b === a ) {
return true;
}
}
}
return false;
};