Programing

LINQ를 사용하여 목록을 하위 목록으로 분할

lottogame 2020. 2. 29. 13:34
반응형

LINQ를 사용하여 목록을 하위 목록으로 분할


항목 인덱스를 각 분할의 구분 기호로 사용하여의 List<SomeObject>여러 개별 목록으로 분리 할 수있는 방법이 SomeObject있습니까?

예를 들어 보겠습니다.

나는이 List<SomeObject>와 내가 필요 List<List<SomeObject>>하거나 List<SomeObject>[]이러한 결과 목록의 각은 원래 목록 (순차적으로) 3 개 항목의 그룹을 포함 할 수 있도록.

예 :

  • 원본 목록 : [a, g, e, w, p, s, q, f, x, y, i, m, c]

  • 결과 목록 : [a, g, e], [w, p, s], [q, f, x], [y, i, m], [c]

또한 결과 목록 크기 가이 함수의 매개 변수가되어야합니다.


다음 코드를 시도하십시오.

public static IList<IList<T>> Split<T>(IList<T> source)
{
    return  source
        .Select((x, i) => new { Index = i, Value = x })
        .GroupBy(x => x.Index / 3)
        .Select(x => x.Select(v => v.Value).ToList())
        .ToList();
}

아이디어는 먼저 요소를 색인별로 그룹화하는 것입니다. 세 가지로 나누면 다음 목록에 각 그룹을 변환 3의 그룹으로 그룹화의 효과와가 IEnumerableListA와 ListList


이 질문은 조금 낡았지만 방금 작성했으며 다른 제안 된 솔루션보다 조금 더 우아하다고 생각합니다.

/// <summary>
/// Break a list of items into chunks of a specific size
/// </summary>
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)
{
    while (source.Any())
    {
        yield return source.Take(chunksize);
        source = source.Skip(chunksize);
    }
}

일반적으로 CaseyB제안한 접근 방식 은 잘 작동합니다. 실제로 전달하는 List<T>경우 오류가 발생하기 어려울 수 있습니다. 아마도 다음과 같이 변경합니다.

public static IEnumerable<IEnumerable<T>> ChunkTrivialBetter<T>(this IEnumerable<T> source, int chunksize)
{
   var pos = 0; 
   while (source.Skip(pos).Any())
   {
      yield return source.Skip(pos).Take(chunksize);
      pos += chunksize;
   }
}

대규모 콜 체인을 피할 수 있습니다. 그럼에도 불구하고,이 접근법에는 일반적인 결함이 있습니다. 실행 시도 문제를 강조하기 위해 청크 당 두 개의 열거를 구체화합니다.

foreach (var item in Enumerable.Range(1, int.MaxValue).Chunk(8).Skip(100000).First())
{
   Console.WriteLine(item);
}
// wait forever 

이것을 극복하기 위해 우리는 Cameron의 접근 방식을 시도 할 수 있습니다. 카메론의 접근 방식은 열거 형을 한 번만 걷기 때문에 위의 테스트를 비행 색상으로 통과시킵니다.

문제는 다른 결함이 있으며 각 청크의 모든 항목을 구체화한다는 것입니다.이 접근법의 문제점은 메모리가 부족하다는 것입니다.

실행을 설명하기 위해 :

foreach (var item in Enumerable.Range(1, int.MaxValue)
               .Select(x => x + new string('x', 100000))
               .Clump(10000).Skip(100).First())
{
   Console.Write('.');
}
// OutOfMemoryException

마지막으로, 모든 구현은 다음과 같이 비 순차적 청크 반복을 처리 할 수 ​​있어야합니다.

Enumerable.Range(1,3).Chunk(2).Reverse().ToArray()
// should return [3],[1,2]

이 답변의 첫 번째 개정판 과 같은 많은 최적의 솔루션 이 거기에서 실패했습니다. casperOne의 최적화 된 답변 에서도 같은 문제를 볼 수 있습니다 .

이러한 모든 문제를 해결하기 위해 다음을 사용할 수 있습니다.

namespace ChunkedEnumerator
{
    public static class Extensions 
    {
        class ChunkedEnumerable<T> : IEnumerable<T>
        {
            class ChildEnumerator : IEnumerator<T>
            {
                ChunkedEnumerable<T> parent;
                int position;
                bool done = false;
                T current;


                public ChildEnumerator(ChunkedEnumerable<T> parent)
                {
                    this.parent = parent;
                    position = -1;
                    parent.wrapper.AddRef();
                }

                public T Current
                {
                    get
                    {
                        if (position == -1 || done)
                        {
                            throw new InvalidOperationException();
                        }
                        return current;

                    }
                }

                public void Dispose()
                {
                    if (!done)
                    {
                        done = true;
                        parent.wrapper.RemoveRef();
                    }
                }

                object System.Collections.IEnumerator.Current
                {
                    get { return Current; }
                }

                public bool MoveNext()
                {
                    position++;

                    if (position + 1 > parent.chunkSize)
                    {
                        done = true;
                    }

                    if (!done)
                    {
                        done = !parent.wrapper.Get(position + parent.start, out current);
                    }

                    return !done;

                }

                public void Reset()
                {
                    // per http://msdn.microsoft.com/en-us/library/system.collections.ienumerator.reset.aspx
                    throw new NotSupportedException();
                }
            }

            EnumeratorWrapper<T> wrapper;
            int chunkSize;
            int start;

            public ChunkedEnumerable(EnumeratorWrapper<T> wrapper, int chunkSize, int start)
            {
                this.wrapper = wrapper;
                this.chunkSize = chunkSize;
                this.start = start;
            }

            public IEnumerator<T> GetEnumerator()
            {
                return new ChildEnumerator(this);
            }

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

        }

        class EnumeratorWrapper<T>
        {
            public EnumeratorWrapper (IEnumerable<T> source)
            {
                SourceEumerable = source;
            }
            IEnumerable<T> SourceEumerable {get; set;}

            Enumeration currentEnumeration;

            class Enumeration
            {
                public IEnumerator<T> Source { get; set; }
                public int Position { get; set; }
                public bool AtEnd { get; set; }
            }

            public bool Get(int pos, out T item) 
            {

                if (currentEnumeration != null && currentEnumeration.Position > pos)
                {
                    currentEnumeration.Source.Dispose();
                    currentEnumeration = null;
                }

                if (currentEnumeration == null)
                {
                    currentEnumeration = new Enumeration { Position = -1, Source = SourceEumerable.GetEnumerator(), AtEnd = false };
                }

                item = default(T);
                if (currentEnumeration.AtEnd)
                {
                    return false;
                }

                while(currentEnumeration.Position < pos) 
                {
                    currentEnumeration.AtEnd = !currentEnumeration.Source.MoveNext();
                    currentEnumeration.Position++;

                    if (currentEnumeration.AtEnd) 
                    {
                        return false;
                    }

                }

                item = currentEnumeration.Source.Current;

                return true;
            }

            int refs = 0;

            // needed for dispose semantics 
            public void AddRef()
            {
                refs++;
            }

            public void RemoveRef()
            {
                refs--;
                if (refs == 0 && currentEnumeration != null)
                {
                    var copy = currentEnumeration;
                    currentEnumeration = null;
                    copy.Source.Dispose();
                }
            }
        }

        public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)
        {
            if (chunksize < 1) throw new InvalidOperationException();

            var wrapper =  new EnumeratorWrapper<T>(source);

            int currentPos = 0;
            T ignore;
            try
            {
                wrapper.AddRef();
                while (wrapper.Get(currentPos, out ignore))
                {
                    yield return new ChunkedEnumerable<T>(wrapper, chunksize, currentPos);
                    currentPos += chunksize;
                }
            }
            finally
            {
                wrapper.RemoveRef();
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            int i = 10;
            foreach (var group in Enumerable.Range(1, int.MaxValue).Skip(10000000).Chunk(3))
            {
                foreach (var n in group)
                {
                    Console.Write(n);
                    Console.Write(" ");
                }
                Console.WriteLine();
                if (i-- == 0) break;
            }


            var stuffs = Enumerable.Range(1, 10).Chunk(2).ToArray();

            foreach (var idx in new [] {3,2,1})
            {
                Console.Write("idx " + idx + " ");
                foreach (var n in stuffs[idx])
                {
                    Console.Write(n);
                    Console.Write(" ");
                }
                Console.WriteLine();
            }

            /*

10000001 10000002 10000003
10000004 10000005 10000006
10000007 10000008 10000009
10000010 10000011 10000012
10000013 10000014 10000015
10000016 10000017 10000018
10000019 10000020 10000021
10000022 10000023 10000024
10000025 10000026 10000027
10000028 10000029 10000030
10000031 10000032 10000033
idx 3 7 8
idx 2 5 6
idx 1 3 4
             */

            Console.ReadKey();


        }

    }
}

또한 범위를 벗어난 청크의 비 순차적 반복에 대해 소개 할 수있는 최적화 과정도 있습니다.

어떤 방법을 선택해야합니까? 그것은 완전히 해결하려는 문제에 달려 있습니다. 첫 번째 결함에 관심이 없다면 간단한 답변이 매우 매력적입니다.

참고 대부분의 방법과 마찬가지로, 이것은 당신이 당신이 개정 할 필요가 스레드 안전 확인하고자하는 경우 멀티 스레딩을 위해, 물건 이상한 얻을 수있는 안전하지 않습니다 EnumeratorWrapper.


당신은 할 수 사용하는 쿼리의 숫자 사용 TakeSkip,하지만 원래 목록에 너무 많은 반복을 추가, 저는 믿습니다.

오히려 다음과 같이 자신 만의 반복자를 만들어야한다고 생각합니다.

public static IEnumerable<IEnumerable<T>> GetEnumerableOfEnumerables<T>(
  IEnumerable<T> enumerable, int groupSize)
{
   // The list to return.
   List<T> list = new List<T>(groupSize);

   // Cycle through all of the items.
   foreach (T item in enumerable)
   {
     // Add the item.
     list.Add(item);

     // If the list has the number of elements, return that.
     if (list.Count == groupSize)
     {
       // Return the list.
       yield return list;

       // Set the list to a new list.
       list = new List<T>(groupSize);
     }
   }

   // Return the remainder if there is any,
   if (list.Count != 0)
   {
     // Return the list.
     yield return list;
   }
}

그런 다음 이것을 호출하면 LINQ가 활성화되어 결과 시퀀스에서 다른 작업을 수행 할 수 있습니다.


Sam의 답변에 비추어 볼 때 , 나는 이것을하지 않고 이것을 수행하는 더 쉬운 방법이 있다고 느꼈습니다.

  • 목록을 다시 반복합니다 (원래는하지 않았습니다)
  • 청크를 해제하기 전에 그룹으로 항목을 구체화합니다 (큰 청크의 경우 메모리 문제가 있음)
  • Sam이 게시 한 모든 코드

즉 나는 여기에 확장 메서드에 성문화 한 또 다른 패스,의, 말했다 IEnumerable<T>이라고는 Chunk:

public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, 
    int chunkSize)
{
    // Validate parameters.
    if (source == null) throw new ArgumentNullException("source");
    if (chunkSize <= 0) throw new ArgumentOutOfRangeException("chunkSize",
        "The chunkSize parameter must be a positive value.");

    // Call the internal implementation.
    return source.ChunkInternal(chunkSize);
}

기본 오류 확인만으로 놀라운 것은 없습니다.

다음으로 넘어갑니다 ChunkInternal.

private static IEnumerable<IEnumerable<T>> ChunkInternal<T>(
    this IEnumerable<T> source, int chunkSize)
{
    // Validate parameters.
    Debug.Assert(source != null);
    Debug.Assert(chunkSize > 0);

    // Get the enumerator.  Dispose of when done.
    using (IEnumerator<T> enumerator = source.GetEnumerator())
    do
    {
        // Move to the next element.  If there's nothing left
        // then get out.
        if (!enumerator.MoveNext()) yield break;

        // Return the chunked sequence.
        yield return ChunkSequence(enumerator, chunkSize);
    } while (true);
}

기본적으로 IEnumerator<T>각 항목을 가져오고 수동으로 반복합니다. 현재 열거 할 항목이 있는지 확인합니다. 각 청크가 열거 된 후 남은 항목이 없으면 나옵니다.

시퀀스에 항목이 있음을 감지하면 내부 IEnumerable<T>구현에 대한 책임을 다음에 위임합니다 ChunkSequence.

private static IEnumerable<T> ChunkSequence<T>(IEnumerator<T> enumerator, 
    int chunkSize)
{
    // Validate parameters.
    Debug.Assert(enumerator != null);
    Debug.Assert(chunkSize > 0);

    // The count.
    int count = 0;

    // There is at least one item.  Yield and then continue.
    do
    {
        // Yield the item.
        yield return enumerator.Current;
    } while (++count < chunkSize && enumerator.MoveNext());
}

이후 MoveNext이미 호출 된 IEnumerator<T>전달 ChunkSequence, 그것은에 의해 반환 된 항목을 얻을 수 Current보다 더 많은 돌아 결코 확실하게하지, 다음 수를 증가 chunkSize항목마다 반복 한 후 순서의 다음 항목으로 이동 (그러나 수의 경우 단락 생산량은 청크 크기를 초과합니다).

항목이 남아 있지 않으면 InternalChunk메소드는 외부 루프에 다른 패스를 전달하지만 MoveNext두 번째로 호출 되면 문서 (강조 광산)에 따라 여전히 false를 반환합니다 .

MoveNext가 컬렉션의 끝을 통과하면 열거자는 컬렉션의 마지막 요소 뒤에 배치되고 MoveNext는 false를 반환합니다. 열거자가이 위치에있을 때 ResetNext가 호출 될 때까지 MoveNext에 대한 후속 호출도 false를 반환합니다.

이 시점에서 루프가 중단되고 시퀀스 시퀀스가 ​​종료됩니다.

이것은 간단한 테스트입니다.

static void Main()
{
    string s = "agewpsqfxyimc";

    int count = 0;

    // Group by three.
    foreach (IEnumerable<char> g in s.Chunk(3))
    {
        // Print out the group.
        Console.Write("Group: {0} - ", ++count);

        // Print the items.
        foreach (char c in g)
        {
            // Print the item.
            Console.Write(c + ", ");
        }

        // Finish the line.
        Console.WriteLine();
    }
}

산출:

Group: 1 - a, g, e,
Group: 2 - w, p, s,
Group: 3 - q, f, x,
Group: 4 - y, i, m,
Group: 5 - c,

중요한 점 은 전체 하위 시퀀스를 배수하거나 상위 시퀀스의 어느 시점에서든 중단하지 않으면 작동하지 않습니다. 이것은 중요한 경고이지만, 사용 사례가 시퀀스 시퀀스의 모든 요소를 소비한다는 것이면 이것이 효과가 있습니다.

또한 Sam이 한 시점에서했던 것처럼 주문을 가지고 놀면 이상한 일을 할 것 입니다.


좋아, 여기에 내가 가지고있다 :

  • 완전히 게으른 : 무한 열거 가능
  • 중간 복사 / 버퍼 없음
  • O (n) 실행 시간
  • 내부 시퀀스가 ​​부분적으로 만 소비되는 경우에도 작동

public static IEnumerable<IEnumerable<T>> Chunks<T>(this IEnumerable<T> enumerable,
                                                    int chunkSize)
{
    if (chunkSize < 1) throw new ArgumentException("chunkSize must be positive");

    using (var e = enumerable.GetEnumerator())
    while (e.MoveNext())
    {
        var remaining = chunkSize;    // elements remaining in the current chunk
        var innerMoveNext = new Func<bool>(() => --remaining > 0 && e.MoveNext());

        yield return e.GetChunk(innerMoveNext);
        while (innerMoveNext()) {/* discard elements skipped by inner iterator */}
    }
}

private static IEnumerable<T> GetChunk<T>(this IEnumerator<T> e,
                                          Func<bool> innerMoveNext)
{
    do yield return e.Current;
    while (innerMoveNext());
}

사용법 예

var src = new [] {1, 2, 3, 4, 5, 6}; 

var c3 = src.Chunks(3);      // {{1, 2, 3}, {4, 5, 6}}; 
var c4 = src.Chunks(4);      // {{1, 2, 3, 4}, {5, 6}}; 

var sum   = c3.Select(c => c.Sum());    // {6, 15}
var count = c3.Count();                 // 2
var take2 = c3.Select(c => c.Take(2));  // {{1, 2}, {4, 5}}

설명

이 코드는 두 개의 yield반복자 를 중첩하여 작동 합니다.

외부 반복자는 내부 (청크) 반복자가 효과적으로 소비 한 요소 수를 추적해야합니다. 로 종료하면 remaining됩니다 innerMoveNext(). 청크의 소비되지 않은 요소는 외부 이터레이터가 다음 청크를 생성하기 전에 삭제됩니다. 내부 열거 형이 (완전히) 소비되지 않을 때 (예를 들어 c3.Count()6을 반환 할 때) 일치하지 않는 결과가 나오기 때문에 필요 합니다.

참고 : @aolszowka가 지적한 단점을 해결하기 위해 답변이 업데이트되었습니다.


완전히 게 으르거나 계산 또는 복사하지 않음 :

public static class EnumerableExtensions
{

  public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int len)
  {
     if (len == 0)
        throw new ArgumentNullException();

     var enumer = source.GetEnumerator();
     while (enumer.MoveNext())
     {
        yield return Take(enumer.Current, enumer, len);
     }
  }

  private static IEnumerable<T> Take<T>(T head, IEnumerator<T> tail, int len)
  {
     while (true)
     {
        yield return head;
        if (--len == 0)
           break;
        if (tail.MoveNext())
           head = tail.Current;
        else
           break;
     }
  }
}

다음 제안이 가장 빠를 것이라고 생각합니다. Array.Copy를 사용할 수있는 기능으로 소스 Enumerable의 게으름을 희생하고 각 하위 목록의 길이를 미리 알고 있습니다.

public static IEnumerable<T[]> Chunk<T>(this IEnumerable<T> items, int size)
{
    T[] array = items as T[] ?? items.ToArray();
    for (int i = 0; i < array.Length; i+=size)
    {
        T[] chunk = new T[Math.Min(size, array.Length - i)];
        Array.Copy(array, i, chunk, 0, chunk.Length);
        yield return chunk;
    }
}

@JaredPar의 솔루션을 개선하여 진정한 지연 평가를 수행 할 수 있습니다. GroupAdjacentBy동일한 키로 연속 요소 그룹을 생성 하는 방법을 사용합니다 .

sequence
.Select((x, i) => new { Value = x, Index = i })
.GroupAdjacentBy(x=>x.Index/3)
.Select(g=>g.Select(x=>x.Value))

그룹은 하나씩 생성되므로이 솔루션은 길거나 무한한 시퀀스로 효율적으로 작동합니다.


몇 년 전에 Clump extension 방법을 작성했습니다. 훌륭하게 작동하며 여기서 가장 빠른 구현입니다. :피

/// <summary>
/// Clumps items into same size lots.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source list of items.</param>
/// <param name="size">The maximum size of the clumps to make.</param>
/// <returns>A list of list of items, where each list of items is no bigger than the size given.</returns>
public static IEnumerable<IEnumerable<T>> Clump<T>(this IEnumerable<T> source, int size)
{
    if (source == null)
        throw new ArgumentNullException("source");
    if (size < 1)
        throw new ArgumentOutOfRangeException("size", "size must be greater than 0");

    return ClumpIterator<T>(source, size);
}

private static IEnumerable<IEnumerable<T>> ClumpIterator<T>(IEnumerable<T> source, int size)
{
    Debug.Assert(source != null, "source is null.");

    T[] items = new T[size];
    int count = 0;
    foreach (var item in source)
    {
        items[count] = item;
        count++;

        if (count == size)
        {
            yield return items;
            items = new T[size];
            count = 0;
        }
    }
    if (count > 0)
    {
        if (count == size)
            yield return items;
        else
        {
            T[] tempItems = new T[count];
            Array.Copy(items, tempItems, count);
            yield return tempItems;
        }
    }
}

Buffer()이를 위해 System.Interactive가 제공 됩니다. 일부 빠른 테스트에 따르면 성능은 Sam의 솔루션과 유사합니다.


몇 달 전에 쓴 목록 분할 루틴은 다음과 같습니다.

public static List<List<T>> Chunk<T>(
    List<T> theList,
    int chunkSize
)
{
    List<List<T>> result = theList
        .Select((x, i) => new {
            data = x,
            indexgroup = i / chunkSize
        })
        .GroupBy(x => x.indexgroup, x => x.data)
        .Select(g => new List<T>(g))
        .ToList();

    return result;
}

이것은 오래된 질문이지만 이것이 내가 끝낸 것입니다. 열거 형을 한 번만 열거하지만 각 파티션에 대한 목록을 만듭니다. ToArray()일부 구현에서와 같이 호출 될 때 예기치 않은 동작으로 고통받지 않습니다 .

    public static IEnumerable<IEnumerable<T>> Partition<T>(IEnumerable<T> source, int chunkSize)
    {
        if (source == null)
        {
            throw new ArgumentNullException("source");
        }

        if (chunkSize < 1)
        {
            throw new ArgumentException("Invalid chunkSize: " + chunkSize);
        }

        using (IEnumerator<T> sourceEnumerator = source.GetEnumerator())
        {
            IList<T> currentChunk = new List<T>();
            while (sourceEnumerator.MoveNext())
            {
                currentChunk.Add(sourceEnumerator.Current);
                if (currentChunk.Count == chunkSize)
                {
                    yield return currentChunk;
                    currentChunk = new List<T>();
                }
            }

            if (currentChunk.Any())
            {
                yield return currentChunk;
            }
        }
    }

나는이 작은 발췌 문장이 꽤 잘 작동합니다.

public static IEnumerable<List<T>> Chunked<T>(this List<T> source, int chunkSize)
{
    var offset = 0;

    while (offset < source.Count)
    {
        yield return source.GetRange(offset, Math.Min(source.Count - offset, chunkSize));
        offset += chunkSize;
    }
}

David B의 솔루션이 가장 효과적이라는 것을 알았습니다. 그러나 우리는 더 일반적인 솔루션에 적용했습니다.

list.GroupBy(item => item.SomeProperty) 
   .Select(group => new List<T>(group)) 
   .ToArray();

이건 어때?

var input = new List<string> { "a", "g", "e", "w", "p", "s", "q", "f", "x", "y", "i", "m", "c" };
var k = 3

var res = Enumerable.Range(0, (input.Count - 1) / k + 1)
                    .Select(i => input.GetRange(i * k, Math.Min(k, input.Count - i * k)))
                    .ToList();

내가 아는 한 GetRange () 는 가져온 항목 수의 관점에서 선형입니다. 따라서 이것은 잘 수행되어야합니다.


이 다음 솔루션은 내가 얻을 수있는 가장 컴팩트 한 것은 O (n)입니다.

public static IEnumerable<T[]> Chunk<T>(IEnumerable<T> source, int chunksize)
{
    var list = source as IList<T> ?? source.ToList();
    for (int start = 0; start < list.Count; start += chunksize)
    {
        T[] chunk = new T[Math.Min(chunksize, list.Count - start)];
        for (int i = 0; i < chunk.Length; i++)
            chunk[i] = list[start + i];

        yield return chunk;
    }
}

오래된 코드이지만 이것이 내가 사용한 것입니다.

    public static IEnumerable<List<T>> InSetsOf<T>(this IEnumerable<T> source, int max)
    {
        var toReturn = new List<T>(max);
        foreach (var item in source)
        {
            toReturn.Add(item);
            if (toReturn.Count == max)
            {
                yield return toReturn;
                toReturn = new List<T>(max);
            }
        }
        if (toReturn.Any())
        {
            yield return toReturn;
        }
    }

목록이 system.collections.generic 유형 인 경우 사용 가능한 "CopyTo"메소드를 사용하여 배열의 요소를 다른 하위 배열로 복사 할 수 있습니다. 복사 할 시작 요소 및 요소 수를 지정합니다.

원본 목록을 3 개 복제 한 다음 각 목록에서 "RemoveRange"를 사용하여 목록을 원하는 크기로 축소 할 수도 있습니다.

또는 도우미 메소드를 작성하여 수행하십시오.


오래된 솔루션이지만 다른 접근 방식이 있습니다. 내가 사용 Skip목적의 오프셋 (offset)와 이동 Take요소의 추출 원하는 번호 :

public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, 
                                                   int chunkSize)
{
    if (chunkSize <= 0)
        throw new ArgumentOutOfRangeException($"{nameof(chunkSize)} should be > 0");

    var nbChunks = (int)Math.Ceiling((double)source.Count()/chunkSize);

    return Enumerable.Range(0, nbChunks)
                     .Select(chunkNb => source.Skip(chunkNb*chunkSize)
                     .Take(chunkSize));
}

패키지 / 유지 관리 솔루션에 관심이있는 사용자를 위해 MoreLINQ 라이브러리는 Batch요청한 동작과 일치 하는 확장 방법을 제공합니다 .

IEnumerable<char> source = "Example string";
IEnumerable<IEnumerable<char>> chunksOfThreeChars = source.Batch(3);

Batch구현은 유사하다 카메론 MacFarland의 답변을 반환하기 전에 청크 / 배치를 변환하기위한 과부하의 추가와 함께, 그리고 수행 아주 잘.


모듈 식 파티셔닝 사용 :

public IEnumerable<IEnumerable<string>> Split(IEnumerable<string> input, int chunkSize)
{
    var chunks = (int)Math.Ceiling((double)input.Count() / (double)chunkSize);
    return Enumerable.Range(0, chunks).Select(id => input.Where(s => s.GetHashCode() % chunks == id));
}

그냥 내 두 센트를 넣어. 목록을 "버킷"(왼쪽에서 오른쪽으로 시각화)하려는 경우 다음을 수행 할 수 있습니다.

 public static List<List<T>> Buckets<T>(this List<T> source, int numberOfBuckets)
    {
        List<List<T>> result = new List<List<T>>();
        for (int i = 0; i < numberOfBuckets; i++)
        {
            result.Add(new List<T>());
        }

        int count = 0;
        while (count < source.Count())
        {
            var mod = count % numberOfBuckets;
            result[mod].Add(source[count]);
            count++;
        }
        return result;
    }

다른 방법은 Rx 버퍼 연산자를 사용하는 것입니다

//using System.Linq;
//using System.Reactive.Linq;
//using System.Reactive.Threading.Tasks;

var observableBatches = anAnumerable.ToObservable().Buffer(size);

var batches = aList.ToObservable().Buffer(size).ToList().ToTask().GetAwaiter().GetResult();

나는 주요 대답을 취하여 분할 할 곳을 결정하는 IOC 컨테이너로 만들었습니다. ( 대답을 검색하는 동안이 게시물을 읽을 때 3 항목으로 만 분할하려는 사람은 누구입니까? )

이 방법을 사용하면 필요에 따라 모든 유형의 항목을 분할 할 수 있습니다.

public static List<List<T>> SplitOn<T>(List<T> main, Func<T, bool> splitOn)
{
    int groupIndex = 0;

    return main.Select( item => new 
                             { 
                               Group = (splitOn.Invoke(item) ? ++groupIndex : groupIndex), 
                               Value = item 
                             })
                .GroupBy( it2 => it2.Group)
                .Select(x => x.Select(v => v.Value).ToList())
                .ToList();
}

따라서 OP의 경우 코드는

var it = new List<string>()
                       { "a", "g", "e", "w", "p", "s", "q", "f", "x", "y", "i", "m", "c" };

int index = 0; 
var result = SplitOn(it, (itm) => (index++ % 3) == 0 );

Sam Saffron 의 접근 방식 만큼 성능이 뛰어납니다 .

public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size)
{
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size), "Size must be greater than zero.");

    return BatchImpl(source, size).TakeWhile(x => x.Any());
}

static IEnumerable<IEnumerable<T>> BatchImpl<T>(this IEnumerable<T> source, int size)
{
    var values = new List<T>();
    var group = 1;
    var disposed = false;
    var e = source.GetEnumerator();

    try
    {
        while (!disposed)
        {
            yield return GetBatch(e, values, group, size, () => { e.Dispose(); disposed = true; });
            group++;
        }
    }
    finally
    {
        if (!disposed)
            e.Dispose();
    }
}

static IEnumerable<T> GetBatch<T>(IEnumerator<T> e, List<T> values, int group, int size, Action dispose)
{
    var min = (group - 1) * size + 1;
    var max = group * size;
    var hasValue = false;

    while (values.Count < min && e.MoveNext())
    {
        values.Add(e.Current);
    }

    for (var i = min; i <= max; i++)
    {
        if (i <= values.Count)
        {
            hasValue = true;
        }
        else if (hasValue = e.MoveNext())
        {
            values.Add(e.Current);
        }
        else
        {
            dispose();
        }

        if (hasValue)
            yield return values[i - 1];
        else
            yield break;
    }
}

}


무한 발전기로 작업 할 수 있습니다 :

a.Zip(a.Skip(1), (x, y) => Enumerable.Repeat(x, 1).Concat(Enumerable.Repeat(y, 1)))
 .Zip(a.Skip(2), (xy, z) => xy.Concat(Enumerable.Repeat(z, 1)))
 .Where((x, i) => i % 3 == 0)

데모 코드 : https://ideone.com/GKmL7M

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

public class Test
{
  private static void DoIt(IEnumerable<int> a)
  {
    Console.WriteLine(String.Join(" ", a));

    foreach (var x in a.Zip(a.Skip(1), (x, y) => Enumerable.Repeat(x, 1).Concat(Enumerable.Repeat(y, 1))).Zip(a.Skip(2), (xy, z) => xy.Concat(Enumerable.Repeat(z, 1))).Where((x, i) => i % 3 == 0))
      Console.WriteLine(String.Join(" ", x));

    Console.WriteLine();
  }

  public static void Main()
  {
    DoIt(new int[] {1});
    DoIt(new int[] {1, 2});
    DoIt(new int[] {1, 2, 3});
    DoIt(new int[] {1, 2, 3, 4});
    DoIt(new int[] {1, 2, 3, 4, 5});
    DoIt(new int[] {1, 2, 3, 4, 5, 6});
  }
}
1

1 2

1 2 3
1 2 3

1 2 3 4
1 2 3

1 2 3 4 5
1 2 3

1 2 3 4 5 6
1 2 3
4 5 6

그러나 실제로 linq없이 해당 메소드를 작성하는 것을 선호합니다.


이것 좀 봐! 시퀀스 카운터와 날짜가있는 요소 목록이 있습니다. 시퀀스가 다시 시작될 때마다 새 목록을 만들고 싶습니다.

전의. 메시지 목록.

 List<dynamic> messages = new List<dynamic>
        {
            new { FcntUp = 101, CommTimestamp = "2019-01-01 00:00:01" },
            new { FcntUp = 102, CommTimestamp = "2019-01-01 00:00:02" },
            new { FcntUp = 103, CommTimestamp = "2019-01-01 00:00:03" },

            //restart of sequence
            new { FcntUp = 1, CommTimestamp = "2019-01-01 00:00:04" },
            new { FcntUp = 2, CommTimestamp = "2019-01-01 00:00:05" },
            new { FcntUp = 3, CommTimestamp = "2019-01-01 00:00:06" },

            //restart of sequence
            new { FcntUp = 1, CommTimestamp = "2019-01-01 00:00:07" },
            new { FcntUp = 2, CommTimestamp = "2019-01-01 00:00:08" },
            new { FcntUp = 3, CommTimestamp = "2019-01-01 00:00:09" }
        };

카운터가 다시 시작될 때 목록을 별도의 목록으로 나누고 싶습니다. 코드는 다음과 같습니다.

var arraylist = new List<List<dynamic>>();

        List<dynamic> messages = new List<dynamic>
        {
            new { FcntUp = 101, CommTimestamp = "2019-01-01 00:00:01" },
            new { FcntUp = 102, CommTimestamp = "2019-01-01 00:00:02" },
            new { FcntUp = 103, CommTimestamp = "2019-01-01 00:00:03" },

            //restart of sequence
            new { FcntUp = 1, CommTimestamp = "2019-01-01 00:00:04" },
            new { FcntUp = 2, CommTimestamp = "2019-01-01 00:00:05" },
            new { FcntUp = 3, CommTimestamp = "2019-01-01 00:00:06" },

            //restart of sequence
            new { FcntUp = 1, CommTimestamp = "2019-01-01 00:00:07" },
            new { FcntUp = 2, CommTimestamp = "2019-01-01 00:00:08" },
            new { FcntUp = 3, CommTimestamp = "2019-01-01 00:00:09" }
        };

        //group by FcntUp and CommTimestamp
        var query = messages.GroupBy(x => new { x.FcntUp, x.CommTimestamp });

        //declare the current item
        dynamic currentItem = null;

        //declare the list of ranges
        List<dynamic> range = null;

        //loop through the sorted list
        foreach (var item in query)
        {
            //check if start of new range
            if (currentItem == null || item.Key.FcntUp < currentItem.Key.FcntUp)
            {
                //create a new list if the FcntUp starts on a new range
                range = new List<dynamic>();

                //add the list to the parent list
                arraylist.Add(range);
            }

            //add the item to the sublist
            range.Add(item);

            //set the current item
            currentItem = item;
        }

public static List<List<T>> GetSplitItemsList<T>(List<T> originalItemsList, short number)
    {
        var listGroup = new List<List<T>>();
        int j = number;
        for (int i = 0; i < originalItemsList.Count; i += number)
        {
            var cList = originalItemsList.Take(j).Skip(i).ToList();
            j += number;
            listGroup.Add(cList);
        }
        return listGroup;
    }

내 두 센트를 삽입하려면 ...

소스가 청크 될 목록 유형을 사용하여 매우 컴팩트 한 또 다른 솔루션을 찾았습니다.

public static IEnumerable<IEnumerable<TSource>> Chunk<TSource>(this IEnumerable<TSource> source, int chunkSize)
{
    // copy the source into a list
    var chunkList = source.ToList();

    // return chunks of 'chunkSize' items
    while (chunkList.Count > chunkSize)
    {
        yield return chunkList.GetRange(0, chunkSize);
        chunkList.RemoveRange(0, chunkSize);
    }

    // return the rest
    yield return chunkList;
}

참고 URL : https://stackoverflow.com/questions/419019/split-list-into-sublists-with-linq



반응형