2010-10-08 11 views
10

Próbuję podzielić kolekcję na wiele kolekcji, zachowując sortowanie, które mam w kolekcji. Próbowałem użyć następującej metody rozszerzenia, ale niepoprawnie je zepsuje. Zasadniczo, jeśli przyjrzeć się przedmiotom z kolekcji, kolejność powinna być taka sama w porównaniu do podzielonych kolekcji. Oto kod używam, że nie działa:Podziel kolekcję C# na równe części, zachowując sortowanie

public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts) 
     { 
      int i = 0; 
      var splits = from name in list 
         group name by i++ % parts into part 
         select part.AsEnumerable(); 
      return splits; 
     } 
  • int części = liczba enumerables sub
+0

możliwe duplikat [LINQ listy partycji na list z 8 członków.] (Http://stackoverflow.com/questions/3773403/linq-partition-list-into-lists- of-8-członków) –

+1

@Kirk Woll: To nie to samo, w pytaniu, które podałeś, metoda rozszerzenia przyjmuje maksymalną liczbę elementów w jednym subliczby, podczas gdy tutaj, jak rozumiem, mamy pożądaną liczbę sub-wyliczalnych. –

+0

@Andrew, masz rację, widzę, że jesteś w punkcie –

Odpowiedz

10

Musiałem skorzystać z tego, aby porównać listę obiektów ze sobą w grupach 4 ... będzie przechowywać obiekty w kolejności, w której oryginał był w posiadaniu. Może być rozszerzona do zrobienia czegoś innego niż 'listy'

/// <summary> 
/// Partition a list of elements into a smaller group of elements 
/// </summary> 
/// <typeparam name="T"></typeparam> 
/// <param name="list"></param> 
/// <param name="totalPartitions"></param> 
/// <returns></returns> 
public static List<T>[] Partition<T>(List<T> list, int totalPartitions) 
{ 
    if (list == null) 
     throw new ArgumentNullException("list"); 

    if (totalPartitions < 1) 
     throw new ArgumentOutOfRangeException("totalPartitions"); 

    List<T>[] partitions = new List<T>[totalPartitions]; 

    int maxSize = (int)Math.Ceiling(list.Count/(double)totalPartitions); 
    int k = 0; 

    for (int i = 0; i < partitions.Length; i++) 
    { 
     partitions[i] = new List<T>(); 
     for (int j = k; j < k + maxSize; j++) 
     { 
      if (j >= list.Count) 
       break; 
      partitions[i].Add(list[j]); 
     } 
     k += maxSize; 
    } 

    return partitions; 
} 
+0

Dzięki. Pracował świetnie. –

+0

Nie ma błędu z tym algorytmem. Czy ktoś próbował go zweryfikować? Wydaje mi się, że linia int maxSize = (int) Math.Ceiling (list.Count/(double) totalPartitions) może prowadzić do nieprawidłowej liczby zliczeń maksymalnych. Na przykład; listę 21 elementów, które zostaną podzielone na 10 pożądanych grup; w rzeczywistości utworzy tylko 7 grup po 3 elementy z 3 pustymi grupami. z tego powodu, że obliczenia rozmiaru zaokrągli się z 2,1 do 3 –

+0

Tak, wyobrażam sobie, że tak. Jeśli mam listę alfabetu i rozłożę ją na 100 partycji, otrzymam obiekt, który ma pierwsze 0-25 wypełnione po 1 pozycją, a 26-99 będzie puste. LINQ od 3.5 prawdopodobnie poprawi problem, po prostu nie spojrzałem na to za kilka lat. Zrobił to, czego potrzebowałem i oczywiście, czego potrzebował PO. –

0

Jak rozumiem chcesz przerwać przeliczalny na kilka części o jednakowej wielkości i bez łamania porządku twoich żywiołów. Wygląda na to, że jedynym wyborem jest najpierw wyliczenie długości zmiennej wejściowej, więc potrzebujesz co najmniej dwóch iteracji w przeliczeniu.

+0

@Kirk Woll: zależy to od tego, co jest potrzebne. Jeśli "części" to liczba elementów w jednym przeliczniku, to jest łatwa do rozwiązania. Ale powyższy kod sprawia, że ​​myślę, że "części" to liczba pożądanych podkatalogów, a nie elementów w przelicznikach. –

+0

"części" to liczba podliczników –

1
double partLength = list.Count()/(double)parts; 

    int i = 0; 
    var splits = from name in list 
       group name by Math.Floor((double)(i++/partLength)) into part 
       select part; 
+0

W twoim przykładzie "podziały" stają się IZliczbowe >, ale musi to być liczba mnoga > –

+0

Popraw mnie, jeśli się mylę, ale nie jest IGrouping an IEnumerable ? – kevev22

2

MoreLINQ biblioteka Jon Skeet za mogłyby rade dla Ciebie:

https://code.google.com/p/morelinq/source/browse/MoreLinq/Batch.cs

var items = list.Batch(parts); // gives you IEnumerable<IEnumerable<T>> 
var items = list.Batch(parts, seq => seq.ToList()); // gives you IEnumerable<List<T>> 
// etc... 

Inny przykład:

public class Program 
{ 
    static void Main(string[] args) 
    { 
     var list = new List<int>(); 
     for (int i = 1; i < 10000; i++) 
     { 
      list.Add(i); 
     } 

     var batched = list.Batch(681); 

     // will print 15. The 15th element has 465 items... 
     Console.WriteLine(batched.Count().ToString()); 
     Console.WriteLine(batched.ElementAt(14).Count().ToString()); 
     Console.WriteLine(); 
     Console.WriteLine("Press enter to exit."); 
     Console.ReadLine(); 
    } 
} 

Kiedy przejrzał zawartość partii, jednostka zamówienie zostało zachowane.

+0

W tych przykładach wydaje się, że "części" to ilość elementów w każdej kolekcji. W moim przykładzie chcę, aby "części" były liczbą sub-kolekcji do stworzenia. –

0
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts) 
    { 
     int nGroups = (int)Math.Ceiling(list.Count()/(double)parts); 

     var groups = Enumerable.Range(0, nGroups); 

     return groups.Select(g => list.Skip(g * parts).Take(parts)); 
    } 
+0

To nie jest poprawne.Spowoduje to utworzenie grup o rozmiarze równym częściom. –

0

będzie to robić dokładnie na żądanie. Będzie również uwzględniać nierówne zgrupowania, tj.27 elementy 10 grup przyniesie 7 grup po trzy i 3 grupy dwóch

 public static IEnumerable<IEnumerable<T>> SplitMaintainingOrder<T>(this IEnumerable<T> list, int parts) 
    { 
     if (list.Count() == 0) return Enumerable.Empty<IEnumerable<T>>(); 

     var toreturn = new List<IEnumerable<T>>(); 

     var splitFactor = Decimal.Divide((decimal)list.Count(), parts); 
     int currentIndex = 0; 

     for (var i = 0; i < parts; i++) 
     { 
      var toTake = Convert.ToInt32(
       i == 0 ? Math.Ceiling(splitFactor) : (
        (Decimal.Compare(Decimal.Divide(Convert.ToDecimal(currentIndex), Convert.ToDecimal(i)), splitFactor) > 0) ? 
         Math.Floor(splitFactor) : Math.Ceiling(splitFactor))); 

      toreturn.Add(list.Skip(currentIndex).Take(toTake)); 
      currentIndex += toTake; 
     } 

     return toreturn; 
    } 

Dla celów demonstracyjnych

 [TestMethod] 
    public void splitlist() 
    { 
     var list = new decimal[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27 }; 

     var splitlists = list.SplitMaintainingOrder(10); 

     int i = 1; 

     foreach (var group in splitlists) 
     { 
      Console.WriteLine("Group {0} elements {1}", i++, String.Join(",", group));     
     } 
    } 

wyżej, otrzymuje się demo

Test Name: splitlist 
Test Outcome: Passed 
Result StandardOutput: 
Group 1 elements 1,2,3 
Group 2 elements 4,5 
Group 3 elements 6,7,8 
Group 4 elements 9,10,11 
Group 5 elements 12,13 
Group 6 elements 14,15,16 
Group 7 elements 17,18,19 
Group 8 elements 20,21 
Group 9 elements 22,23,24 
Group 10 elements 25,26,27 
4

Nieco bardziej czyste LINQ podejście, dla tego raczej starego pytania:

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> source, int n) 
{ 
    var count = source.Count(); 

    return source.Select((x, i) => new { value = x, index = i }) 
     .GroupBy(x => x.index/(int)Math.Ceiling(count/(double)n)) 
     .Select(x => x.Select(z => z.value)); 
} 
0

Aby podzielić listę rodzajowy w celu równe kawałki użyć poniżej metoda rodzajowa

private IEnumerable<IEnumerable<T>> SplitMaintainingOrder<T>(IEnumerable<T> list, int columnCount) 
 
       { 
 
        var elementsCount = list.Count(); 
 
        int rowCount = elementsCount/columnCount; 
 
        int noOfCells = elementsCount % columnCount; 
 

 
        int finalRowCount = rowCount; 
 
        if (noOfCells > 0) 
 
        { 
 
         finalRowCount++; 
 
        } 
 

 
        var toreturn = new List<IEnumerable<T>>(); 
 
        var pushto = 0; 
 
        for (int j = 0; j < columnCount; j++) 
 
        { 
 
         var start = j; 
 
         int i = 0; 
 
         var end = i; 
 
         for (i = 0; i < finalRowCount; i++) 
 
         { 
 
          if ((i < rowCount) || ((i == rowCount) && (j < noOfCells))) 
 
          { 
 
           start = j; 
 
           end = i; 
 
          } 
 
         } 
 
         toreturn.Add(list.Skip(pushto).Take(end + 1)); 
 
         pushto += end + 1; 
 
        } 
 

 
        return toreturn; 
 

 
       }

List<int> recordNumbers = new List<int>() { 1, 2, 3, 4, 5, 6,7,8,9,10,11}; 
 

 
var splitedItems = SplitMaintainingOrder<int>(recordNumbers , 4);

Output will be: 
 

 
List 1 : 1,2,3 
 
List 2 : 4,5,6 
 
List 3 : 7,8,9 
 
List 4 : 10,11

~ Szczęśliwy kodowania ..