C#Enumerable. Tomar con valor por defecto
.net linq (7)
¿Cuál es la mejor manera de obtener exactamente los valores x de un Enumerable en C #? Si uso Enumerable .Take () de esta manera:
var myList = Enumerable.Range(0,10);
var result = myList.Take(20);
El resultado solo tendrá 10 elementos.
Quiero llenar las entradas que faltan con un valor predeterminado. Algo como esto:
var myList = Enumerable.Range(0,10);
var result = myList.TakeOrDefault(20, default(int)); //Is there anything like this?
¿Existe tal función en C # y, de no ser así, cuál sería la mejor manera de lograrlo?
¿Por qué no escribir un método de extensión que verifique el conteo y devuelva el valor predeterminado para las entradas restantes?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
List<int> values = new List<int>{1, 2, 3, 4};
IEnumerable<int> moreValues = values.TakeOrDefault(3, 100);
Console.WriteLine(moreValues.Count());
moreValues = values.TakeOrDefault(4, 100);
Console.WriteLine(moreValues.Count());
moreValues = values.TakeOrDefault(10, 100);
Console.WriteLine(moreValues.Count());
}
}
public static class ExtensionMethods
{
public static IEnumerable<T> TakeOrDefault<T>(this IEnumerable<T> enumerable, int count, T defaultValue)
{
int returnedCount = 0;
foreach (T variable in enumerable)
{
returnedCount++;
yield return variable;
if (returnedCount == count)
{
yield break;
}
}
if (returnedCount < count)
{
for (int i = returnedCount; i < count; i++)
{
yield return defaultValue;
}
}
}
}
}
Escribí una extensión rápida para esto que depende de que T sea un tipo de valor.
public static class Extensions
{
public static IEnumerable<T> TakeOrDefault<T>(this IEnumerable<T> list, int totalElements)
{
List<T> finalList = list.ToList();
if (list.Count() < totalElements)
{
for (int i = list.Count(); i < totalElements; i++)
{
finalList.Add(Activator.CreateInstance<T>());
}
}
return finalList;
}
}
Lo que está buscando es un método PadTo
propósito PadTo
, que extiende la longitud de la colección si es necesario utilizando un valor determinado.
public static IEnumerable<T> PadTo<T>(this IEnumerable<T> source, int len)
{
return source.PadTo(len, default(T));
}
public static IEnumerable<T> PadTo<T>(this IEnumerable<T> source, int len, T elem)
{
return source.PadTo(len, () => elem);
}
public static IEnumerable<T> PadTo<T>(this IEnumerable<T> source, int len, Func<T> elem)
{
int i = 0;
foreach(var t in source)
{
i++;
yield return t;
}
while(i++ < len)
yield return elem();
}
Ahora puedes expresar:
myList.Take(20).PadTo(20);
Esto es análogo a la List[A].padTo
de Scala List[A].padTo
No está ahí por defecto, pero es lo suficientemente fácil de escribir como un método de extensión
public static IEnumerable<T> TakeOrDefault<T>(this IEnumerable<T> items, int count, T defaultValue)
{
var i = 0;
foreach(var item in items)
{
i++;
yield return item;
if(i == count)
yield break;
}
while(i++<count)
{
yield return defaultValue;
}
}
Ejemplo en vivo: http://rextester.com/XANF91263
No hay nada en el .NET Framework, no que yo sepa. Sin embargo, esto se puede lograr fácilmente usando un método de extensión y funciona para todos los tipos si usted mismo proporciona un valor predeterminado:
public static class ListExtensions
{
public static IEnumerable<T> TakeOrDefault<T>(this List<T> list, int count, T defaultValue)
{
int missingItems = count - list.Count;
List<T> extra = new List<T>(missingItems);
for (int i = 0; i < missingItems; i++)
extra.Add(defaultValue);
return list.Take(count).Concat(extra);
}
}
Podrías hacer algo como:
var result = myList.Concat(Enumerable.Repeat(default(int), 20)).Take(20);
Y sería fácil convertir esto en un método de extensión:
public static IEnumerable<T> TakeOrDefault<T>(this IEnumerable<T> list, int count, T defaultValue)
{
return list.Concat(Enumerable.Repeat(defaultValue, count)).Take(count);
}
Pero hay un sutil gotcha aquí. Esto funcionaría perfectamente bien para los tipos de valor , para un tipo de referencia, si su valor defaultValue
no es nulo, está agregando el mismo objeto varias veces. Lo que probablemente no es lo que quieres. Por ejemplo, si tuvieras esto:
var result = myList.TakeOrDefault(20, new Foo());
Vas a agregar la misma instancia de Foo
para rellenar tu colección. Para resolver ese problema, necesitarías algo como esto:
public static IEnumerable<T> TakeOrDefault<T>(this IEnumerable<T> list, int count, Func<T> defaultFactory)
{
return list.Concat(Enumerable.Range(0, count).Select(i => defaultFactory())).Take(count);
}
Que llamarías así:
var result = myList.TakeOrDefault(20, () => new Foo())
Por supuesto, ambos métodos pueden coexistir, por lo que fácilmente podría tener:
// pad a list of ints with zeroes
var intResult = myIntList.TakeOrDefault(20, default(int));
// pad a list of objects with null
var objNullResult = myObjList.TakeOrDefault(20, (object)null);
// pad a list of Foo with new (separate) instances of Foo
var objPadNewResult = myFooList.TakeOrDefault(20, () => new Foo());
Podrías usar Concat
para este propósito. Puedes usar un método simple de ayuda para unir todo esto:
public IEnumerable<T> TakeSpawn(this IEnumerable<T> @this, int take, T defaultElement)
{
return @this.Concat(Enumerable.Repeat(defaultElement, take)).Take(take);
}
La idea es que siempre agregue otro enumerable al final del enumerable original, por lo que si la entrada no tiene suficientes elementos, comenzará la enumeración de la Repeat
.