百分比序列凑整算法咨询:实现调整后总和为100,适配Pl/PgSql
匹配需求的成熟算法
你要实现的百分比凑整逻辑属于最大余数法(Hamilton Method,也叫汉密尔顿席位分配法),该算法原本用于选举场景下的席位分配,适配后可完美满足你要求的总和固定、优先调整非整数、小值优先分配差值的规则,也能解决你当前遇到的49.99无法凑整到50.00的问题。
适配你业务规则的算法步骤
调整后的算法完全兼容你现有逻辑,同时覆盖所有约束:
- 前置校验:先计算所有输入百分比的总和,若总和<99.9或>100.1,直接返回原数值不调整
- 数值转换:将所有百分比乘以100转为整数,记总和为
sum_int,总差值diff = 10000 - sum_int(100%对应10000个0.01的最小单位) - 权重计算:沿用你现有权重规则,权重越低调整优先级越高:
- (int mod 1000)=0 → 权重30
- (int mod 100)=0 → 权重20
- (int mod 10)=0 → 权重10
- (int mod 5)=0 → 权重5
- 其余情况 → 权重0
- 排序规则:先按权重升序排序,同权重按数值升序排序,保证非整数、小数值优先调整
- 差值分配:
- 若diff>0(缺额):按排序顺序依次给每个项加1,每加1次diff减1,直到diff=0
- 若diff<0(溢出):按排序顺序依次给每个项减1,每减1次diff加1,直到diff=0
- 结果转换:将所有整数除以100转回两位小数的百分比数值
该逻辑天然支持你提到的49.99转50.00的场景:49.99转整后为4999,权重为0(调整优先级最高),只要存在正差值会优先对其调整,一步即可凑整为50.00。
Pl/PgSQL 实现参考
CREATE OR REPLACE FUNCTION adjust_percentages(input_nums numeric[]) RETURNS numeric[] AS $$ DECLARE i integer; total numeric; sum_int integer; diff integer; item_int integer; weight integer; items RECORD; item_list RECORD[]; result numeric[]; BEGIN -- 1. 前置校验总和 SELECT sum(val) INTO total FROM unnest(input_nums) val; IF total < 99.9 OR total > 100.1 THEN RETURN input_nums; END IF; -- 2. 转整数并计算权重、保留原始下标保证输出顺序和输入一致 FOR i IN 1..array_length(input_nums, 1) LOOP item_int := round(input_nums[i] * 100)::integer; -- 按规则计算权重 IF item_int % 1000 = 0 THEN weight := 30; ELSIF item_int % 100 = 0 THEN weight := 20; ELSIF item_int % 10 = 0 THEN weight := 10; ELSIF item_int % 5 = 0 THEN weight := 5; ELSE weight := 0; END IF; item_list[i] := (i, item_int, weight)::RECORD(idx integer, val integer, weight integer); END LOOP; -- 3. 计算总差值 SELECT sum((item).val) INTO sum_int FROM unnest(item_list) item; diff := 10000 - sum_int; -- 4. 循环分配差值 WHILE diff != 0 LOOP -- 取优先级最高的待调整项 SELECT * INTO items FROM unnest(item_list) item ORDER BY (item).weight ASC, (item).val ASC LIMIT 1; -- 更新数值 IF diff > 0 THEN item_list[items.idx].val := items.val + 1; diff := diff - 1; ELSE item_list[items.idx].val := items.val - 1; diff := diff + 1; END IF; END LOOP; -- 5. 组装返回结果,保留两位小数 FOR i IN 1..array_length(input_nums, 1) LOOP result[i] := round(item_list[i].val::numeric / 100, 2); END LOOP; RETURN result; END; $$ LANGUAGE plpgsql IMMUTABLE;
内容的提问来源于stack exchange,提问作者JC Boggio
相关产品推荐
相关产品推荐

