2
\$\begingroup\$

I'm attempting to create a function that will select the correct carton based on the qty of the carton content.

Here are my cartons that have the number of items they can hold:

SMALL_PASCAL = 300
BIG_PASCAL = 600
BABY_BOX = 1200
A485_1201 = 1800
A4140_1901 = 3000
A485 = 5000

And here is the method that will return the CartonType:

/// <summary>
/// Get Carton Type
/// </summary>
/// <param name="qty"></param>
/// <returns></returns>
[Test]
private static CartonType GetCartonType(int qty)
{
     if (qty <= 300)
     {
         return CartonType.SMALL_PASCAL;
     }
     else if (qty > 300 && qty <= 600)
     {
         return CartonType.SMALL_PASCAL;
     }
     else if (qty > 600 && qty <= 1200)
     {
         return CartonType.BABY_BOX;
     }
     else if (qty > 1200 && qty <= 1800)
     {
         return CartonType.A485_1201;
     }
     else if (qty >1800 && qty <=3000)
     {
         return CartonType.A4140_1901;
     }
     else // 5000 or more.
     {
         return CartonType.A485;
     }
}

Calling the method like so:

int qty = 1540;
Console.WriteLine(GetCartonType(qty));

Output:

A485_1201

Is there a better way to achieve this rather than an if statement? Also, I just had a thought that what if the qty is like 10,000? I would then require 2 A485.

\$\endgroup\$
4
\$\begingroup\$

Is there a better way to achieve this rather than an If statement?

Yes. You could have an enumeration of the container types and their capacities, in increasing order by capacity, loop over in order, and as soon as you find one that's big enough, return it.

It's perhaps easier to see after you simplify the if-else chain, by removing redundant conditions, for example:

 if (qty <= 300)
 {
     return CartonType.SMALL_PASCAL;
 }
 if (qty <= 600)
 {
     return CartonType.SMALL_PASCAL;
 }
 if (qty <= 1200)
 {
     return CartonType.BABY_BOX;
 }
 // ...
\$\endgroup\$
  • \$\begingroup\$ do you have any suggestion if there is for anything over 5000? so eg the largest box and a small pascal could be required? \$\endgroup\$ – user1234433222 Dec 16 '18 at 23:21
  • 2
    \$\begingroup\$ @user1234433222 sure, but we don't implement feature requests here... You could subtract from the quantity the capacity of the selected container, and then call the method again. \$\endgroup\$ – janos Dec 16 '18 at 23:23
  • \$\begingroup\$ Thanks for that, working backwards might be the key to this. \$\endgroup\$ – user1234433222 Dec 16 '18 at 23:24
3
\$\begingroup\$

Bug

There is a bug in your example where you are using SMALL_PASCAL twice:

 if (qty <= 300)
 {
     return CartonType.SMALL_PASCAL;
 }
 else if (qty > 300 && qty <= 600)
 {
     return CartonType.SMALL_PASCAL;
 }

More flexibility with free mappings

I would take a different approach for the other answer. Quantities are something that might change in time or be different in different contexts so I wouldn't use them as enum or const values but instead created a pure enum first:

public enum CartonType
{
    Undefined = 0,
    SMALL_PASCAL,
    BIG_PASCAL,
    BABY_BOX,
    A485_1201,
    A4140_1901,
    A485,
    Default = A485
}

where there are two new items: Undefined and Default - that we can conveniently use in a new extension method. It would map Quantity to CartonType for any collection:

public static CartonType GetCartonType(this int quantity, IEnumerable<(int Quantity, CartonType Type)> mappings)
{
     var mapping = mappings.FirstOrDefault(m => quantity <= m.Quantity);
     return 
        mapping.Type == CartonType.Undefined 
            ? CartonType.Default 
            : mapping.Type;
}

With this you can specify different quantities if necessary and use them as a parameter:

var quantityCartonTypeMappings = new(int Quantity, CartonType Type)[]
{
    (300, CartonType.SMALL_PASCAL),
    (600, CartonType.BIG_PASCAL),
};

var quantity = 700;

var cartonType = quantity.GetCartonType(quantityCartonTypeMappings);
\$\endgroup\$
2
\$\begingroup\$

I am not a big fan of "smart" enum because they do not scale well (what if you will have a more complex logic?) and they force you to spread business logic all around in your code. Because of this - in most cases - I'd suggest to use the approach in t3chb0t's answer ("mapping" might be even moved to a separate configuration/rule file).

For simple cases you have, however, an easier approach:

enum CartonType
{
    SMALL_PASCAL = 300,
    BIG_PASCAL = 600,
    BABY_BOX = 1200,
    A485_1201 = 1800,
    A4140_1901 = 3000,
    A485 = 5000,
}

CartonType GetCartonType(int quantity)
{
    return Enum.GetValues(typeof(CartonType))
        .Cast<CartonType?>()
        .OrderByDescending(x => x)
        .LastorDefault(x => quantity <= (int)x) ?? CartonType.A485;
}

I don't like that CartonType.A485 hard-coded default then you might need to make it slightly more complex:

CartonType GetCartonType(int quantity)
{
    var types = Enum.GetValues(typeof(CartonType));
    var biggest = types.Cast<CartonType>().Max();

    return types
        .Cast<CartonType?>()
        .OrderByDescending(x => x)
        .LastOrDefault(x => quantity <= (int)x) ?? biggest;
}

Simply used like this:

Debug.Assert(GetCartonQuantity(100) == CartonType.SMALL_PASCAL);
Debug.Assert(GetCartonQuantity(1000) == CartonType.BABY_BOX);
Debug.Assert(GetCartonQuantity(10000) == CartonType.A485);

Note: if you put this "business knowledge" inside your enum then you must write proper tests not only for GetCartonQuantity() but also for CartonType itself (to be sure values are consistent).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.