diff --git a/src/lib/utils/math.ts b/src/lib/utils/math.ts index 7a202aa..5aaba6f 100644 --- a/src/lib/utils/math.ts +++ b/src/lib/utils/math.ts @@ -42,6 +42,51 @@ function findMinPositive(items: T[], evaluate: (item: T) => number) { return bestItem; } +function findMeanInt(sum: number, constraints: { min: number, max: number }[]) { + let mean = Math.floor(sum / constraints.length); + while (true) { + let actualSum = 0; + let increasable = 0; + let decreasable = 0; + let low = -Infinity; + let high = Infinity; + for (const constraint of constraints) { + const value = clamp(mean, constraint.min, constraint.max); + actualSum += value; + if (mean > constraint.min) { + decreasable++; + if (value > low) { + low = value; + } + } + if (mean < constraint.max) { + increasable++; + if (value < high) { + high = value; + } + } + } + + const oldMean = mean; + const error = actualSum - sum; + if (error > 0) { + // need to decrease mean + if (decreasable > 0) { + mean = Math.floor(low - error / decreasable); + } + } else if (error < 0) { + // need to increase mean + if (increasable > 0) { + mean = Math.floor(high - error / increasable); + } + } + + if (mean === oldMean) { + return mean; + } + } +} + function rectEquals(a: QmlRect, b: QmlRect) { return a.x === b.x && a.y === b.y && diff --git a/src/tests/units/utils/findMean.ts b/src/tests/units/utils/findMean.ts new file mode 100644 index 0000000..136cd4a --- /dev/null +++ b/src/tests/units/utils/findMean.ts @@ -0,0 +1,93 @@ +tests.register("findMean", 1, () => { + const testCases: { + sum: number, + constraints: { min: number, max: number }[], + check: (result: number) => boolean, + }[] = [ + { + sum: 600, + constraints: [ + { min: 10, max: 600 }, + { min: 10, max: 600 }, + ], + check: r => r === 300, + }, + { + sum: 600, + constraints: [ + { min: 10, max: 250 }, + { min: 10, max: 500 }, + ], + check: r => r === 350, + }, + { + sum: 600, + constraints: [ + { min: 10, max: 250 }, + { min: 400, max: 500 }, + ], + check: r => r === 200, + }, + { + sum: 765, + constraints: [ + { min: 10, max: 250 }, + { min: 10, max: 254 }, + { min: 10, max: 500 }, + ], + check: r => r === 261, + }, + { + sum: 600, + constraints: [ + { min: 10, max: 150 }, + { min: 400, max: 500 }, + ], + check: r => r === 450, + }, + { + sum: 750, + constraints: [ + { min: 10, max: 250 }, + { min: 10, max: 250 }, + { min: 400, max: 500 }, + { min: 10, max: 300 }, + ], + check: r => r === 116, + }, + { + sum: 750, + constraints: [ + { min: 10, max: 250 }, + { min: 120, max: 250 }, + { min: 400, max: 500 }, + { min: 10, max: 300 }, + ], + check: r => r === 115, + }, + { + sum: 1200, + constraints: [ + { min: 10, max: 250 }, + { min: 10, max: 500 }, + ], + check: r => r >= 500, + }, + { + sum: 5, + constraints: [ + { min: 10, max: 250 }, + { min: 10, max: 500 }, + ], + check: r => r <= 10, + }, + ]; + + for (const testCase of testCases) { + const result = findMeanInt(testCase.sum, testCase.constraints); + Assert.assert( + testCase.check(result), + { message: `got ${result} for test case ${JSON.stringify(testCase)}` }, + ); + } +});