Tout d'abord, quelle est ma situation ici ...

  • Mon SomeObject a une propriété string Status qui m'intéresse pour ce scénario.
  • La propriété Status peut contenir exactement les valeurs «Ouvert», «Fermé», «Terminé».
  • J'ai une méthode appelée FilterObjects qui renvoie un List<SomeObject>
  • La méthode accepte un argument identique à son type de retour, List<SomeObject>
  • La méthode est censée filtrer selon les cas suivants expliqués ci-dessous et renvoyer la liste des objets.
  • Le List<SomeObject> que j'envoie en tant qu'argument à ma méthode est garanti d'être en ordre (grâce à son ID et à son type).

Les cas sont (tous liés à la propriété string Status que j'ai mentionnée):

  1. Si un élément de la liste contient Status = "Finished";, éliminez tous les autres éléments qui étaient dans la liste d'origine et ne retournez que l'objet qui a le statut "Terminé".

  2. Si un élément PAS contient Status = Finished mais contient "CLOSED", je dois vérifier s'il y a un autre élément qui a la valeur "Open" après celui "CLOSED". Vous pouvez considérer cela comme "une tâche peut être fermée, mais peut être rouverte. Mais une fois terminée, elle ne peut pas être rouverte".

    S'il contient un "CLOSED" et n'a pas de "OPEN" après cet élément, j'ignorerai tous les éléments avant CLOSED et ne retournerai que l'objet CLOSED. S'il contient "OPEN" après tout fermé, je dois retourner quoi que ce soit APRÈS que FERME, en s'excluant.

J'ai également essayé d'expliquer la même chose avec mes formidables compétences en MS Paint.

enter image description here

L'objet lui-même n'est pas vraiment un problème, mais ma méthode est quelque chose comme ceci:

private List<SomeObject> FilterObjects(List<SomeObject> objectList)
{
    var objects = objectList;
    var returnList = new List<SomeObject>();
                    
    foreach (var obj in objects)
    {
        if (obj.Status == "Finished")
        {
            returnList.Add(obj);
            return returnList;
        }
    }

    return new List<SomeObject>();
}

Bref, quel serait le moyen le meilleur et le plus efficace d'appliquer toute cette logique dans cette seule méthode? Honnêtement, je ne pouvais pas aller plus loin que le premier cas que j'ai déjà implémenté, qui est le FINISHED. Tout cela pourrait-il être fait avec un peu de magie LINQ?

Il est garanti que je reçois une liste commandée ET que je n'obtiendrai jamais plus de quelques centaines d'articles, donc la collection ne sera jamais massive.

Merci d'avance pour l'aide.

5
Michael Wayne 27 janv. 2017 à 11:16

6 réponses

Meilleure réponse

Vous pouvez essayer quelque chose comme ça:

private List<SomeObject> FilterObjects(List<SomeObject> objectList)
{
    SomeObject finished = objectList.FirstOrDefault(o => o.Status.Equals("Finished"));
    if (finished != null) { return new List<SomeObject> { finished }; }

    List<SomeObject> closed = objectList.SkipWhile(o => !o.Status.Equals("Closed")).ToList();
    if (closed.Count == 1) { return closed; }
    if (closed.Count > 1) { return closed.Skip(1).ToList(); }

    // if you need a new list object than return new List<SomeObject>(objectList);
    return objectList;
}
4
Kevin Wallis 27 janv. 2017 à 08:50

Quelque chose comme ça :

    private List<SomeObject> FilterObjects(List<SomeObject> objectList)
    {
        if (objectList.Where(x => x.Status == "Finished").Any())
        {
            return objectList.Where(x => x.Status == "Finished").ToList();
        }

        else if (objectList.Where(x => x.Status == "Closed").Any())
        {
            if (objectList.FindIndex(x => x.Status == "Closed") == objectList.Count() - 1)
            {
                return objectList.Where(x => x.Status == "Closed").ToList();
            }
            else
            {
                return objectList.GetRange(objectList.FindIndex(x => x.Status == "Closed") + 1, objectList.Count() - (objectList.FindIndex(x => x.Status == "Closed") + 1));
            }
        }

        return objectList;
     }
0
Donny Situngkir 27 janv. 2017 à 09:11

Vous pouvez écrire une méthode comme celle-ci. C'est le strict minimum, vous devrez ajouter une vérification nulle et une gestion des exceptions.

public List<SomeCls> GetResult(List<SomeCls> lstData)
    {
        List<SomeCls> lstResult;

        if(lstData.Any(x=>x.Status=="Finished"))
        {
            lstResult = lstData.Where(x=>x.Status=="Finished").ToList();
        }

        else if(lstData.Any(x=>x.Status=="Closed"))
        {
            // Here assuming that there is only one Closed in whole list
            int index = lstData.FindIndex(0,lstData.Count(),x=>x.Status=="Closed");
            lstResult = lstData.GetRange(index,lstData.Count()-index);

            if(lstResult.Count()!=1) // check if it contains Open.
            {
                lstResult = lstResult.Where(x=>x.Status=="Open").ToList();
            }
        }
        else // Only Open
        {
            lstResult = lstData;
        }
        return lstResult;
    }
0
Hakunamatata 27 janv. 2017 à 09:11

Je l'ai fait de cette façon:

public static IEnumerable<SomeObject> convert(this IEnumerable<SomeObject> input)
{
    var finished = input.FirstOrDefault(x => x.Status == "Finished");
    if (finished != null)
    {
        return new List<SomeObject> {finished};
    }
   return input.Aggregate(new List<SomeObject>(), (a, b) =>
   {
       if (!a.Any())
       {
          a.Add(b);
       }
       else if (b.Status == "Open")
       {
          if (a.Last().Status == "Closed")
          {
            a.Remove(a.Last());
          }
          a.Add(b);
       }
       else if (b.Status == "Closed")
       {
          a = new List<SomeObject> {b};
       }
       return a;
   });
}
1
Maksim Simkin 27 janv. 2017 à 09:23

Je ne prendrais vraiment pas la peine d'utiliser Linq pour cela, car vous allez créer une instruction trop compliquée à gérer ou vous aurez besoin de plusieurs itérations de boucle. J'irais plutôt pour quelque chose comme ça:

    private List<SomeObject> FilterObjects(List<SomeObject> objectList)
    {
        int lastClosed = -1;
        for (int i = 0; i < objectList.Count; i++)
        {
            if (objectList[i].Status == "Closed")
                lastClosed = i;
            else if (objectList[i].Status == "Finished")
                return new List<SomeObject>() { objectList[i] };
        }

        if (lastClosed > -1)
            if (lastClosed == objectList.Count - 1)
                return new List<SomeObject>() { objectList[lastClosed] };
            else 
                return objectList.Skip(lastClosed + 1).ToList();
        else
            return objectList;
    }

EDIT: légèrement modifié le dernier bit de code pour qu'il ne déclenche pas d'exception si objectList est vide

3
Innat3 27 janv. 2017 à 10:21

LINQ n'est pas bien adapté et inefficace pour les scénarios où vous devez appliquer une logique basée sur les éléments précédents / suivants d'une séquence.

La manière optimale d'appliquer votre logique est d'utiliser une seule boucle et de suivre l'état Closed et la position où le changement d'état s'est produit. À la fin, vous retournerez un seul élément à cette position si le dernier état est Closed, ou une plage commençant à cette position sinon.

static List<SomeObject> FilterObjects(List<SomeObject> objectList)
{
    int pos = 0;
    bool closed = false;
    for (int i = 0; i < objectList.Count; i++)
    {
        var item = objectList[i];

        if (item.Status == "Finished")
            return new List<SomeObject> { item };

        if (item.Status == (closed ? "Opened" : "Closed"))
        {
            pos = i;
            closed = !closed;
        }
    }
    return objectList.GetRange(pos, closed ? 1 : objectList.Count - pos);
}
2
Ivan Stoev 27 janv. 2017 à 12:37