С# Enumerable.Take со значением по умолчанию
Каков наилучший способ получить точно значения x из Enumerable в С#.
Если я использую Enumerable.Take() следующим образом:
var myList = Enumerable.Range(0,10);
var result = myList.Take(20);
Результат будет содержать только 10 элементов.
Я хочу заполнить недостающие записи значением по умолчанию.
Что-то вроде этого:
var myList = Enumerable.Range(0,10);
var result = myList.TakeOrDefault(20, default(int)); //Is there anything like this?
Есть ли такая функция в С#, а если нет, то какой лучший способ достичь этого?
Ответы
Ответ 1
Вы можете сделать что-то вроде:
var result = myList.Concat(Enumerable.Repeat(default(int), 20)).Take(20);
И было бы легко превратить это в метод расширения:
public static IEnumerable<T> TakeOrDefault<T>(this IEnumerable<T> list, int count, T defaultValue)
{
return list.Concat(Enumerable.Repeat(defaultValue, count)).Take(count);
}
Но здесь есть тонкий getcha. Это будет отлично работать для типов значений, для ссылочного типа, если ваш defaultValue
не является нулевым, вы добавляете один и тот же объект несколько раз. Скорее всего, вы этого не хотите. Например, если у вас есть это:
var result = myList.TakeOrDefault(20, new Foo());
Вы добавите тот же экземпляр Foo
, чтобы добавить свою коллекцию. Чтобы решить эту проблему, вам понадобится примерно следующее:
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);
}
Что вы бы назвали так:
var result = myList.TakeOrDefault(20, () => new Foo())
Конечно, оба метода могут сосуществовать, поэтому вы можете легко:
// 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());
Ответ 2
Его нет там по умолчанию, но достаточно легко написать в качестве метода расширения
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;
}
}
Пример в реальном времени: http://rextester.com/XANF91263
Ответ 3
То, что вы ищете, - это универсальный метод PadTo
, который, если необходимо, расширяет длину коллекции с использованием заданного значения.
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();
}
Теперь вы можете выразить:
myList.Take(20).PadTo(20);
Это аналогично Scala List[A].padTo
Ответ 4
Вы можете использовать Concat
для этой цели. Вы можете использовать простой вспомогательный метод, чтобы объединить все это:
public IEnumerable<T> TakeSpawn(this IEnumerable<T> @this, int take, T defaultElement)
{
return @this.Concat(Enumerable.Repeat(defaultElement, take)).Take(take);
}
Идея состоит в том, что вы всегда добавляете другой перечислимый в конец исходного перечисляемого, поэтому, если на входе не хватает элементов, он начнет перечисление с Repeat
.
Ответ 5
В .NET Framework ничего нет, я не знаю. Это может быть достигнуто легко, используя метод расширения, хотя и работает для всех типов, если вы сами по умолчанию используете значение:
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);
}
}
Ответ 6
Я написал быстрое расширение для этого, которое зависит от того, что T является типом значения.
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;
}
}
Ответ 7
Почему бы просто не написать метод расширения, который проверяет количество и возвращает значение по умолчанию для остальных записей:
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;
}
}
}
}
}