You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

一维元素空间分配问题:基于填充因子的Rust实现优化需求

一维元素空间布局实现需求

我需要在指定空间内布局一维元素。元素拥有初始尺寸,可拉伸以填充可用空间,但不可收缩。每个元素关联一个填充因子(fill factor),用于确定与其他元素的理想尺寸比例。填充因子为0的元素始终保持初始尺寸。

当初始尺寸与可用空间无法实现理想相对尺寸分配时,我希望分配额外空间,使新的相对尺寸尽可能接近最优值。我未指定“最接近”的具体衡量标准,但绝不希望为相对空间已大于其相对填充因子的元素分配更多空间。

我编写了一个Rust实现,根据相对填充因子分配额外可用空间,但仅在有足够可用空间达成最优尺寸比例时有效。空间不足时会失效,因为它总会为所有填充因子为正的元素分配一些额外空间。

以下是我的代码及测试用例,希望能展示我的需求。

fn allocate_space(
    current_sizes: &[f32],
    fill_factors: &[u16],
    available_space: f32,
) -> Vec<f32> {
    // Assumptions
    {
        assert_eq!(current_sizes.len(), fill_factors.len());
        assert!(current_sizes.iter().sum::<f32>() <= available_space);
    }

    // Implementation
    let mut sizes = Vec::from(current_sizes);
    let fill_factor_sum = fill_factors.iter().sum::<u16>();

    // I'll probably need this
    let _fixed_size = current_sizes
        .iter()
        .zip(fill_factors)
        .filter_map(|(space, fill_factor)| {
            (*fill_factor == 0u16).then_some(space)
        })
        .sum::<f32>();

    if fill_factor_sum > 0 {
        let extra_space = available_space - sizes.iter().sum::<f32>();
        for (space, fill_factor) in sizes.iter_mut().zip(fill_factors) {
            *space += f32::from(*fill_factor) / f32::from(fill_factor_sum)
                * extra_space;
        }
    }

    // Constraints on the result
    {
        for ((current_size, new_size), fill_factor) in
            current_sizes.iter().zip(&sizes).zip(fill_factors)
        {
            // The allocated space is not allowed to shrink
            assert!(new_size >= current_size);
            // Items with a fill factor of zero keep their original size
            if *fill_factor == 0u16 {
                assert_eq!(current_size, new_size);
            }
        }
        // Can't use more space than available
        assert!(sizes.iter().sum::<f32>() <= available_space);
    }

    sizes.into()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn enough_space_for_relative_fill_factors() {
        assert_eq!(allocate_space(&[1.0, 1.0], &[1, 1], 4.0), vec![2.0, 2.0]);
        assert_eq!(allocate_space(&[1.0, 1.0], &[1, 1], 5.0), vec![2.5, 2.5]);
    }

    #[test]
    fn not_enough_space_for_relative_fill_factors() {
        assert_eq!(allocate_space(&[1.0, 2.0], &[1, 1], 3.5), vec![1.5, 2.0]);
    }

    #[test]
    fn unequal_fill_factors() {
        assert_eq!(allocate_space(&[1.0, 1.0], &[1, 2], 3.0), vec![1.0, 2.0]);
        assert_eq!(allocate_space(&[1.0, 1.0], &[1, 2], 2.5), vec![1.0, 1.5]);
    }

    #[test]
    fn fixed_spaces() {
        assert_eq!(
            allocate_space(&[1.0, 2.0, 2.0], &[0, 1, 1], 7.0),
            vec![1.0, 3.0, 3.0]
        );
        assert_eq!(
            allocate_space(&[1.0, 2.0, 2.0], &[0, 1, 2], 6.0),
            vec![1.0, 2.0, 3.0]
        );
    }

    #[test]
    fn three_variable_sizes() {
        assert_eq!(
            allocate_space(&[1.0, 1.0, 2.0], &[1, 2, 1], 4.0),
            vec![1.0, 1.0, 2.0]
        );
        assert_eq!(
            allocate_space(&[1.0, 1.0, 2.0], &[1, 2, 1], 6.0),
            vec![1.0 + 1.0 / 3.0, 2.0 + 2.0 / 3.0, 2.0]
        );
        assert_eq!(
            allocate_space(&[1.0, 1.0, 2.0], &[1, 2, 1], 7.0),
            vec![1.0 + 2.0 / 3.0, 3.0 + 1.0 / 3.0, 2.0]
        );
        assert_eq!(
            allocate_space(&[1.0, 1.0, 2.0], &[1, 2, 1], 8.0),
            vec![2.0, 4.0, 2.0]
        );
    }
}

我直觉认为这个问题有相对简单的解决方案,但我还没完全想出来。

我提供Rust代码示例是因为我在Rust程序中使用,但也欢迎任何语言、伪代码、数学公式或文字形式的帮助。

编辑
以下是我基于@btilly的答案实现的版本,使用float-cmp crate检查f32的近似相等性。

use std::cmp::Ordering;

#[derive(Debug, Clone, Copy, PartialEq)]
struct Item {
    size: f32,
    fill_factor: u16,
}

impl Item {
    fn size_over_fill(&self) -> f32 {
        self.size / self.fill_factor as f32
    }
}

fn item(size: f32, fill_factor: u16) -> Item {
    Item { size, fill_factor }
}

fn allocate_space(items: Vec<Item>, available_space: f32) -> Vec<f32> {
    assert!(
        items.iter().map(|i| i.size).sum::<f32>() <= available_space,
        "Sum of current sizes is smaller than total available space"
    );

    // Implementation
    let mut fill_factor_sum = items.iter().map(|i| i.fill_factor).sum::<u16>();
    let fixed_space = items
        .iter()
        .filter_map(|i| (i.fill_factor == 0).then_some(i.size))
        .sum::<f32>();
    let mut size_sum = available_space;

    let sorted_items = sort_items(items.clone());

    let mut new_sizes = sorted_items
        .iter()
        .map(|(i, item)| {
            let fill_size =
                item.fill_factor as f32 / fill_factor_sum as f32 * size_sum;
            let new_size = if item.size < fill_size {
                fill_size
            } else {
                item.size
            };
            size_sum -= new_size;
            fill_factor_sum -= item.fill_factor;
            (i, new_size)
        })
        .collect::<Vec<_>>();

    // Sort the elements back into original order
    new_sizes.sort_by(|(a, _), (b, _)| a.cmp(&b));

    // Constraints on the result
    for ((_, new_size), Item { size, fill_factor }) in
        new_sizes.iter().zip(&items)
    {
        assert!(
            new_size >= size,
            "Allocated space must not be smaller than the starting size"
        );
        if *fill_factor == 0u16 {
            assert_eq!(
                new_size, size,
                "Items with a fill factor of zero must keep their original
                size. Original size: {}, new size: {}",
                size, new_size
            );
        }
    }
    assert!(
        new_sizes.iter().map(|i| i.1).sum::<f32>() == available_space,
        "Should use all available space. Space used: {}, available_space: {}",
        new_sizes.iter().map(|i| i.1).sum::<f32>(),
        available_space
    );

    new_sizes.into_iter().map(|(_, size)| size).collect()
}

fn sort_items(items: Vec<Item>) -> Vec<(usize, Item)> {
    let mut items = items.into_iter().enumerate().collect::<Vec<_>>();
    items.sort_by(|a, b| {
        if a.1.fill_factor == 0 {
            return Ordering::Less;
        } else if b.1.fill_factor == 0 {
            return Ordering::Greater;
        }

        b.1.size_over_fill().total_cmp(&a.1.size_over_fill())
    });
    items
}

#[cfg(test)]
mod tests {
    use float_cmp::assert_approx_eq;

    use super::*;

    #[test]
    fn sort_correctly() {
        let item_a = item(1., 1);
        let item_b = item(1., 0);

        assert_eq!(
            sort_items(vec![item_a, item_b]),
            vec![(1, item_b), (0, item_a)]
        );

        let item_a = item(1., 1);
        let item_b = item(1., 0);
        let item_c = item(2., 1);
        let item_d = item(2., 3);
        assert_eq!(
            sort_items(vec![item_a, item_b, item_c, item_d]),
            vec![(1, item_b), (2, item_c), (0, item_a), (3, item_d)]
        );
    }

    #[test]
    fn enough_space_for_relative_fill_factors() {
        assert_eq!(
            allocate_space(vec![item(1., 1), item(1., 1)], 2.),
            vec![1., 1.]
        );
        assert_eq!(
            allocate_space(vec![item(1., 1), item(1., 1)], 4.),
            vec![2., 2.]
        );
        assert_eq!(
            allocate_space(vec![item(1., 1), item(1., 1)], 5.),
            vec![2.5, 2.5]
        );
    }

    #[test]
    fn not_enough_space_for_relative_fill_factors() {
        assert_eq!(
            allocate_space(vec![item(1., 1), item(2., 1)], 3.5),
            vec![1.5, 2.]
        );
    }

    #[test]
    fn unequal_fill_factors() {
        assert_eq!(
            allocate_space(vec![item(1., 1), item(1., 2)], 2.5),
            vec![1., 1.5]
        );
        assert_eq!(
            allocate_space(vec![item(1., 1), item(1., 2)], 3.),
            vec![1., 2.]
        );
    }

    #[test]
    fn fixed_spaces() {
        assert_eq!(
            allocate_space(vec![item(1., 0), item(2., 1), item(2., 1)], 7.),
            vec![1., 3., 3.]
        );
        assert_eq!(
            allocate_space(vec![item(1., 0), item(2., 1), item(2., 2)], 7.),
            vec![1., 2., 4.]
        );
    }

    #[test]
    fn three_variable_sizes() {
        assert_approx_eq!(
            &[f32],
            &allocate_space(vec![item(1., 1), item(1., 2), item(2., 1)], 4.),
            &[1., 1., 2.]
        );
        assert_approx_eq!(
            &[f32],
            &allocate_space(vec![item(1., 1), item(1., 2), item(2., 1)], 4.5),
            &[1., 1.5, 2.]
        );
        assert_approx_eq!(
            &[f32],
            &allocate_space(vec![item(1., 1), item(1., 2), item(2., 1)], 5.),
            &[1., 2., 2.]
        );
        assert_approx_eq!(
            &[f32],
            &allocate_space(vec![item(1., 1), item(1., 2), item(2., 1)], 6.),
            &[4. / 3., 8. / 3., 2.]
        );
        assert_approx_eq!(
            &[f32],
            &allocate_space(vec![item(1., 1), item(1., 2), item(2., 1)], 7.),
            &[5. / 3., 10. / 3., 2.]
        );
        assert_approx_eq!(
            &[f32],
            &allocate_space(vec![item(1., 1), item(1., 2), item(2., 1)], 8.),
            &[2., 4., 2.]
        );
        assert_approx_eq!(
            &[f32],
            &allocate_space(vec![item(1., 1), item(1., 2), item(2., 0)], 7.),
            &[5. / 3., 10. / 3., 2.]
        );
    }
}

内容的提问来源于stack exchange,提问作者avs

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 01:15:54