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

如何为长度未知的列表实现Range的嵌套范围计算?

C# Range嵌套切片的实现问题

我正在构建一个扩展方法库,希望在合适场景中集成Range对象的使用。因此创建了一个类,用于表示任意列表的轻量级视图,该类基于给定的Range定义:

public static class ListSlice
{
    public static IReadOnlyList<T> Create<T>(IReadOnlyList<T> list, Range range) => new ListSlice<T>(list, range);
    public static IReadOnlyList<T> Create<T>(IReadOnlyList<T> list, Index start, Index end) => Create(list, new Range(start, end));
}

public class ListSlice<T> : IReadOnlyList<T>
{
    public ListSlice(IReadOnlyList<T> list, Range range)
    {
        (List, Range) = list is ListSlice<T> slice
            ? (slice.List, slice.Range.Apply(range)) // 此处尝试直接组合Range,存在问题
            : (list, range);
    }
    public IReadOnlyList<T> List { get; }
    public Range Range { get; }

    public T this[int index] => List[Range.GetOffsetAndLength(List.Count).Offset + index];
    public int Count => Range.GetOffsetAndLength(List.Count).Length;

    public IEnumerator<T> GetEnumerator()
    {
        var (offset, length) = Range.GetOffsetAndLength(List.Count);
        foreach (var i in Enumerable.Range(0, length))
            yield return List[offset + i];
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

核心问题

现在需要实现切片的切片功能:已知可以通过原始列表长度计算新的有效范围,但仅通过Range对象本身能否完成这种嵌套计算?

当Range的起始/结束为绝对数值时,计算逻辑很简单,但遇到包含相对结束位置(如^n)、开放式Range(如0..)的场景时,无法直接理清计算逻辑。比如:

// 绝对起始/结束,长度可直接通过二者差值得到
0..20    // 长度: 20
5..10    // 长度: 5
5..15    // 长度: 10

// 起始和结束均为相对结束位置时,长度仍是二者差值
^20..^0  // 长度: 20
^15..^10 // 长度: 5
^15..^5  // 长度: 10

// 开放式Range的长度必须结合集合长度才能确定
0..^10  // 长度: 集合长度 - 10
^5..20   // 长度: 20 - (集合长度 - 5) = 25 - 集合长度(需保证结果非负)
0..     // 长度: 集合长度(等价于 0..^0)

我曾尝试用固定的虚拟长度来计算,但这种方式会遗漏很多场景:

public static class RangeExtensions
{
    public static Range Apply(this Range range, int baseLength, Range subRange)
    {
        var (offset1, length1) = range.GetOffsetAndLength(baseLength);
        var (offset2, length2) = subRange.GetOffsetAndLength(length1);
        return (offset1 + offset2)..(offset1 + offset2 + length2);
    }

    public static Range Apply(this Range range, Range subRange)
    {
        // 简单场景: (5..10)(..2) -> (5..7)
        // 复杂场景: (5..10)(^2..) -> ???
        var dummyLength = 100; // 此处用固定值会导致错误
        var (offset1, length1) = range.GetOffsetAndLength(dummyLength);
        var (offset2, length2) = subRange.GetOffsetAndLength(length1);
        var offset = offset1 + offset2;
        return offset..(offset + length2); // 结果依赖虚拟长度,不可靠
    }
}

解决方案

结论:仅靠Range对象本身无法完成嵌套计算,因为Range本质是一个“解析规则”,相对索引和开放式Range的实际偏移/长度必须结合具体集合的长度才能确定。

正确的做法是在ListSlice<T>的构造函数中,始终基于原始列表的实际长度来计算最终的有效Range:

public ListSlice(IReadOnlyList<T> list, Range range)
{
    if (list is ListSlice<T> slice)
    {
        // 1. 获取原切片在原始列表上的偏移和长度
        var (baseOffset, baseLength) = slice.Range.GetOffsetAndLength(slice.List.Count);
        // 2. 计算子Range在原切片长度下的偏移和长度
        var (subOffset, subLength) = range.GetOffsetAndLength(baseLength);
        // 3. 推导最终在原始列表上的Range
        var finalStart = baseOffset + subOffset;
        var finalEnd = finalStart + subLength;
        List = slice.List;
        Range = finalStart..finalEnd;
    }
    else
    {
        List = list;
        Range = range;
    }
}

为什么这个方案可行?

  • 每次嵌套切片都回溯到原始列表的长度,确保所有Range(包括相对、开放式)都能被正确解析
  • 避免了虚拟长度带来的误差,所有计算都基于真实的集合长度
  • 最终的Range直接对应原始列表的范围,后续的索引访问、枚举都能直接正确映射

补充说明

  • 任何Range的长度都不能脱离具体集合长度独立计算,因为相对索引和开放式Range的语义本身就依赖于目标集合的长度
  • 如果需要单独处理Range的嵌套解析,必须传入对应的基准长度(即原Range对应的集合长度),这也是Apply(this Range range, int baseLength, Range subRange)方法可行的原因——只要传入真实的baseLength,就能得到正确结果

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 13:20:33