【问题标题】:C# Enumerable.Take with default valueC# Enumerable.Take 带默认值
【发布时间】:2015-07-27 14:56:24
【问题描述】:

从 C# 中的 Enumerable 中准确获取 x 值的最佳方法是什么。 如果我像这样使用 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?

C# 中是否有这样的功能,如果没有,实现此功能的最佳方法是什么?

【问题讨论】:

  • 为什么不写一个扩展方法来检查计数并返回剩余条目的默认值?
  • @Jamiec 如果其他人已经回答了,那么回答错了吗?我评论说它可以通过扩展方法完成并坐下来制定答案。我花了一些时间,但是如果我写了一些东西,我会发帖
  • @GaneshR。 - 一点都不。但是你可能想回来删除你的评论,否则看起来有点奇怪
  • @MattiVirkkunen 从技术上讲,是的。但在语义上,它是非常不同的。当然,它对于没有简单文字的类型(如default(string)default(DateTime))更有用。同样重要的是,您可以将default(T) 与泛型一起使用。
  • @MattiVirkkunen 你和我不同意什么更易读——DateTime.MinValue 对我有明确的意义,说“这是日期时间可以拥有的最低值”。 default(DateTime) 说“这是默认值”。 null 没有字符串文字。 string.Empty 是一个空字符串(你使用string.Empty 还是""@""?)。 default(string)null 类型的 string - 对于类型推断非常重要。最初设计的功能并不重要 - 重要的是它实际上擅长做什么:)

标签: c# .net linq ienumerable take


【解决方案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);
}

但是这里有一个微妙的问题。 对于值类型,这可以很好地工作,对于引用类型,如果您的 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());

【讨论】:

  • 不过,减去计数并没有什么意义(如果您只关注Take,这可能更简洁。)
  • 我喜欢你的第二个解决方案。我会放弃第一个解决方案 - 它可能会引发异常(如您所述)并且它会枚举 myList 两次。
  • 感谢 Concat/Repeat 版本非常适合我的需求。
【解决方案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

【讨论】:

  • 您不需要T defaultValue。您可以返回default(T)
  • @YuvalItzchakov 如果他们想提供自己的默认值很有用,但可以将中间的会议设为可选参数。
  • @YuvalItzchakov - 我只是在 OP 要求时才添加它。当然这意味着你可以通过default(int) 或者你可以通过17(当枚举IEnumerable&lt;int&gt;..时)
【解决方案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开始枚举。

    【讨论】:

    • @Jamiec 不,据我所知,它适用于任何事情。
    • @Richiban - 我读的是Enujmerable.Range,不是Enumerable.Repeat。我的阅读理解得了 0/10。
    • @Jamiec 不,你是对的。我在发布问题后一瞬间就注意到了,并立即修复了它:)
    【解决方案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);
        }
    }
    

    【讨论】:

    • 他正在使用 IEnumerable。您正在使用列表。 IEumerable 在运行时被查找。 List 在声明时分配内存
    【解决方案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;
                      }
                  }
              }
          }
      }
      

      【讨论】:

      • 投反对票时请发表评论
      猜你喜欢
      • 1970-01-01
      • 2011-06-17
      • 2012-05-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-03
      • 2023-02-03
      • 1970-01-01
      相关资源
      最近更新 更多