fillSpace: reimplement
This commit is contained in:
9
src/lib/utils/collections.ts
Normal file
9
src/lib/utils/collections.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
function mapGetOrInit<K, V>(map: Map<K, V>, key: K, defaultItem: V) {
|
||||
const item = map.get(key);
|
||||
if (item !== undefined) {
|
||||
return item;
|
||||
} else {
|
||||
map.set(key, defaultItem);
|
||||
return defaultItem;
|
||||
}
|
||||
}
|
||||
@@ -4,55 +4,58 @@ function fillSpace(availableSpace: number, items: { min: number, max: number }[]
|
||||
}
|
||||
|
||||
function findMeanSpaceFiller(availableSpace: number, items: { min: number, max: number }[]) {
|
||||
let mean = Math.floor(availableSpace / items.length);
|
||||
for (let i = 0; true; i++) {
|
||||
let requiredSpace = 0;
|
||||
let low = -Infinity;
|
||||
let high = Infinity;
|
||||
for (const item of items) {
|
||||
const size = clamp(mean, item.min, item.max);
|
||||
requiredSpace += size;
|
||||
if (mean > item.min) {
|
||||
if (size > low) {
|
||||
low = size;
|
||||
}
|
||||
}
|
||||
if (mean < item.max) {
|
||||
if (size < high) {
|
||||
high = size;
|
||||
}
|
||||
}
|
||||
const ranges = buildRanges(items);
|
||||
let requiredSpace = items.reduce((acc, item) => acc + item.min, 0);
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
const range = ranges[i];
|
||||
const rangeSize = range.end - range.start;
|
||||
const maxRequiredSpaceDelta = rangeSize * range.n;
|
||||
if (requiredSpace + maxRequiredSpaceDelta >= availableSpace || i === ranges.length-1) {
|
||||
const positionInRange = (availableSpace - requiredSpace) / maxRequiredSpaceDelta;
|
||||
return Math.floor(range.start + rangeSize * positionInRange);
|
||||
}
|
||||
requiredSpace += maxRequiredSpaceDelta;
|
||||
}
|
||||
return 0;
|
||||
|
||||
const oldMean = mean;
|
||||
const error = requiredSpace - availableSpace;
|
||||
if (error > 0) {
|
||||
// need to decrease mean
|
||||
let decreasable = 0;
|
||||
for (const item of items) {
|
||||
if (mean > item.min && low - error < item.max) {
|
||||
decreasable++;
|
||||
}
|
||||
}
|
||||
if (decreasable > 0) {
|
||||
mean = Math.floor(low - error / decreasable);
|
||||
}
|
||||
} else if (error < 0) {
|
||||
// need to increase mean
|
||||
let increasable = 0;
|
||||
for (const item of items) {
|
||||
if (mean < item.max && high - error > item.min) {
|
||||
increasable++;
|
||||
}
|
||||
}
|
||||
if (increasable > 0) {
|
||||
mean = Math.floor(high - error / increasable);
|
||||
}
|
||||
function buildRanges(items: { min: number, max: number }[]) {
|
||||
const landmarks = buildLandmarks(items);
|
||||
const ranges: Range[] = [];
|
||||
let n = 0;
|
||||
for (let i = 1; i < landmarks.length; i++) {
|
||||
const startLandmark = landmarks[i-1];
|
||||
const endLandmark = landmarks[i];
|
||||
n = n - startLandmark.nMax + startLandmark.nMin;
|
||||
ranges.push({
|
||||
start: startLandmark.value,
|
||||
end: endLandmark.value,
|
||||
n: n,
|
||||
});
|
||||
}
|
||||
return ranges;
|
||||
|
||||
if (mean === oldMean) {
|
||||
log(`findMeanSpaceFiller ${i} / ${items.length}`);
|
||||
return mean;
|
||||
type Range = {
|
||||
start: number,
|
||||
end: number,
|
||||
n: number,
|
||||
};
|
||||
|
||||
function buildLandmarks(items: { min: number, max: number }[]) {
|
||||
const landmarks = new Map<number, Landmark>();
|
||||
for (const item of items) {
|
||||
mapGetOrInit(landmarks, item.min, { value: item.min, nMin: 0, nMax: 0 }).nMin++;
|
||||
mapGetOrInit(landmarks, item.max, { value: item.max, nMin: 0, nMax: 0 }).nMax++;
|
||||
}
|
||||
|
||||
const array = Array.from(landmarks.values());
|
||||
array.sort((a, b) => a.value - b.value);
|
||||
return array;
|
||||
|
||||
type Landmark = {
|
||||
value: number,
|
||||
nMin: number,
|
||||
nMax: number,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user