2015-07-27 8 views
18

Was ist der beste Weg, um genau x Werte von einer Enumerable in C# zu erhalten. Wenn ich Enumerable .Nehmen() wie folgt verwenden:C# Enumerable.Take mit Standardwert

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

Das Ergebnis wird nur 10 Elemente.

Ich möchte die fehlenden Einträge mit einem Standardwert füllen. Etwas wie folgt aus:

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

Gibt es eine solche Funktion in C# und wenn nicht, was wäre der beste Weg, um dies zu erreichen?

+1

Warum nicht einfach eine Erweiterungsmethode schreiben, die die Anzahl überprüft und den Standardwert für die verbleibenden Einträge zurückgibt? –

+0

@Jamiec Ist es falsch zu antworten, wenn jemand anderes bereits geantwortet hat? Ich bemerkte, dass es über Erweiterungsmethoden gemacht werden kann und setzte sich, um die Antwort zu rahmen. Es hat mich Zeit gekostet, aber ich werde posten, wenn ich etwas geschrieben habe –

+1

@GaneshR. - Nein überhaupt nicht. Aber vielleicht möchtest du zurückkommen und deinen Kommentar danach löschen, sonst sieht es etwas seltsam aus * – Jamiec

Antwort

16

Sie könnten wie etwas tun:

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

Und es wäre leicht, dies in eine Erweiterungsmethode zu verwandeln:

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

Aber gibt es hier eine subtile gotcha. Dies würde tadellos funktionieren für Werttypen, wenn Ihre defaultValue nicht Null ist, fügen Sie das gleiche Objekt mehrere Male. Was wahrscheinlich nicht willst du willst. Zum Beispiel hatten, wenn Sie dies:

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

Sie werden die gleiche Instanz von Foo aufzufüllen Ihre Sammlung hinzuzufügen. Um dieses Problem zu lösen, würden Sie so etwas wie dieses benötigen:

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

Welche du so nennen würde:

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

Natürlich können beide Verfahren nebeneinander existieren, so könnte man leicht haben :

// 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()); 
+1

Kein echter Punkt, der die Anzahl abzieht (wenn Sie nur mit folgen werden) 'Take', das könnte besser sein.) – Rawling

+0

Ich mag deine zweite Lösung. Ich würde die erste Lösung fallen lassen - es könnte eine Ausnahme auslösen (wie Sie bemerkt haben) und es "myList" zweimal aufzählt. –

+0

Dank der Concat/Repeat-Version funktionierte perfekt für meine Bedürfnisse. – HectorLector

1

Sie könnten Concat für diesen Zweck verwenden. Sie können eine einfache Hilfsmethode verwenden diese alle zusammen zu verbinden:

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

Die Idee ist, dass man immer eine andere zählbare am Ende des ursprünglichen enumerable hängen, so dass, wenn die Eingabe nicht genug Elemente hat, wird es Beginnen Sie mit dem Aufzählen von Repeat.

+0

Dies funktioniert nur für ganze Zahlen – Jamiec

+0

@Jamiec Nein, es wird für alles funktionieren, so weit ich kann sehen. – Richiban

+0

@Richiban - Ich lese das als 'Enujferable.Range', es ist nicht 'Enumerable.Repeat'. Ich bekomme 0/10 für Leseverständnis. – Jamiec

11

Es ist nicht dort standardmäßig, aber es ist leicht genug, um als eine Erweiterung Methode

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-Beispiel zu schreiben: http://rextester.com/XANF91263

+0

Sie brauchen den 'T defaultValue' nicht. Sie können 'default (T)' zurückgeben. –

+2

@YuvalItzchakov Nützlich, wenn sie ihren eigenen Standardwert angeben möchten, aber das Treffen in der Mitte könnte ein optionaler Parameter sein. –

+1

@YuvalItzchakov - Ich habe nur hinzugefügt, wie das OP es angefordert hat. Natürlich bedeutet das, dass Sie 'default (int)' übergeben können oder Sie '17' übergeben können (wenn Sie über 'IEnumerable ' ..) zählen. – Jamiec

0

Es gibt nichts im .NET Framework, nicht, dass ich bewusst bin. Dies kann jedoch leicht über ein Verlängerungsverfahren erreicht werden, und es funktioniert für alle Arten, wenn Sie einen Standardwert selbst liefern:

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

Er verwendet ein IEnumerable. Sie verwenden eine Liste. IEumerable wird zur Laufzeit gesucht.Liste reserviert Speicher bei der Deklaration –

5

Was Sie suchen ist ein Mehrzweck PadTo Methode, die die Länge der Sammlung erstreckt wenn nötig mit einem gegebenen Wert.

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

Sie können jetzt ausdrücken:

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

Dies zu Scala analog List[A].padTo

-1

Warum nicht einfach eine Erweiterungsmethode schreiben, die die Zählung überprüft und gibt den Standardwert für restliche Einträge:

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

Bitte kommentieren Sie, wenn nach unten Abstimmung –

+2

Nicht mein Downvote, aber bitte Kommentar beim Codieren/Beantworten .. – TaW

0

Ich schrieb eine schnelle Erweiterung für dieses whi ch hängt davon ab, dass T ein Werttyp ist.

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