Home > database >  C# Dictionary using enum
C# Dictionary using enum

Time:12-23

I don't understand what mistake I'm making.

I'm calculating the general average of some students and from this average I give a concept to the class.

public enum Grade
    {
        A,
        B,
        C,
        D,
        E
    }

var overallAverage = fullScore / numberStudents;
Grade generalGrade;

var cases = new Dictionary<Func<decimal,bool>,Grade>
                        {
                            {x => overallAverage < 2, generalGrade = Grade.E},
                            {x => overallAverage < 4, generalGrade = Grade.D},
                            {x => overallAverage < 6, generalGrade = Grade.C},
                            {x => overallAverage < 8, generalGrade = Grade.B},
                            {x => overallAverage < 10, generalGrade = Grade.A}
                        };   
                        
Console.WriteLine($"Overall Average: {overallAverage} - GRADE: {generalGrade}");

As I'm starting to study with C# I decided to use Dictionary, but the result for General Grade is not what expected. For any average the general grade is returning A. Overall average is returning the value correctly.

CodePudding user response:

You declare dictionary with Key being lambda: Dictionary<Func<decimal, bool>, Grade>, technically you query it with a help of Linq:

 using System.Linq;

 ...

 // Techincally, it's possible to use the current dictionary...
 generalGrade = cases
   .Where(pair => pair.Key((decimal)overallAverage))
   .Select(pai => pai.Value)
   .Min();

However, it's not a good practice: note, that we query dictionary, instead of obtaining Value by Key. If you want to use Dictionary, try

 // Just mapping average to grade
 var cases = new Dictionary<int, Grade>() {
   {0, Grade.E},
   {1, Grade.E},
   {2, Grade.E},
   {3, Grade.D},
   {4, Grade.D},
   {5, Grade.C},
   {6, Grade.C},
   {7, Grade.B},
   {8, Grade.B},
   {9, Grade.A},
  {10, Grade.A},
 };

 // Typical dictionary usage: having key (truncated average)
 // we obtain value - corresponding grade
 generalGrade = cases[(int) overallAverage];

In case of arbitrary borders, I vote for List<> or array and query:

 using System.Linq;

 ...

 IReadOnlyList<(grade : Grade, min : double)> cases = 
   new List<(grade : Grade, min : double)>() {
     (Grade.A, 8), // to have A student must get at least 8 average
     (Grade.B, 6), // to have B student must get at least 6 average
     (Grade.C, 4),
     (Grade.D, 2),
     (Grade.E, 0), 
 };

 ...

 generalGrade = cases
   .Where(item => item.min <= overallAverage)
   .OrderBy(item => item.grade)
   .First()
   .grade; 

CodePudding user response:

A dictionary stablishes a relation between a key and a value, in this case you are passing a function as the key, and to each function you are assigning a grade, this is strange and it it maybe not what you were trying to do.

If you really want to use a dictionary then you maybe need to:

var cases = new Dictionary<int, Grade>
{
   { 1, generalGrade = Grade.E},
   { 2, generalGrade = Grade.E},
   { 3, generalGrade = Grade.D},
   { 4, generalGrade = Grade.D},
   // ... add the rest of the cases
};   

Grade generalGrade = cases[(int)overallAverage];

But that is not efficient at all, and also I used int, but if you want to use decimals then that is not an optimal solution.

Instead try:

Func<double, Grade> casesResolver = grade => 
{
   if (grade <= 2) return Grade.E;
   if (grade <= 4) return Grade.D;
   // the rest of the cases here...

   return Grade.A;
}

// finally
Grade generalGrade = casesResolver(overallAverage);
Console.WriteLine($"Overall Average: {overallAverage} - GRADE: {generalGrade}");

CodePudding user response:

This does not work, because the dictionary expects a key type as the first type argument. This is often a string or int or enum type. A function does not work in this place, because it will not be evaluated as part of a dictionary lookup, it will only be tested for reference equality.

You can use a switch expression (C# 8.0) and relational patterns (C# 9.0) to formulate these conditions

Grade grade = overallAverage switch {
    < 2 => Grade.E,
    < 4 => Grade.D
    < 6 => Grade.C
    < 8 => Grade.B
    _ => Grade.A
};

If your C# version is an older one, use a series of if-statements statements:

Grade grade;
if (overallAverage < 2) { grade = Grade.E; }
else if (overallAverage < 4) { grade = Grade.D; }
else if (overallAverage < 6) { grade = Grade.C; }
else if (overallAverage < 8) { grade = Grade.B; }
else { grade = Grade.A; }

In both solutions you do not have to test a condition for the last case, because, since all other possibilities have been handled, the grade A is the only one left that can apply. Note that when a condition is matched, the succeeding ones are not evaluated any more.

It is still possible to use a data-based solution by simply using an array with average values and corresponding grade

(decimal average, Grade grade)[] limits =
    { (2m, Grade.E), (4m, Grade.D), (6m, Grade.C), (8m, B) };

Grade grade = Grade.A;
foreach (var limit in limits) {
    if (overallAverage  < limit.average) {
        grade = limit.grade;
        break;
    }
}

The m as number suffix denotes a decimal.

Take care to use the right comparison operator (< or <=).

  • Related