C# - elegant way of partitioning a list?

前端 未结 11 599
情歌与酒
情歌与酒 2020-12-01 04:36

I\'d like to partition a list into a list of lists, by specifying the number of elements in each partition.

For instance, suppose I have the list {1, 2, ... 11}, and

相关标签:
11条回答
  • 2020-12-01 04:57

    Something like (untested air code):

    IEnumerable<IList<T>> PartitionList<T>(IList<T> list, int maxCount)
    {
        List<T> partialList = new List<T>(maxCount);
        foreach(T item in list)
        {
            if (partialList.Count == maxCount)
            {
               yield return partialList;
               partialList = new List<T>(maxCount);
            }
            partialList.Add(item);
        }
        if (partialList.Count > 0) yield return partialList;
    }
    

    This returns an enumeration of lists rather than a list of lists, but you can easily wrap the result in a list:

    IList<IList<T>> listOfLists = new List<T>(PartitionList<T>(list, maxCount));
    
    0 讨论(0)
  • 2020-12-01 04:59

    To avoid multiple checks, unnecessary instantiations, and repetitive iterations, you could use the code:

    namespace System.Collections.Generic
    {
        using Linq;
        using Runtime.CompilerServices;
    
        public static class EnumerableExtender
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            public static bool IsEmpty<T>(this IEnumerable<T> enumerable) => !enumerable?.GetEnumerator()?.MoveNext() ?? true;
    
            public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> source, int size)
            {
                if (source == null)
                    throw new ArgumentNullException(nameof(source));
                if (size < 2)
                    throw new ArgumentOutOfRangeException(nameof(size));
                IEnumerable<T> items = source;
                IEnumerable<T> partition;
                while (true)
                {
                    partition = items.Take(size);
                    if (partition.IsEmpty())
                        yield break;
                    else
                        yield return partition;
                    items = items.Skip(size);
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-01 05:02

    To avoid grouping, mathematics and reiteration.

    The method avoids unnecessary calculations, comparisons and allocations. Parameter validation is included.

    Here is a working demonstration on fiddle.

    public static IEnumerable<IList<T>> Partition<T>(
        this IEnumerable<T> source,
        int size)
    {
        if (size < 2)
        {
            throw new ArgumentOutOfRangeException(
                nameof(size),
                size,
                "Must be greater or equal to 2.");  
        }
    
        T[] partition;
        int count;
    
        using (var e = source.GetEnumerator())
        {
            if (e.MoveNext())
            {
                partition = new T[size];
                partition[0] = e.Current;
                count = 1;
            }
            else
            {
                yield break;    
            }
    
            while(e.MoveNext())
            {
                partition[count] = e.Current;
                count++;
    
                if (count == size)
                {
                    yield return partition;
                    count = 0;
                    partition = new T[size];
                }
            }
        }
    
        if (count > 0)
        {
            Array.Resize(ref partition, count);
            yield return partition;
        }
    }
    
    0 讨论(0)
  • 2020-12-01 05:03

    You could use an extension method:

    public static IList<HashSet<T>> Partition<T>(this IEnumerable<T> input, Func<T, object> partitionFunc)
    {
          Dictionary<object, HashSet> partitions = new Dictionary<object, HashSet<T>>();

      object currentKey = null;
      foreach (T item in input ?? Enumerable.Empty<T>())
      {
          currentKey = partitionFunc(item);
    
          if (!partitions.ContainsKey(currentKey))
          {
              partitions[currentKey] = new HashSet<T>();
          }
    
          partitions[currentKey].Add(item);
      }
    
      return partitions.Values.ToList();
    

    }

    0 讨论(0)
  • 2020-12-01 05:06

    Or in .Net 2.0 you would do this:

        static void Main(string[] args)
        {
            int[] values = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
            List<int[]> items = new List<int[]>(SplitArray(values, 4));
        }
    
        static IEnumerable<T[]> SplitArray<T>(T[] items, int size)
        {
            for (int index = 0; index < items.Length; index += size)
            {
                int remains = Math.Min(size, items.Length-index);
                T[] segment = new T[remains];
                Array.Copy(items, index, segment, 0, remains);
                yield return segment;
            }
        }
    
    0 讨论(0)
  • 2020-12-01 05:10
    public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> list, int size)
    {
        while (list.Any()) { yield return list.Take(size); list = list.Skip(size); }
    }
    

    and for the special case of String

    public static IEnumerable<string> Partition(this string str, int size)
    {
        return str.Partition<char>(size).Select(AsString);
    }
    
    public static string AsString(this IEnumerable<char> charList)
    {
        return new string(charList.ToArray());
    }
    
    0 讨论(0)
提交回复
热议问题