c# - sistema - tipos de particiones en windows
¿C#- forma elegante de particionar una lista? (11)
Algo como (código de aire no probado):
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;
}
Esto devuelve una enumeración de listas en lugar de una lista de listas, pero puede ajustar fácilmente el resultado en una lista:
IList<IList<T>> listOfLists = new List<T>(PartitionList<T>(list, maxCount));
Me gustaría particionar una lista en una lista de listas, especificando el número de elementos en cada partición.
Por ejemplo, supongamos que tengo la lista {1, 2, ... 11}, y me gustaría particionarla de manera que cada conjunto tenga 4 elementos, con el último conjunto llenando tantos elementos como sea posible. La partición resultante se vería como {{1..4}, {5..8}, {9..11}}
¿Cuál sería una forma elegante de escribir esto?
Aquí hay un método de extensión que hará lo que quieras:
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));
}
Edición: Aquí hay una versión mucho más limpia de la función:
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));
}
El uso de ArraySegments puede ser una solución legible y breve (se requiere la conversión de su lista a la matriz):
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);
}
No estoy seguro de por qué Jochems respondió utilizando ArraySegment fue rechazado. Podría ser realmente útil siempre y cuando no necesite extender los segmentos (emitidos a IList). Por ejemplo, imagine que lo que está tratando de hacer es pasar segmentos a un flujo de datos TPL para su procesamiento simultáneo. Pasar los segmentos como instancias de IList permite que el mismo código se ocupe de las matrices y las listas de forma agnóstica.
Por supuesto, eso plantea la pregunta: ¿por qué no derivar una clase ListSegment que no requiere perder memoria llamando a ToArray ()? La respuesta es que las matrices pueden procesarse de forma ligeramente más rápida en algunas situaciones (indexación ligeramente más rápida). Pero tendrías que estar haciendo un procesamiento bastante duro para notar una gran diferencia. Más importante aún, no hay una buena manera de protegerse contra las operaciones de inserción y eliminación aleatorias realizadas por otro código que contiene una referencia a la lista.
Llamar a ToArray () en una lista numérica de valor millonario toma aproximadamente 3 milisegundos en mi estación de trabajo. Por lo general, no es un precio demasiado alto para pagar cuando lo usa para obtener los beneficios de una seguridad de subprocesos más robusta en operaciones concurrentes, sin incurrir en el alto costo del bloqueo.
O en .Net 2.0 harías esto:
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;
}
}
Para evitar la agrupación, las matemáticas y la reiteración.
IEnumerable<IList<T>> Partition<T>(this IEnumerable<T> source, int size)
{
var partition = new T[size];
var count = 0;
foreach(var t in source)
{
partition[count] = t;
count ++;
if (count == size)
{
yield return partition;
var partition = new T[size];
var count = 0;
}
}
if (count > 0)
{
Array.Resize(ref partition, count);
yield return partition;
}
}
Para evitar múltiples controles, ejemplificaciones innecesarias e repeticiones repetitivas, puede usar el código:
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);
}
}
}
}
Podrías usar un método de extensión:
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();
}
Usando LINQ puedes cortar tus grupos en una sola línea de código como esta ...
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);
A continuación, podría iterar sobre los grupos como los siguientes ...
foreach (var group in groups)
{
Console.WriteLine("Group: {0}", group.Key);
foreach (var item in group)
{
Console.WriteLine("/tValue: {0}", item);
}
}
y obtendrás una salida como esta ...
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
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); }
}
y para el caso especial de 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());
}
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();
}
Si necesita una List<List<T>>
real List<List<T>>
lugar de un IEnumerable<IEnumerable<T>>
, cambie la consulta de la siguiente manera:
var query = yourList
.Select((x, i) => new { x, i })
.GroupBy(i => i.i / groupSize, x => x.x)
.Select(g => g.ToList())
.ToList();