首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何将IList<T>分割成N个大小的段,而不创建副本和没有内存分配?

如何将IList<T>分割成N个大小的段,而不创建副本和没有内存分配?
EN

Stack Overflow用户
提问于 2020-12-10 06:58:19
回答 2查看 263关注 0票数 1

我有一个非常大的集合,它实现了通用IList接口,包含了数千万个元素,我想使用PLINQ并行处理它们。我注意到并行性的开销相当大,因为处理每个单独的元素都是非常轻量级的,所以我正在寻找通过将IList<T>分割成小段来分组处理的方法。我的目标是最终得到这样的东西:

代码语言:javascript
运行
复制
IList<Person> source = GetAllPersons();

double averageAge = source
    .Segmentate(1000) // Hypothetical operator that segmentates the source
    .AsParallel()
    .Select(segment => segment.Select(person => (double)person.CalculateAge()).Sum())
    .Sum() / source.Count;

我可以使用来自Batch库的MoreLinq操作符,或者使用许多 相关 问题的任何答案,但是所有这些解决方案都在分配多个小数组(或列表),并将源代码复制到这些容器中,我不想这样做。在我的例子中,我有一个额外的要求,就是尽可能多地保持垃圾收集器空闲。

我注意到.NET具有ArraySegment类型,这似乎完全符合我的要求:

代码语言:javascript
运行
复制
// Delimits a section of a one-dimensional array.
public readonly struct ArraySegment<T> : ICollection<T>, IEnumerable<T>,
    IEnumerable, IList<T>, IReadOnlyCollection<T>, IReadOnlyList<T>

我可以使用这种类型来实现这样的无分配Segmentate操作符:

代码语言:javascript
运行
复制
/// <summary>Segmentates the source array into sized segments.</summary>
public static IEnumerable<ArraySegment<T>> Segmentate<T>(this T[] source, int size)
{
    for (int offset = 0; offset < source.Length; offset += size)
    {
        yield return new ArraySegment<T>(source, offset,
            Math.Min(size, source.Length - offset));
    }
}

但是我不能使用这种类型,因为我的源是IList<T>而不是数组。而将其复制到数组并不是一个真正的选项,因为源经常发生变异。并且一直创建新的数组副本违背了我的要求。

因此,我正在搜索ListSegment<T>类型,但据我所知,它并不存在于.NET中,是否必须自己实现它?如果是,怎么做?或者是否有其他方法可以在不引起分配的情况下分割IList<T>

Clarification:我的源代码集合不是List。它是一个实现IList<T>接口的自定义类。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-12-10 10:04:26

您需要为IList<T>实现一个等价的IList<T>。见下文的实施。要获得最佳性能,请考虑使用跨度

ListSegment结构

代码语言:javascript
运行
复制
public readonly struct ListSegment<T> : IList<T>
{
    public List<T> Items { get; }
    public int Offset { get; }
    public int Count { get; }

    public ListSegment(List<T> items, int offset, int count)
    {
        Items = items ?? throw new ArgumentNullException(nameof(items));
        Offset = offset;
        Count = count;

        if (items.Count < offset + count)
        {
            throw new ArgumentException("List segment out of range.", nameof(count));
        }
    }

    public void CopyTo(T[] array, int index)
    {
        if (Count > 0)
        {
            Items.CopyTo(Offset, array, index, Count);
        }
    }

    public bool Contains(T item) => IndexOf(item) != -1;

    public int IndexOf(T item)
    {
        for (var i = Offset; i < Offset + Count; i++)
        {
            if (Items[i].Equals(item))
            {
                return i;
            }
        }

        return -1;
    }

    private T ElementAt(int index)
    {
        if (Count > 0)
        {
            return Items[Offset + index];
        }

        throw new ArgumentOutOfRangeException(nameof(index));
    }

    public ListSegmentEnumerator GetEnumerator() => new ListSegmentEnumerator(this);

    #region IEnumerable<T> interface
    IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    #endregion

    #region ICollection<T> interface
    bool ICollection<T>.IsReadOnly => true;

    void ICollection<T>.Add(T item) => throw new NotImplementedException();
    bool ICollection<T>.Remove(T item) => throw new NotImplementedException();
    void ICollection<T>.Clear() => throw new NotImplementedException();
    #endregion

    #region IList<T> interface
    void IList<T>.Insert(int index, T item) => throw new NotImplementedException();
    void IList<T>.RemoveAt(int index) => throw new NotImplementedException();
    T IList<T>.this[int index]
    {
        get => ElementAt(index);
        set => throw new NotImplementedException();
    }
    #endregion

    public struct ListSegmentEnumerator : IEnumerator<T>
    {
        private readonly List<T> items;
        private readonly int start;
        private readonly int end;
        private int current;

        public ListSegmentEnumerator(ListSegment<T> segment)
        {
            items = segment.Items;
            start = segment.Offset;
            end = start + segment.Count;
            current = start - 1;
        }

        public bool MoveNext()
        {
            if (current < end)
            {
                current++;

                return current < end;
            }
            return false;
        }

        public T Current => items[current];
        object IEnumerator.Current => Current;

        void IEnumerator.Reset() => current = start - 1;
        void IDisposable.Dispose() { }
    }
}
票数 4
EN

Stack Overflow用户

发布于 2020-12-10 07:08:45

这个答案假设您的具体IListList类型的。您可以使用GetRange函数,它可以满足您的需要:

引用类型集合的浅表副本或该集合的子集仅包含对集合元素的引用。对象本身不被复制。新列表中的引用指向与原始列表中的引用相同的对象。

甚至ArraySegment<T>也会创建某种引用对象来存储数组段,因此您不能完全避免这种情况。

如果您想避免存储引用(也称为浅拷贝),那么应该使用Span,但您的评论是您的集合更改与冲突。

在使用Span时,不应从列表中添加或删除项目。

所以,你唯一的解决办法就是自己创造一个,就像你提到的那样。

警告:有一个原因,为什么这样的东西不存在。数组的大小是固定的,因此获取一个段要安全得多。在创建这样的构造时,要小心意外的后果和副作用。这就是为什么Span警告你它是不安全的。您只知道您的需求以及您的数据是如何变化的,所以您的集合包装器应该考虑它们并相应地处理它们。

票数 -1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65229811

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档