2010-10-08 16 views
10

J'essaie de diviser une collection en plusieurs collections tout en conservant un tri que j'ai sur la collection. J'ai essayé d'utiliser la méthode d'extension suivante, mais elle les casse incorrectement. Fondamentalement, si je devais regarder les articles dans la collection, l'ordre devrait être le même par rapport aux collections rompues jointes. Voici le code que je utilise cela ne fonctionne pas:Diviser la collection C# en parties égales, maintenir le tri

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; 
     } 
  • parties int = nombre de sous enumerables
+0

double possible de [LINQ Liste des partitions dans des listes de 8 membres.] (Http://stackoverflow.com/questions/3773403/linq-partition-list-into-lists- de-8-membres) –

+1

@Kirk Woll: Ce n'est pas pareil, dans la question que vous avez donné la méthode d'extension prend le nombre maximum d'éléments dans un sous-énumérable tandis qu'ici, si je comprends bien, nous avons désiré le nombre de sous-énumérables. –

+0

@Andrew, vous avez raison, je vois que vous êtes le point –

Répondre

10

Je devais faire usage de ceci pour comparer une liste d'objets les uns aux autres dans les groupes de 4 ... il gardera les objets dans l'ordre que l'original possédait. Pourrait être élargi pour faire autre chose que « Liste »

/// <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

Merci. J'ai bien travaillé. –

+0

N'y at-il pas un bug avec cet algorithme .. Quelqu'un at-il essayé de le vérifier? Il me semble que la ligne int maxSize = (int) Math.Ceiling (list.Count/(double) totalPartitions) pourrait conduire à des comptes maxsize invalides. Par exemple; une liste de 21 éléments à diviser indique les 10 groupes souhaités; ne créera en réalité que 7 groupes de 3 éléments avec 3 groupes vides .. raison étant que le calcul de taille arrondira de 2,1 à 3 –

+0

Oui, j'imagine que c'est le cas. Si j'ai une liste de l'alphabet et que je mets à diviser cela en 100 partitions, je récupèrerai un objet qui aura le premier 0-25 rempli de 1 objet et 26-99 sera vide. LINQ à partir de 3.5 corrigera probablement le problème, je n'ai pas regardé cela dans quelques années. Il a fait ce que j'avais besoin de faire, et évidemment ce dont le PO avait besoin. –

0

Si je comprends bien, vous voulez briser dénombrable sur plusieurs parties avec une taille égale et sans casser l'ordre de vos éléments. Il semble que le seul choix est d'obtenir la longueur de votre entrée énumérable en premier, donc vous aurez besoin d'au moins deux itérations à travers l'énumérable.

+0

@Kirk Woll: cela dépend de ce qui est nécessaire. Si "parties" est le nombre d'éléments dans un énumérable qu'il est facile à résoudre. Mais le code ci-dessus me fait penser que "parties" est le nombre de sous-énumérables désirés, pas d'éléments dans les énumérables. –

+0

"parts" est le nombre de sous-énumérables –

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

Dans votre exemple, "splits" devient un IEnumerable > mais il doit être un IEnumerable > –

+0

Corrigez-moi si je me trompe, mais IGrouping n'est pas et IEnumerable ? – kevev22

2

bibliothèque MoreLINQ de Jon Skeet pourrait faire l'affaire pour vous:

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... 

Un autre exemple:

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

Quand je scrutais le contenu des lots, la la commande a été conservée.

+0

Dans ces exemples, il semble vouloir que "parts" soit la quantité d'éléments dans chaque collection. Dans MON exemple, je veux que "parts" soit le nombre de sous-collections à créer. –

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

Ce n'est pas correct.Cela va créer des groupes avec une taille égale à des parties. –

0

Cela fera exactement comme demandé. Il couvrira également les regroupements inégaux, c'est-à-dire27 éléments en 10 groupes produiront 7 groupes de trois et 3 groupes de deux

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

À des fins de démonstration

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

Les rendements de démonstration ci-dessus

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

A LINQ légèrement plus propre approche, pour cette question plutôt ancienne:

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

Pour diviser une liste générique pour des morceaux égaux ci-dessous utilisent la méthode générique

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

~ Bonne codage ..