assignDeterministicIds = (
	items,
	getName,
	comparator,
	assignId,
	ranges = [10],
	expandFactor = 10,
	extraSpace = 0,
	salt = 0
) => {
	items.sort(comparator);

	// max 5% fill rate
	const optimalRange = Math.min(
		items.length * 20 + extraSpace,
		Number.MAX_SAFE_INTEGER
	);

	let i = 0;
	let range = ranges[i];
	while (range < optimalRange) {
		i++;
		if (i < ranges.length) {
			range = Math.min(ranges[i], Number.MAX_SAFE_INTEGER);
		} else if (expandFactor) {
			range = Math.min(range * expandFactor, Number.MAX_SAFE_INTEGER);
		} else {
			break;
		}
	}

	for (const item of items) {
		const ident = getName(item);
		let id;
		let i = salt;
		do {
			id = numberHash(ident + i++, range);
		} while (!assignId(item, id));
	}
}
@template T
@param {T[]} items list of items to be named
@param {(item: T) => string} getName get a name for an item
@param {(a: T, n: T) => -1 | 0 | 1} comparator order of items
@param {(item: T, id: number) => boolean} assignId assign an id to an item
@param {number[]} ranges usable ranges for ids
@param {number} expandFactor factor to create more ranges
@param {number} extraSpace extra space to allocate, i. e. when some ids are already used
@param {number} salt salting number to initialize hashing
@returns {void}