21

What is the best way to get exactly x values from an Enumerable in C#. If i use Enumerable .Take() like this:

var myList = Enumerable.Range(0,10);
var result = myList.Take(20);

The result will only have 10 elements.

I want to fill the missing entries with a default value. Something like this:

var myList = Enumerable.Range(0,10);
var result = myList.TakeOrDefault(20, default(int));  //Is there anything like this?

Is there such a function in C# and if not, what would be the best way to achieve this?

HectorLector
  • 1,851
  • 1
  • 23
  • 33
  • 1
    Why not just write an extension method that checks the count and returns the default value for remaining entries? – Ganesh R. Jul 27 '15 at 15:01
  • @Jamiec Is it wrong to answer if some else has already answered? I commented that it can be done via extension methods and sat down to frame the answer. It took me time but heck I will post if I wrote something – Ganesh R. Jul 27 '15 at 15:19
  • 1
    @GaneshR. - no, not at all. But you might want to come back and delete your comment after, otherwise it looks a bit *weird* – Jamiec Jul 27 '15 at 15:20
  • since you have hardcoded values (the 10 and 20) in your example,i dont know if it suits your example but you could create an array with lenght 20 then use Array.Copy() to copy your values to the new array,then call AsEnumerable() on the new array, the rest of the values will be the default for that type(for primitive value types 0),this is only according to your example where you use int,where you also can use double for example or other primitive value types. – terrybozzio Jul 27 '15 at 15:27
  • `default(int)` is just a convoluted way of writing `0`... – Matti Virkkunen Jul 27 '15 at 17:01
  • @MattiVirkkunen - I know, it was just a bad example. – HectorLector Jul 27 '15 at 19:11
  • 2
    @MattiVirkkunen Technically, yes. But semantically, it's very different. And of course, it's a lot more useful with types that don't have simple literals, like `default(string)` or `default(DateTime)`. Just as importantly, you can use `default(T)` with generics. – Luaan Jul 28 '15 at 06:48
  • @Luaan: `string` has plenty of simple literals, and `default(DateTime)` is yet again just a less readable way of writing `DateTime.MinValue`. `default` only exists for generics, and there's no point in using it with a concrete type. – Matti Virkkunen Jul 28 '15 at 13:44
  • 1
    @MattiVirkkunen You and I disagree on what is more readable then - `DateTime.MinValue` has an explicit meaning to me saying "this is the lowest possible value a datetime can have". `default(DateTime)` says "this is the default value". There's no string literal for `null`. `string.Empty` is an empty string (do you use `string.Empty` or `""`? `@""`?). `default(string)` is a `null` of type `string` - very important for type inference. What a feature was originally designed doesn't matter all that much - the important thing is what it's actually good at doing :) – Luaan Jul 28 '15 at 16:17

7 Answers7

19

You could do something like:

var result = myList.Concat(Enumerable.Repeat(default(int), 20)).Take(20); 

And it would be easy to turn this into an extension method:

public static IEnumerable<T> TakeOrDefault<T>(this IEnumerable<T> list, int count, T defaultValue)
{
    return  list.Concat(Enumerable.Repeat(defaultValue, count)).Take(count);
}

But there is a subtle gotcha here. This would work perfectly fine for value types, for a reference type, if your defaultValue isn't null, you are adding the same object multiple times. Which probably isn't want you want. For example, if you had this:

var result = myList.TakeOrDefault(20, new Foo());

You are going to add the same instance of Foo to pad your collection. To solve that problem, you'd need something like this:

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

Which you'd call like this:

var result = myList.TakeOrDefault(20, () => new Foo())

Of course, both methods can co-exist, so you could easily have:

// 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());
Matt Burland
  • 44,552
  • 18
  • 99
  • 171
11

Its not there by default, but it's easy enough to write as an extension method

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

Live example: http://rextester.com/XANF91263

Jamiec
  • 133,658
  • 13
  • 134
  • 193
  • You don't need the `T defaultValue`. You can return `default(T)`. – Yuval Itzchakov Jul 27 '15 at 15:09
  • 2
    @YuvalItzchakov Useful if they want to supply their own default value, but meeting in the middle could be made an optional parameter. – Adam Houldsworth Jul 27 '15 at 15:09
  • 1
    @YuvalItzchakov - I only added that as the OP requested it. Of course it means you can pass `default(int)` or you could pass `17` (when enumerating over a `IEnumerable`..) – Jamiec Jul 27 '15 at 15:10
5

What you're looking for is a general-purpose PadTo method, which extends the collection's length if needed using a given value.

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

You can now express:

myList.Take(20).PadTo(20);

This is analogous to Scala's List[A].padTo

dcastro
  • 66,540
  • 21
  • 145
  • 155
1

You could use Concat for this purpose. You can use a simple helper method to join this all together:

public IEnumerable<T> TakeSpawn(this IEnumerable<T> @this, int take, T defaultElement)
{
  return @this.Concat(Enumerable.Repeat(defaultElement, take)).Take(take);
}

The idea is that you always append another enumerable on the end of the original enumerable, so if the input doesn't have enough elements, it will start enumerating from the Repeat.

Luaan
  • 62,244
  • 7
  • 97
  • 116
  • @Jamiec No, it'll work for anything, as far as I can see. – Richiban Jul 27 '15 at 15:20
  • @Richiban - I read that as `Enujmerable.Range`, its not it's `Enumerable.Repeat`. I get 0/10 for reading comprehension. – Jamiec Jul 27 '15 at 15:22
  • 1
    @Jamiec No, you were right. I noticed a split second after I posted the question, and fixed it right away :) – Luaan Jul 28 '15 at 06:45
0

There isn't anything in the .NET Framework, not that I'm aware of. This can be achieved easily using an extension method though and it works for all types if you supply a default value yourself:

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);
    }
}
Jurgen Camilleri
  • 3,559
  • 20
  • 45
  • 1
    He is using an IEnumerable. You are using a List. IEumerable is looked up at run time. List allocates memory at declaration – Ganesh R. Jul 27 '15 at 15:15
0

Why not just write an extension method that checks the count and returns the default value for remaining entries:

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;
                }
            }
        }
    }
}
Ganesh R.
  • 4,337
  • 3
  • 30
  • 46
0

I wrote a quick extension for this which depends on T being a value type.

  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;
        }
    }
LSU.Net
  • 829
  • 6
  • 17