[C#] LINQ를 사용하여 컬렉션을 ‘n’부분으로 분할 하시겠습니까?

nLINQ를 사용 하여 컬렉션을 여러 부분으로 분할하는 좋은 방법이 있습니까? 물론 균등할 필요는 없습니다.

즉, 컬렉션을 하위 컬렉션으로 나누고 싶습니다. 각 하위 컬렉션에는 요소의 하위 집합이 포함되어 있으며 마지막 컬렉션은 비정형 일 수 있습니다.



답변

순수한 linq와 가장 간단한 해결책은 아래와 같습니다.

static class LinqExtensions
{
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
    {
        int i = 0;
        var splits = from item in list
                     group item by i++ % parts into part
                     select part.AsEnumerable();
        return splits;
    }
}


답변

편집 : 좋아, 내가 질문을 잘못 읽은 것 같습니다. 나는 그것을 “n 개”라기보다는 “길이 n 개”로 읽었다. 도! 답변 삭제 고려 중 …

(원래 답변)

LINQ to Objects에 대한 추가 항목 집합에 하나를 작성하려고하지만 기본 제공 분할 방법이 있다고 생각하지 않습니다. Marc Gravell은 여기구현이 있지만 읽기 전용 뷰를 반환하도록 수정합니다.

public static IEnumerable<IEnumerable<T>> Partition<T>
    (this IEnumerable<T> source, int size)
{
    T[] array = null;
    int count = 0;
    foreach (T item in source)
    {
        if (array == null)
        {
            array = new T[size];
        }
        array[count] = item;
        count++;
        if (count == size)
        {
            yield return new ReadOnlyCollection<T>(array);
            array = null;
            count = 0;
        }
    }
    if (array != null)
    {
        Array.Resize(ref array, count);
        yield return new ReadOnlyCollection<T>(array);
    }
}


답변

static class LinqExtensions
{
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
    {
            return list.Select((item, index) => new {index, item})
                       .GroupBy(x => x.index % parts)
                       .Select(x => x.Select(y => y.item));
    }
}


답변

좋아, 반지에 모자를 던질 게. 내 알고리즘의 장점 :

  1. 값 비싼 곱셈, 나눗셈 또는 모듈러스 연산자 없음
  2. 모든 작업은 O (1)입니다 (아래 참고 참조).
  3. IEnumerable <> 소스에서 작동합니다 (Count 속성이 필요하지 않음).
  4. 단순한

코드:

public static IEnumerable<IEnumerable<T>>
  Section<T>(this IEnumerable<T> source, int length)
{
  if (length <= 0)
    throw new ArgumentOutOfRangeException("length");

  var section = new List<T>(length);

  foreach (var item in source)
  {
    section.Add(item);

    if (section.Count == length)
    {
      yield return section.AsReadOnly();
      section = new List<T>(length);
    }
  }

  if (section.Count > 0)
    yield return section.AsReadOnly();
}

아래 주석에서 지적했듯이이 접근 방식은 거의 동일한 길이의 고정 된 수의 섹션을 요구하는 원래 질문을 실제로 해결하지 않습니다. 즉, 내 접근 방식을 사용하여 원래 질문을 다음과 같이 호출하여 해결할 수 있습니다.

myEnum.Section(myEnum.Count() / number_of_sections + 1)

이 방식으로 사용하면 Count () 연산이 O (N)이므로 접근 방식은 더 이상 O (1)이 아닙니다.


답변

이것은 받아 들여지는 대답과 동일하지만 훨씬 더 간단한 표현입니다.

public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> items,
                                                   int numOfParts)
{
    int i = 0;
    return items.GroupBy(x => i++ % numOfParts);
}

위의 방법은을 IEnumerable<T>크기가 같거나 크기가 비슷한 N 개의 청크로 분할 합니다.

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items,
                                                       int partitionSize)
{
    int i = 0;
    return items.GroupBy(x => i++ / partitionSize).ToArray();
}

위의 방법은를 IEnumerable<T>원하는 고정 크기의 청크로 분할하고 총 청크 수는 중요하지 않습니다. 이것은 질문의 내용이 아닙니다.

Split방법 의 문제점 은 속도가 느리다는 것 외에도 그룹화가 각 위치에 대해 N의 i 배수를 기반으로 수행된다는 의미에서 출력을 스크램블하거나 즉, 청크를 얻지 못한다는 것입니다. 원래 순서대로.

여기에있는 거의 모든 대답은 순서를 유지하지 않거나 분할하지 않고 분할하는 것에 관한 것이거나 명백히 잘못된 것입니다. 더 빠르고, 순서는 유지하지만 좀 더 장황한 방법을 시도해보십시오.

public static IEnumerable<IEnumerable<T>> Split<T>(this ICollection<T> items,
                                                   int numberOfChunks)
{
    if (numberOfChunks <= 0 || numberOfChunks > items.Count)
        throw new ArgumentOutOfRangeException("numberOfChunks");

    int sizePerPacket = items.Count / numberOfChunks;
    int extra = items.Count % numberOfChunks;

    for (int i = 0; i < numberOfChunks - extra; i++)
        yield return items.Skip(i * sizePerPacket).Take(sizePerPacket);

    int alreadyReturnedCount = (numberOfChunks - extra) * sizePerPacket;
    int toReturnCount = extra == 0 ? 0 : (items.Count - numberOfChunks) / extra + 1;
    for (int i = 0; i < extra; i++)
        yield return items.Skip(alreadyReturnedCount + i * toReturnCount).Take(toReturnCount);
}

여기 에서 Partition작업에 해당하는 방법


답변

이전에 게시 한 파티션 기능을 자주 사용하고 있습니다. 그것의 유일한 나쁜 점은 완전히 스트리밍되지 않는다는 것입니다. 시퀀스에서 몇 가지 요소로 작업하는 경우 문제가되지 않습니다. 시퀀스에서 10,000 개 이상의 요소로 작업을 시작했을 때 새로운 솔루션이 필요했습니다.

다음 솔루션은 훨씬 더 복잡하지만 (더 많은 코드!) 매우 효율적입니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace LuvDaSun.Linq
{
    public static class EnumerableExtensions
    {
        public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> enumerable, int partitionSize)
        {
            /*
            return enumerable
                .Select((item, index) => new { Item = item, Index = index, })
                .GroupBy(item => item.Index / partitionSize)
                .Select(group => group.Select(item => item.Item)                )
                ;
            */

            return new PartitioningEnumerable<T>(enumerable, partitionSize);
        }

    }


    class PartitioningEnumerable<T> : IEnumerable<IEnumerable<T>>
    {
        IEnumerable<T> _enumerable;
        int _partitionSize;
        public PartitioningEnumerable(IEnumerable<T> enumerable, int partitionSize)
        {
            _enumerable = enumerable;
            _partitionSize = partitionSize;
        }

        public IEnumerator<IEnumerable<T>> GetEnumerator()
        {
            return new PartitioningEnumerator<T>(_enumerable.GetEnumerator(), _partitionSize);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }


    class PartitioningEnumerator<T> : IEnumerator<IEnumerable<T>>
    {
        IEnumerator<T> _enumerator;
        int _partitionSize;
        public PartitioningEnumerator(IEnumerator<T> enumerator, int partitionSize)
        {
            _enumerator = enumerator;
            _partitionSize = partitionSize;
        }

        public void Dispose()
        {
            _enumerator.Dispose();
        }

        IEnumerable<T> _current;
        public IEnumerable<T> Current
        {
            get { return _current; }
        }
        object IEnumerator.Current
        {
            get { return _current; }
        }

        public void Reset()
        {
            _current = null;
            _enumerator.Reset();
        }

        public bool MoveNext()
        {
            bool result;

            if (_enumerator.MoveNext())
            {
                _current = new PartitionEnumerable<T>(_enumerator, _partitionSize);
                result = true;
            }
            else
            {
                _current = null;
                result = false;
            }

            return result;
        }

    }



    class PartitionEnumerable<T> : IEnumerable<T>
    {
        IEnumerator<T> _enumerator;
        int _partitionSize;
        public PartitionEnumerable(IEnumerator<T> enumerator, int partitionSize)
        {
            _enumerator = enumerator;
            _partitionSize = partitionSize;
        }

        public IEnumerator<T> GetEnumerator()
        {
            return new PartitionEnumerator<T>(_enumerator, _partitionSize);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }


    class PartitionEnumerator<T> : IEnumerator<T>
    {
        IEnumerator<T> _enumerator;
        int _partitionSize;
        int _count;
        public PartitionEnumerator(IEnumerator<T> enumerator, int partitionSize)
        {
            _enumerator = enumerator;
            _partitionSize = partitionSize;
        }

        public void Dispose()
        {
        }

        public T Current
        {
            get { return _enumerator.Current; }
        }
        object IEnumerator.Current
        {
            get { return _enumerator.Current; }
        }
        public void Reset()
        {
            if (_count > 0) throw new InvalidOperationException();
        }

        public bool MoveNext()
        {
            bool result;

            if (_count < _partitionSize)
            {
                if (_count > 0)
                {
                    result = _enumerator.MoveNext();
                }
                else
                {
                    result = true;
                }
                _count++;
            }
            else
            {
                result = false;
            }

            return result;
        }

    }
}

즐겨!


답변

흥미로운 스레드. Split / Partition의 스트리밍 버전을 얻으려면 열거자를 사용하고 확장 메서드를 사용하여 열거 자에서 시퀀스를 생성 할 수 있습니다. yield를 사용하여 명령형 코드를 기능 코드로 변환하는 것은 실제로 매우 강력한 기술입니다.

먼저 요소 수를 지연 시퀀스로 바꾸는 열거 자 확장 :

public static IEnumerable<T> TakeFromCurrent<T>(this IEnumerator<T> enumerator, int count)
{
    while (count > 0)
    {
        yield return enumerator.Current;
        if (--count > 0 && !enumerator.MoveNext()) yield break;
    }
}

그리고 시퀀스를 분할하는 열거 가능한 확장 :

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> seq, int partitionSize)
{
    var enumerator = seq.GetEnumerator();

    while (enumerator.MoveNext())
    {
        yield return enumerator.TakeFromCurrent(partitionSize);
    }
}

최종 결과는 매우 간단한 코드에 의존하는 매우 효율적이고 스트리밍 및 지연 구현입니다.

즐겨!