3 votes

Diviser une liste en une liste de listes, division sur un élément

Peut-on réécrire ce qui suit de manière à ce qu'il utilise LINQ (plutôt qu'un bon vieux foreach boucles)

IEnumerable<IEnumerable<T>> SplitIntoSections<T>(IEnumerable<T> content, 
    Func<T, bool> isSectionDivider)
{
    var sections = new List<List<T>>();
    sections.Add(new List<T>());
    foreach (var element in content)
    {
        if (isSectionDivider(element))
        {
            sections.Add(new List<T>());
        }
        else
        {
            sections.Last().Add(element);
        }
    }

    return sections;
}

Je pensais avoir trouvé un moyen de le faire (en faisant appel à des colections FSharp) lorsque j'ai réalisé que cela pouvait être fait avec un fichier de type foreach boucle.

2voto

casperOne Points 49736

Vous ne voulez pas utiliser LINQ ici. Vous ne serez pas en mesure d'ordonner et de grouper de la bonne manière sans faire quelque chose de bizarre.

La chose la plus simple à faire est de prendre votre code et de lui faire différer l'exécution en utilisant la fonction yield déclaration . Une façon simple de procéder est la suivante :

IEnumerable<IEnumerable<T>> SplitIntoSections<T>(this IEnumerable<T> source, 
    Func<T, bool> sectionDivider)
{
    // The items in the current group.
    IList<T> currentGroup = new List<T>();

    // Cycle through the items.
    foreach (T item in source)
    {
        // Check to see if it is a section divider, if
        // it is, then return the previous section.
        // Also, only return if there are items.
        if (sectionDivider(item) && currentGroup.Count > 0)
        {
            // Return the list.
            yield return currentGroup;

            // Reset the list.
            currentGroup = new List<T>();
        }

        // Add the item to the list.
        currentGroup.Add(item);
    }

    // If there are items in the list, yield it.
    if (currentGroup.Count > 0) yield return currentGroup;
}

Il y a un problème ici ; pour les très grands groupes, il est inefficace de stocker les sous-groupes dans une liste, ils devraient être diffusés également. Le problème avec votre approche est que vous avez une fonction qui doit être appelée sur chaque élément ; cela interfère avec l'opération de flux puisqu'on ne peut pas réinitialiser le flux en arrière une fois que le groupement est trouvé (comme vous avez effectivement besoin de deux méthodes qui donnent des résultats).

0voto

Jon Skeet Points 692016

Vous pourriez utiliser un effet secondaire qui n'est utilisé que dans une zone bien définie... c'est assez malodorant, mais.. :

int id = 0;
return content.Select(x => new { Id = isSectionDivider(x) ? id : ++id,
                                 Value = x })
              .GroupBy(pair => pair.Id, pair.Value)
              .ToList();

Il doit y avoir une meilleure alternative cependant... Aggregate vous y amènera si nécessaire...

return content.Aggregate(new List<List<T>>(), (lists, value) => {
                             if (lists.Count == 0 || isSectionDivider(value)) {
                                 lists.Add(new List<T>());
                             };
                             lists[lists.Count - 1].Add(value);
                             return lists;
                         });

... mais dans l'ensemble, je suis d'accord avec casperOne il s'agit d'une situation qu'il vaut mieux traiter en dehors de LINQ.

0voto

Ani Points 59747

Voici une solution inefficace mais purement LINQ :

var dividerIndices = content.Select((item, index) => new { Item = item, Index = index })
                            .Where(tuple => isSectionDivider(tuple.Item))
                            .Select(tuple => tuple.Index);

return new[] { -1 }
        .Concat(dividerIndices)
        .Zip(dividerIndices.Concat(new[] { content.Count() }),
            (start, end) => content.Skip(start + 1).Take(end - start - 1));

0voto

AakashM Points 32891

Eh bien, j'utilise une méthode LINQ ici, bien que ce ne soit pas particulièrement dans l'esprit de votre question, je pense :

static class Utility
{
    // Helper method since Add is void
    static List<T> Plus<T>(this List<T> list, T newElement)
    {
        list.Add(newElement);
        return list;
    }

    // Helper method since Add is void
    static List<List<T>> PlusToLast<T>(this List<List<T>> lists, T newElement)
    {
        lists.Last().Add(newElement);
        return lists;
    }

    static IEnumerable<IEnumerable<T>> SplitIntoSections<T>
         (IEnumerable<T> content, 
          Func<T, bool> isSectionDivider)
    {
        return content.Aggregate(                      // a LINQ method!
            new List<List<T>>(),                       // start with empty sections
            (sectionsSoFar, element) =>
            isSectionDivider(element)
                ? sectionsSoFar.Plus(new List<T>())
                  // create new section when divider encountered

                : sectionsSoFar.PlusToLast(element)
                  // add normal element to current section
            );
    }
}

J'espère que vous noterez l'absence totale de vérification des erreurs, si vous décidez d'utiliser ce code...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X