diff --git a/src/lib/utils/collections.ts b/src/lib/utils/collections.ts new file mode 100644 index 0000000..0d684eb --- /dev/null +++ b/src/lib/utils/collections.ts @@ -0,0 +1,9 @@ +function mapGetOrInit(map: Map, key: K, defaultItem: V) { + const item = map.get(key); + if (item !== undefined) { + return item; + } else { + map.set(key, defaultItem); + return defaultItem; + } +} diff --git a/src/lib/utils/fillSpace.ts b/src/lib/utils/fillSpace.ts index bf0be42..b38ddc7 100644 --- a/src/lib/utils/fillSpace.ts +++ b/src/lib/utils/fillSpace.ts @@ -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(); + 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, + } } } }