C# - 优雅的列表分区方式?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1396048/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 16:03:16  来源:igfitidea点击:

C# - elegant way of partitioning a list?

c#listdata-partitioning

提问by David Hodgson

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 would like to partition it such that each set has 4 elements, with the last set filling as many elements as it can. The resulting partition would look like {{1..4}, {5..8}, {9..11}}

例如,假设我有一个列表 {1, 2, ... 11},并且想要对它进行分区,使得每个集合有 4 个元素,最后一个集合尽可能多地填充元素。结果分区看起来像 {{1..4}, {5..8}, {9..11}}

What would be an elegant way of writing this?

写这个的优雅方式是什么?

采纳答案by Andrew Hare

Here is an extension method that will do what you want:

这是一个可以执行您想要的扩展方法:

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

Edit:Here is a much cleaner version of the function:

编辑:这是该函数的一个更清晰的版本:

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

回答by Joe

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

回答by Lee

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

}

回答by Scott Ivey

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

使用 LINQ,您可以像这样在一行代码中分割您的组...

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

回答by LukeH

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

If you need an actual List<List<T>>rather than an IEnumerable<IEnumerable<T>>then change the query as follows:

如果您需要一个实际的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();

回答by csharptest.net

Or in .Net 2.0 you would do this:

或者在 .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;
        }
    }

回答by Scroog1

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

对于 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());
}

回答by Jodrell

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.

这是一个关于 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;
    }
}

回答by Jochem Geussens

Using ArraySegments might be a readable and short solution (casting your list to array is required):

使用 ArraySegments 可能是一个可读且简短的解决方案(需要将列表转换为数组):

var list = new List<int>() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; //Added 0 in front on purpose in order to enhance simplicity.
int[] array = list.ToArray();
int step = 4;
List<int[]> listSegments = new List<int[]>();

for(int i = 0; i < array.Length; i+=step)
{
     int[] segment = new ArraySegment<int>(array, i, step).ToArray();
     listSegments.Add(segment);
}

回答by Ben Stabile

I'm not sure why Jochems answer using ArraySegmentwas voted down. It could be really useful as long as you are not going to need to extend the segments (cast to IList). For example, imagine that what you are trying to do is pass segments into a TPL DataFlow pipeline for concurrent processing. Passing the segments in as IList instances allows the same code to deal with arrays and lists agnostically.

我不确定为什么 Jochems 使用ArraySegment 的回答被否决了。只要您不需要扩展段(转换为 IList),它就会非常有用。例如,假设您正在尝试将段传递到 TPL DataFlow 管道中以进行并发处理。将段作为 IList 实例传入允许相同的代码以不可知的方式处理数组和列表。

Of course, that begs the question: Why not just derive a ListSegmentclass that does not require wasting memory by calling ToArray()? The answer is that arrays can actually be processed marginally faster in some situations (slightly faster indexing). But you would have to be doing some fairly hardcore processing to notice much of a difference. More importantly, there is no good way to protect against random insert and remove operations by other code holding a reference to the list.

当然,这就引出了一个问题:为什么不通过调用 ToArray() 来派生一个不需要浪费内存的ListSegment类呢?答案是在某些情况下实际上可以稍微快一点地处理数组(稍微快一点的索引)。但是你必须做一些相当核心的处理才能注意到很大的不同。更重要的是,没有好的方法可以防止其他代码持有对列表的引用进行随机插入和删除操作。

Calling ToArray() on a million value numeric list takes about 3 milliseconds on my workstation. That's usually not too great a price to pay when you're using it to gain the benefits of more robust thread safety in concurrent operations, without incurring the heavy cost of locking.

在我的工作站上,对百万值数字列表调用 ToArray() 大约需要 3 毫秒。当您使用它来获得并发操作中更强大的线程安全性的好处时,通常不会付出太大的代价,而不会产生大量的锁定成本。