6
votes

Remplacement de l'instruction if else par un modèle de conception ou une meilleure approche

Ce code n'a pas l'air propre et ceci si la condition peut augmenter

public int VisitMonth(int months)
    {
        int visit = 0;

        if (months <= 1)
        {
            visit = 1;
        }
        else if (months <= 2)
        {
            visit = 2;
        }
        else if (months <= 4)
        {
            visit = 3;
        }
        else if (months <= 6)
        {
            visit = 4;
        }
        else if (months <= 9)
        {
            visit = 5;
        }
        else if (months <= 12)
        {
            visit = 6;
        }
        else if (months <= 15)
        {
            visit = 7;
        }
        else if (months <= 18)
        {
            visit = 8;
        }
        else if (months <= 24)
        {
            visit = 9;
        }
        else if (months <= 30)
        {
            visit = 10;
        }
        else if (months <= 36)
        {
            visit = 11;
        }
        else if (months <= 48)
        {
            visit = 12;
        }
        else if (months <= 60)
        {
            visit = 13;
        }
        else
        {
            visit = 14;
        }
        return visit;
    }

Y a-t-il une meilleure solution à ce problème? Malheureusement, cette fonction n'est pas linéaire, il n'est donc pas facile de la coder de manière mathématique.


3 commentaires

Mettez tout cela dans un tableau associatif et utilisez une boucle. Cela vous ramène à une instruction if .


en C # 8, cela pourrait être une expression de commutateur avec des clauses quand ...


Pour clarifier, est-ce pour compter les visites tout au long de l'année ou sur un seul mois à quelle fréquence elles visitent?


6 Réponses :


5
votes

Peut-être en C # 8 (cette fonctionnalité n'est pas encore officielle, mais fonctionne dans les IDE récents si vous l'activez):

public int VisitMonth(int months)
{
    switch (months)
    {
        case int j when j <= 1: return 1;
        case int j when j <= 2: return 2;
        case int j when j <= 4: return 3;
        // etc
        default: return 14;
    }
}

Vous pouvez faire similaire dans plus tôt C #, puisqu'il s'agit d'une méthode:

int months = ...;
int visit = months switch
{
    int j when j <= 1 => 1,
    int j when j <= 2 => 2,
    int j when j <= 4 => 3,
    int j when j <= 6 => 4,
    int j when j <= 9 => 5,
    // ...
    _ => 42 // default
};


4 commentaires

L'une des fonctionnalités intéressantes de C # 8, j'aimerais qu'elle soit déjà publiée.


@Greg cela fonctionne dans VS 16 preview 2 - vous devez utiliser 8.0 , cependant - la dernière ne suffit pas ; il est également disponible dans l'interface utilisateur, sous le nom "C # 8.0 (beta)"


Je vais peut-être devoir le vérifier, je veux également utiliser la nouvelle application de nullité. Où aucune valeur par défaut est nulle, sauf indication contraire.


@ user6392608 alors vous devrez utiliser quelque chose de plus laid :)



2
votes

Vous pouvez utiliser un dictionnaire pour stocker les mois sous forme de clés et les visites sous forme de valeurs.

static readonly (int Months,int Visit)[] monthsToVisits = new (int,int)[] 
{ 
    (1,1), 
    (2,2), 
    (4,3), 
    (6,4) 
};

public int VisitMonth(int months) => 
    monthsToVisits.First(x => months <= x.Months).Visit;

etc ...

Avec cela, vous pouvez facilement regarder la plus grande clé qui est juste plus que les mois que vous voulez juste vérifier, et la valeur associée.

int months = 42;
int visit = monthsToVisits.Where(x => x.Key > months)
                        .OrderBy(x => x.Key)
                        .First().Value;


p >

MISE À JOUR

Comme le disait @Marc Gravell, utiliser un dictionnaire est une solution très inefficace. Une meilleure approche serait un tableau statique.

var monthsToVisits= new Dictionary<int,int>
{
    {1,1},
    {2,2},
    {4,3},
    {6,4}
};


2 commentaires

cela semble convaincant, mais il est en fait très inefficace; le besoin de faire un OrderBy etc à chaque fois est très cher, et vous n’utilisez jamais les fonctionnalités clés d’un Dictionary - cela ne vous achète tout simplement rien - vous feriez mieux d'utiliser un aray plat et juste un First


plus précisément, quelque chose comme public int VisitMonth (int month) => monthToVisits.First (x => mois <= x.Months) .Visit; static readonly (int Month, int Visit) [] monthToVisits = new (int, int) [] {(1,1), (2,2), (4,3), (6,4)};



5
votes

Devrait être plus approprié pour être réutilisé: Vous pouvez écrire une classe "Interval" avec la méthode "inRange" comme ceci:

public static readonly List<Interval<int>> range = new List<Interval<int>>
        {
                new Interval<int>(1, 0, 1),
                new Interval<int>(2, 1, 2),
                new Interval<int>(3, 2, 4),
                new Interval<int>(4, 4, 6),
                new Interval<int>(5, 6, 9),
                new Interval<int>(6, 9, 12),
                new Interval<int>(7, 12, 15),
                new Interval<int>(8, 15, 18),
                new Interval<int>(9, 18, 24),
                new Interval<int>(10, 24, 30),
                new Interval<int>(11, 30, 36),
                new Interval<int>(12, 36, 48),
                new Interval<int>(13, 48, 60),
                new Interval<int>(14, 60, int.MaxValue)
        };

var months = 5;
var visit = range.Where(x => x.InRange(months)).Select(x => x.Visit).FirstOrDefault();

Et puis utiliser comme ceci:

 public struct Interval<T>
       where T : IComparable
{
    public T Start { get; set; }
    public T End { get; set; }
    public T Visit { get; set; }

    public Interval(T visit, T start, T end)
    {
        Visit = visit;
        Start = start;
        End = end;
    }

    public bool InRange(T value)
    {
      return ((!Start.HasValue || value.CompareTo(Start.Value) > 0) &&
          (!End.HasValue || End.Value.CompareTo(value) >= 0));
    }
}


0 commentaires

0
votes

Cela ne fonctionnera que tant que la valeur de retour ( visite ) augmentera toujours linéairement pour chaque condition disponible (c'est-à-dire que visite augmente de 1 dans chaque si / else if block).

static void Main(string[] args)
{
    Console.WriteLine($"0: {VisitMonth(0)}");
    Console.WriteLine($"5: {VisitMonth(5)}");
    Console.WriteLine($"60: {VisitMonth(60)}");
    Console.WriteLine($"100: {VisitMonth(100)}");
    Console.ReadLine();
}

Cette approche a l'avantage de ne pas avoir à définir la valeur de retour ( visite code>) est. Ce qui rend cela extensible dans le sens où si une exigence arrive là où il y a une nouvelle limite quelque part au milieu (par exemple 22), vous n'aurez pas à remapper la valeur de visite pour chaque condition car il est simplement dérivé en fonction de sa position dans le tableau.


Voici un exemple de ce fonctionnement:

static readonly int[] _monthLimits = new int[] { 1, 2, 4, 6, 9, 12, 15, 18, 24, 30, 36, 48, 60 };

public static int VisitMonth(int months)
{
    int visit = 0;

    var maxMonths = _monthLimits[_monthLimits.Length - 1];
    if (months <= maxMonths)
    {
        // Only iterate through month limits if the given "months" is below the max available limit
        for (var i = 0; i < _monthLimits.Length; i++)
        {
            if (months <= _monthLimits[i])
            {
                visit = i + 1;
                break;
            }
        }
    }
    else
    {
        // The given "months" is over the max, default to the size of the array
        visit = _monthLimits.Length + 1;
    }

    return visit;
}

entrer l'image description ici


0 commentaires

0
votes

Une façon de procéder serait de stocker les valeurs que vous comparez dans une List , puis de renvoyer l ' index + 1 de premier élément qui remplit la condition mois , ou la liste Count + 1 si aucun ne remplit cette condition.

Nous utilisons int? pour nous permettre d'obtenir un résultat null lorsque nous appelons FirstOrDefault s'il n'y a pas de correspondance (nous ne utilisez int , car default (int) == 0 afin que nous ne sachions pas si nous correspondions au premier élément à l'index 0 ou s'il y en avait aucune correspondance et la valeur par défaut 0 a été renvoyée). De cette façon, nous pouvons tester un résultat null (ce qui signifie qu'il n'y a pas de correspondance), et retourner List.Count + 1 dans ce cas.

Cela réduit votre méthode à 2 lignes de code, et l'ajout d'une nouvelle condition comme if (mois est aussi simple que d'ajouter un 120 au valeurs affectation:

public static int Visit(int months)
{
    var values = new List<int?> {1, 2, 4, 6, 9, 12, 15, 18, 24, 30, 36, 48, 60};

    return (values.Select((v, i) => new {value = v, index = i})
        .FirstOrDefault(i => months <= i.value)?.index ?? values.Count) + 1;
}


0 commentaires

3
votes
void Main()
{
    var conditionsChain = new SimpleCondition(0, 1);
        conditionsChain.AddNext(new SimpleCondition(1, 1))
        .AddNext(new SimpleCondition(2, 2))
        .AddNext(new SimpleCondition(4, 3))
        .AddNext(new SimpleCondition(6, 4))
        .AddNext(new SimpleCondition(9, 5))
        .AddNext(new SimpleCondition(12, 6))
        .AddNext(new SimpleCondition(15, 7))
        .AddNext(new SimpleCondition(18, 8))
        .AddNext(new SimpleCondition(24, 9))
        .AddNext(new SimpleCondition(30, 10))
        .AddNext(new SimpleCondition(36, 11))
        .AddNext(new SimpleCondition(48, 12))
        .AddNext(new SimpleCondition(60, 13))
        .AddNext(new SimpleCondition(14));

    for (int i = 0; i < 62; i++)
    {
        Console.WriteLine($"{i}: {conditionsChain.Evaluate(i) - VisitMonth(i)}");
    }
}

class SimpleCondition
{
    private SimpleCondition _next;

    private int _key;
    private int _result;

    public SimpleCondition(int key, int result)
    {
        _key = key;
        _result = result;
    }

    public SimpleCondition(int result) : this(-1, result)
    {
    }

    public int Evaluate(int key)
    {
        if(_key == -1)
        {
            return _result; 
        }

        if(key <= _key)
        {
            return _result;
        }
        else
        {
            if(_next == null)
            {
                throw new Exception("Default condition has not been configured.");
            }
            return _next.Evaluate(key); 
        }
    }

    public SimpleCondition AddNext(SimpleCondition next)
    {
        return _next = next;
    }
}

0 commentaires