Skip to content

Instantly share code, notes, and snippets.

@LeaVerou
Last active November 23, 2020 09:34
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save LeaVerou/2881cc634df29c9d1c89fcb965f699f0 to your computer and use it in GitHub Desktop.
Save LeaVerou/2881cc634df29c9d1c89fcb965f699f0 to your computer and use it in GitHub Desktop.
Insert a property before another object literal property, while maintaining all references to that object
function insert(obj, {before, property, value}) {
let found, temp = {};
// Delete all properties from before onwoards and save them in temp
for (let p in obj) {
if (p === before) {
found = true;
}
if (found) {
temp[p] = obj[p];
delete obj[p];
}
}
// Add new property
obj[property] = value;
// Re-add removed properties
for (let p in temp) {
obj[p] = temp[p];
}
}
function insert(obj, {before, property, value}) {
let found, temp = {};
// Delete all properties from before onwoards and save them in temp
for (let p in obj) {
if (p === before) {
found = true;
}
if (found) {
temp[p] = obj[p];
delete obj[p];
}
}
// Add new property and re-add removed properties
Object.assign(obj, {property: value, ...temp});
}
function insert(obj, {before, property, value}) {
// Create a copy of entries and add the new property in place
let entries = Object.entries(obj);
let i = entries.findIndex(a => a[0] === before);
entries.splice(i, 0, [property, value]);
// Delete all obj properties
for (let p in obj) {
delete obj[p];
}
// Re-add with the new one
Object.assign(obj, Object.fromEntries(entries));
}
@bgotink
Copy link

bgotink commented Nov 20, 2020

Slightly modified version of 2:

Personally I find this slightly easier to parse, the Object.assign(obj, {[property]: value, ...temp}) took me a couple of reads to get right.

function insert(obj, {before, property, value}) {
	let found, temp = {[property]: value};
	
	// Delete all properties from before onwoards and save them in temp
	for (let p in obj) {
		if (p === before) {
			found = true;
		}

		if (found) {
			temp[p] = obj[p];
			delete obj[p];
		}
	}
	
	// Add new property and re-add removed properties
	Object.assign(obj, temp);
}

Or an alternative with a single loop:

This is my favourite, a single loop makes this pretty straightforward, but again that's personal preference.

function insert(obj, {before, property, value}) {
	for (let p in obj) {
		if (p === before) {
			obj[property] = value;
			found = true;
		}

		// p comes after the new property, delete it and re-add it to push it to the end
		if (found) {
			let temp = obj[p];
			delete obj[p];
			obj[p] = temp;
		}
	}
}

@pygy
Copy link

pygy commented Nov 20, 2020

Now with benchmarks

@bgotink's last method is the fastest in every browser, I would have expected it to be slower since it intermingles deletions and additions, but it isn't the case. One loop wins (fewer allocations, I suppose).

@monfera
Copy link

monfera commented Nov 20, 2020

Yes, maybe it's not just the memory part of the allocation and gc, and the fact that overall fewer ops are performed, but also the overhead of object processing via hidden classes - still, good call suspecting that deletes/appends in quick succession might also thrash the hidden classesi

@pygy
Copy link

pygy commented Nov 20, 2020

@monfera This looks like this is your bread and butter... Would you have an idea of how to draw useful ticks in log space? I suppose I could find it if I think hard enough about it, but if there's a standard algorithm / lib for that it would save me some time and effort...

@monfera
Copy link

monfera commented Nov 20, 2020

@pygy Assuming that all values are positive, you could use a d3.scaleLog, a minimal example is here (though D3 v3) or a more complete, up to date one is here

image

If some values are zero, you can apply the trick of adding one to all values, but then subtracting one when formatting the axis tick value.

The benefit is that you can use D3 to generate the axis, the tick values, and the ticks (and/or grid lines) with the tick labels, which can then be channeled into SVG or HTML elements.

@pygy
Copy link

pygy commented Nov 20, 2020

@monfera many thanks. I'd rather avoid pulling up d3 as a dep, I'll pilfer^Wstudy https://github.com/d3/d3-scale/blob/master/src/log.js for inspiration :-)

@monfera
Copy link

monfera commented Nov 23, 2020

@pygy rolling your own code not fundamentally difficult, but finding the somewhat optimal set of tick values (so they're nice round numbers, and not too many ticks and not to few) and scale nicing etc. can get labor intensive, has edge cases too. Btw. there's no need to import the entirety of D3; it's enough to import d3-scale, although the last time I checked, one still ends up with color palette arrays etc. which you wouldn't use. Therefore even d3-scale is very large if you need it for just one cartesian scale. Not sure if Svelte-style JS treeshaking can get rid of the palettes etc. now, I think, worth a try, interested if you do that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment