45

各パーティションの要素数を指定して、リストをリストのリストに分割したいと思います。

たとえば、リスト {1, 2, ... 11} があり、各セットが 4 つの要素を持ち、最後のセットができるだけ多くの要素を満たすように分割したいとします。結果のパーティションは {{1..4}, {5..8}, {9..11}} のようになります

これを書くエレガントな方法は何でしょうか?

4

11 に答える 11

61

これがあなたが望むことをする拡張メソッドです:

public static IEnumerable<List<T>> Partition<T>(this IList<T> source, Int32 size)
{
    for (int i = 0; i < (source.Count / size) + (source.Count % size > 0 ? 1 : 0); i++)
        yield return new List<T>(source.Skip(size * i).Take(size));
}

編集:これは関数のはるかにクリーンなバージョンです:

public static IEnumerable<List<T>> Partition<T>(this IList<T> source, Int32 size)
{
    for (int i = 0; i < Math.Ceiling(source.Count / (Double)size); i++)
        yield return new List<T>(source.Skip(size * i).Take(size));
}
于 2009-09-08T20:22:52.417 に答える
34

Using LINQ you could cut your groups up in a single line of code like this...

var x = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };

var groups = x.Select((i, index) => new
{
    i,
    index
}).GroupBy(group => group.index / 4, element => element.i);

You could then iterate over the groups like the following...

foreach (var group in groups)
{
    Console.WriteLine("Group: {0}", group.Key);

    foreach (var item in group)
    {
        Console.WriteLine("\tValue: {0}", item);
    }
}

and you'll get an output that looks like this...

Group: 0
        Value: 1
        Value: 2
        Value: 3
        Value: 4
Group: 1
        Value: 5
        Value: 6
        Value: 7
        Value: 8
Group: 2
        Value: 9
        Value: 10
        Value: 11
于 2009-09-08T20:26:14.007 に答える
11

次のようなもの(テストされていない航空コード):

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;
}

これは、リストのリストではなくリストの列挙を返しますが、結果をリストに簡単にラップできます。

IList<IList<T>> listOfLists = new List<T>(PartitionList<T>(list, maxCount));
于 2009-09-08T20:11:58.447 に答える
10

グループ化、数学、反復を避けるため。

この方法では、不要な計算、比較、および割り当てが回避されます。パラメータの検証が含まれています。

これは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;
    }
}
于 2013-08-13T09:16:58.307 に答える
1
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); }
}

および文字列の特殊なケースの場合

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());
}
于 2012-03-07T12:36:31.173 に答える
1
var yourList = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
var groupSize = 4;

// here's the actual query that does the grouping...
var query = yourList
    .Select((x, i) => new { x, i })
    .GroupBy(i => i.i / groupSize, x => x.x);

// and here's a quick test to ensure that it worked properly...
foreach (var group in query)
{
    foreach (var item in group)
    {
        Console.Write(item + ",");
    }
    Console.WriteLine();
}

List<List<T>>ではなく実際の値が必要な場合はIEnumerable<IEnumerable<T>>、クエリを次のように変更します。

var query = yourList
    .Select((x, i) => new { x, i })
    .GroupBy(i => i.i / groupSize, x => x.x)
    .Select(g => g.ToList())
    .ToList();
于 2009-09-08T20:40:55.560 に答える
1

または、.Net 2.0 では次のようにします。

    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;
        }
    }
于 2009-09-08T21:41:16.697 に答える
0

拡張メソッドを使用できます。

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();

}

于 2009-09-08T20:19:46.773 に答える
0

複数のチェック、不要なインスタンス化、繰り返しの繰り返しを避けるために、次のコードを使用できます。

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);
            }
        }
    }
}
于 2016-12-27T19:50:00.717 に答える