Refactor Large Switch Statement - c#

I have written this method that "guesses" a correct font-awesome icon to apply to an expense based on user input from a form submission - validation is done before calling this method. If no conditions match, it returns a generic icon:
public static class IconService
{
public static string GuessExpenseIcon(string input)
{
string expenseName = input.ToLower();
string expenseIcon;
switch (expenseName)
{
case string a when a.Contains("phone"):
case string b when b.Contains("mobile"):
expenseIcon = "fas fa-mobile-alt";
break;
case string a when a.Contains("rent"):
case string b when b.Contains("mortgage"):
case string c when c.Contains("house"):
case string d when d.Contains("flat"):
case string e when e.Contains("apartment"):
expenseIcon = "fas fa-home";
break;
case string a when a.Contains("gas"):
case string b when b.Contains("util"):
expenseIcon = "fas fa-burn";
break;
case string a when a.Contains("electric"):
case string b when b.Contains("power"):
expenseIcon = "fas fa-bolt";
break;
case string a when a.Contains("petrol"):
case string b when b.Contains("diesel"):
case string c when c.Contains("fuel"):
expenseIcon = "fas fa-gas-pump";
break;
case string a when a.Contains("food"):
case string b when b.Contains("groceries"):
case string c when c.Contains("eat"):
case string d when d.Contains("take"):
expenseIcon = "fas fa-utensils";
break;
case string a when a.Contains("water"):
expenseIcon = "fas fa-shower";
break;
case string a when a.Contains("car"):
case string b when b.Contains("van"):
expenseIcon = "fas fa-car";
break;
case string a when a.Contains("internet"):
case string b when b.Contains("network"):
expenseIcon = "fas fa-wifi";
break;
case string a when a.Contains("spotify"):
expenseIcon = "fab fa-spotify";
break;
case string a when a.Contains("bus"):
case string b when b.Contains("coach"):
expenseIcon = "fas fa-bus";
break;
case string a when a.Contains("charity"):
case string b when b.Contains("donation"):
expenseIcon = "fas fa-hand-holding-heart";
break;
case string a when a.Contains("aws"):
expenseIcon = "fab fa-aws";
break;
default:
expenseIcon = "fas fa-money-bill-alt";
break;
}
return expenseIcon;
}
}
My question is: is a switch statement this large the best way to achieve this?
I know I may just be prematurely optimizing as I haven't noticed negative performance but for some reason it just doesn't seem right to me.

I'd define a Dictionary and use that:
public static class IconService
{
private static Dictionary<string, string> _expenseIcons = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) {
{ "phone", "fa-mobile-alt" },
{ "mobile", "fa-mobile-alt" },
{ "rent", "fa-mobile-alt" },
{ "mortgage", "fa-mobile-alt" },
{ "house", "fa-mobile-alt" },
{ "flat", "fa-mobile-alt" },
{ "apartment", "fa-mobile-alt" }
/* etc */
};
public static string GuessExpenseIcon(string input)
{
if (_expenseIcons.TryGetValue(input, out string expenseIcon)) // if the icon is found in the dictionary
{
return $"fas {expenseIcon}";
}
// default
return "fas fa-money-bill-alt";
}
}
You've said that you need to kind of guess it from a string, a naive tokenization (splitting by string) might work:
public static string GuessExpenseIcon(string input)
{
string[] parts = input.Split();
foreach (string part in parts)
{
if (_expenseIcons.TryGetValue(part, out string expenseIcon)) // if the icon is found in the dictionary
{
return $"fas {expenseIcon}";
}
}
// default
return "fas fa-money-bill-alt";
}
I've used a case-insensitive string comparer for the dictionary key, so you don't need to do the .ToLower() bit. I've also taken the common "fas" part of the icon out from what we store in the dictionary, since every icon has it.
I don't think this is necessarily better than your switch statement, but it's more elegant solution, and it also opens up the possibility of changing how _expenseIcons is configured. For example, you could load it from a config file, etc.
In terms of efficiency, we're initialising the dictionary once for the lifetime of the application / appdomain. The efficiency of lookups in the dictionary itself is close to O(1):
Retrieving a value by using its key is very fast, close to O(1), because the Dictionary<TKey,TValue> class is implemented as a hash table.

How about this?
class MatchPattern
{
public string [] Patterns {get;set;}
// This is to asume that you have more logic
// If all the logic of GetIcon is to return a simple string, you can
// replace with
// public string IconName {get;set;}
// instead.
public Func<string, string> GetIcon {get;set;}
}
public class IconService
{
private MatchPatterns[] _patterns;
public IconService()
{
_patterns=new []
{
new MatchPattern
{
Patterns=new[]{"phone", "mobile"},
GetIcon=(x)=>"fas fa-mobile-alt"
},
new MatchPattern
{
Patterns=new[]{"rent", "mortgage", "house", "flat", "apartment"},
GetIcon=(x)=>"fas fa-home"
}
,
// Here is more
}
}
public static string GuessExpenseIcon(string input)
{
foreach(var pattern in _patterns)
{
if(pattern.Any(item=>input.contains(item))
{
return pattern.GetIcon.Invoke(input);
}
}
}
}
The use of Func is to cover the scenario that you still have some slightly different logic between different cases. If that is not the case, you can use a simply IconName instead of GetIcon Func.
class MatchPattern
{
public string [] Patterns {get;set;}
public string IconName {get;set;}
}

You can work with a list for each case
foreach (var str in new string[] { "phone","mobile" }) {
if (expenseName.Contains(str)){
expenseIcon = "fas fa-mobile-alt";
}
}
It might also be worth to look into Lambda expressions, those have a shorter syntax in Java, but i am not sure how well this is supported in C#

Related

Getting rid of a big switch case

So I have been looking around and it seems like the correct answer to getting rid of big switch case is polymorphism, but I just can't figure out how I can change this from conditionnal to poplymorphic. Is this the right solution here?
Console.WriteLine(#"Menu");
Console.WriteLine(#"1.Create Account");
Console.WriteLine(#"2.ATM");
Console.WriteLine(#"3.Account info");
Console.Write(#"Please enter your selection: ");
var menuChoice = int.Parse(Console.ReadLine());
switch (menuChoice)
{
case 1:
atm.CreateAccount();
break;
case 2:
//Console.WriteLine(#"1.Deposit Or Withdraw");
Console.WriteLine(#"1.Deposit");
Console.WriteLine(#"2.Withdraw");
Console.Write(#"Please enter your selection: ");
var atmMenuChoice = int.Parse(Console.ReadLine());
switch (atmMenuChoice)
{
case 1:
atm.Deposit();
break;
case 2:
atm.Withdraw();
break;
default:
Console.WriteLine(#"Invalid selection!");
break;
}
break;
case 3:
atm.AccountInfo();
break;
default:
Console.WriteLine(#"Invalid selection!");
break;
}
}
In situations like this I tend to use a Dictionary<string, Action> to lookup what to do for each input.
Something like:
var actions = new Dictionary<string, Action>
{
{ "1", atm.CreateAccount }
{ "2", AtmSelection } //This would do the same as below with the atmActions dictionary
{ "3", atm.AccountInfo }
}
var atmActions = new Dictionary<string, Action>
{
{ "1", atm.Deposit }
{ "2", atm.Withdraw }
}
var input = GetInput(); //From stdin as you do currently
if (actions.TryGetValue(input, out var action))
{
action();
}
else
{
Console.WriteLine("Invalid Selection");
}
I personally find this easier to read than a massive nested switch statement
The preference for polymorphism over a switch usually applies when you're using some sort of serialization framework. Imagine that your int is the serialized representation of a member of a class of singletons, all of which have a particular method that operates on (or visits) your atm object. Then you could deserialize the instance and call that method:
var foo = deserializer.deserialize(intVal);
foo.doStuff(atm);
There's still a switch involved, but it's inside the serialization framework and you don't have to maintain it. If you want to implement a similar pattern without a serialization framework, you'll have to write the switch yourself. The benefit is that you can separate the switch from the rest of the logic:
Foo GetFoo(int type) {
// switch on type
}
var foo = GetFoo(intVal);
foo.doStuff(atm);
This pattern developed in languages that do not (or did not) have function pointers or the equivalent. In languages that do have function pointers, a map of int values to functions as suggested in another answer would essentially accomplish the same thing.
I may have gone a little crazy here, but this works in a similar way to Scott's answer.
static IEnumerable<MenuItem> RootMenu;
static void Main(string[] args)
{
RootMenu = BuildRootMenu();
MenuItem.DisplayMenu(RootMenu, new Atm());
}
/// <summary>
/// Creates the entire menu
/// </summary>
static IEnumerable<MenuItem> BuildRootMenu()
{
MenuItem item1 = new MenuItem() { DisplayText = "Create Account", AtmAction = (a) => a.CreateAccount() };
MenuItem item2_1 = new MenuItem() { DisplayText = "Deposit", AtmAction = (a) => a.Deposit() };
MenuItem item2_2 = new MenuItem() { DisplayText = "Withdraw", AtmAction = (a) => a.Withdraw() };
MenuItem item2 = new MenuItem() { DisplayText = "ATM", AtmAction = (a) => MenuItem.DisplayMenu(new List<MenuItem> { item2_1, item2_2 }, a) };
MenuItem item3 = new MenuItem() { DisplayText = "Account Info", AtmAction = (a) => a.CreateAccount() };
return new List<MenuItem> { item1, item2, item3 };
}
class MenuItem
{
public String DisplayText;
public Action<Atm> AtmAction = null;
public void Execute(Atm atm)
{
AtmAction(atm);
DisplayMenu(RootMenu, atm);
}
public static void DisplayMenu(IEnumerable<MenuItem> menuItems, Atm atm)
{
int i = 1;
foreach (var mi in menuItems)
{
Console.WriteLine(i + ": " + mi.DisplayText);
i++;
}
var rk = Console.ReadKey();
menuItems.ToArray()[int.Parse(rk.KeyChar.ToString()) - 1].Execute(atm);
}
}
class Atm
{
public void Deposit()
{
Console.WriteLine("Ran Deposit");
}
public void Withdraw()
{
Console.WriteLine("Ran Withdraw");
}
public void CreateAccount()
{
Console.WriteLine("Ran CreateAccount");
}
public void AccountInfo()
{
Console.WriteLine("Ran AccountInfo");
}

generics casting - it's not able to cast from GClass<string> to GClass<T>

I've a class:
public class GClass<T>
{
private T data;
//...
}
Then I've a function returning a GClass like:
public GClass<T> dialog<T>(String to)
{
GClass<T> result = null;
switch (to)
{
case "x":
result = (GClass<T>)this.dialogX(); <<<<<<<<<<<<
break;
case "y":
result = (GClass<T>)this.dialogY(); <<<<<<<<<<<<
break;
}
return result;
}
Then dialogX and dialogY look like:
private GClass<string> dialogX()
{
//...
}
Compiler is telling me that it's not able to convert a GClass<string> to GClass<T> in lines marked as <<<<<<<<<<<<<<<<<<<<<<
Any ideas?
EDIT
According to this I should be able to do that:
result = (GClass<T>)(GClass<object>)this.dialogX();
Hm, i don't know all code and your needs, but how about just use As operator
switch (to)
{
case "x":
result = this.dialogX() as GClass<T>;
break;
case "y":
result = this.dialogY() as GClass<T>;
break;
}

C# get/set solution

To give some background I'm trying to solve the Project Euler Problem 54 involving poker hands. Though there's infinite approaches to this. What I would like to do is enumerate through a list of strings, for example:
{ "8C", "TS", "KC", "9H", "4S" };
I would like to "get" an instance of class card with properties value, and suit, for each respective string. I've not yet utilized get/set so maybe there is an obvious approach to this I'm missing.
Ultimately I would like to have a list of objects type Card, I don't mind building all the card's ahead of time, such that "2H" returns an instance of type Card where suit = Hearts, and value = 2, for example.
I know this code is wrong, but it should give an idea of what I'm trying to do. Any suggestions would be appreciated.
class Card
{
public string suit;
public int value;
public string cardname
{
get
{
if (cardname == "2H") Card TwoH = new Card();
TwoH.suit = "Hearts"
TwoH.value = 2;
return TwoH;
}
}
}
Why not make a constructor that fills suit and value based on a string parameter
public Card(string name)
{
switch(name)
{
case "2H":
this.suit = "Hearts";
this.value = 2;
break;
//...
}
}
This might not be the exact solution you seem to be asking for but if the values you'll be getting (eg 2H, 3C etc) are all 2 characters long, then you can try this:
public class Card
{
public string suit { get; set; }
public int value { get; set; }
public static Card GetCard(string cardName)
{
string tmpSuit;
int tmpValue;
char[] cardNameParts = cardName.ToCharArray();
switch(charNameParts[0])
{
case "A":
tmpValue = 1;
break;
case "2":
tmpValue = 2;
break;
...
}
switch(charNameParts[1])
{
case "H":
tmpSuit= "Hearts";
break;
case "C":
tmpSuit= "Clubs";
break;
...
}
return new Card() { suit = tmpSuit, value = tmpValue };
}
}
I would do it like that:
public class Card
{
public string Suit { get; set; }
public int Value { get; set; }
public static Card FromString(string s)
{
if (s == "2H") return new Card() { Suit = "Hearts", Value = 2 };
else if (s == "....")
...
else return null;
}
}
I have converted your suit and value field into properties and instead of some getter method which in your case wouldn't work I have added a static method.
You can use it like this Card card2H = Card.FromString("2H");
Maybe use two switch statements, first
switch (cardname[0])
{
...
}
then
switch (cardname[1])
{
...
}
Before that, check that cardname.Length == 2. In each switch, have a default section where you throw an exception in case the char value doesn't make sense.

How can I avoid entering strings for known keys in C#

I have the following code:
switch(first)
{
case 'A':
vm.Content = contentService.Get("0001000", vm.RowKey);
return View("Article", vm);
case 'F':
vm.Content = contentService.Get("0002000", vm.RowKey);
return View("FavoritesList", vm);
}
'A' refers to a page type of Article with a key of "0001000"
'F' refers to a page type of Favorite with a key of "0002000"
Is there a way in C# that I could avoid having to code in the keys as a string?
Some way that would allow me to code in by the key abbreviation or name
and then have C# convert this to a string?
Can I use Enum for this? This seems ideal but I am not sure how to set up an Enum.
You may think about dictionary (if I right understood your question)
//class for holding relation between the code and page name
public class Data
{
public string Code {get;set;}
public string PageName {get;set;}
}
var dic = new Dictionary<string, Data >{
{"A", new Data{Code="0001000", PageName = "Article"},
{"F", newe Data{Code="0002000", PageName="FavoritesList"}
}
and after use it like:
Data dt = null;
if(dic.TryGetValue(first, out dt)) { // *first* is parameter you use in switch
vm.Content = contentService.Get(dt.Code, vm.RowKey);
return View(dt.PageName, vm);
}
You can use enums and use extension methods to allow an alternative text output.
The enum:
public enum PageTypes
{
A,
F
}
The extension method (needs to be in the top level of your project):
public static class Extensions
{
public static string getText(this PageTypes type)
{
switch (type)
{
case PageTypes.A:
return "0001000";
case PageTypes.F:
return "0002000";
default:
return null;
}
}
}
And your code:
PageTypes type;
//assing value to type:
//type = ...
var vm.Content = contentService.Get(type.getText(), vm.RowKey);
switch (type)
{
case PageTypes.A:
return View("Article", vm);
case PageTypes.F:
return View("FavoritesList", vm);
}
Now you do not need to use strings to retrieve the values.
I would put the keys in a dictionary, e.g.
var keyData = new Dictionary(char,string);
keyData.Add('A',"0001000");
keyData.Add('A',"0001000");
keyData.Add('F',"0002000");
You could then reference them using
var value = keyData['A'];

Best way to represent game card class in C#

I use class Card which contains 2 enumerated properties (suite - hearts diamonds spades and clubs) and card value from 2 to A. And overrides ToString() method to returns something like Ah Ad etc. All ok, but enum value can't starts with number, therefore my card value enumerated looks like x2, x3, x4 ... it is not beautiful.
Also need simple approach to parse few cards from single string.
Who know the best approach to design this class?
Couldn't you assign Jack, Queen, King, and Ace to be 11, 12, 13, and 14, respectively? It'd end up looking something like:
public class Card
{
public int Value { get; private set; }
public enum SuitType
{
Clubs, Spades, Hearts, Diamonds
}
public SuitType Suit { get; private set; }
public Card(int value, SuitType suit)
{
Suit = suit;
Value = value;
}
public Card(string input)
{
if (input == null || input.Length < 2 || input.Length > 2)
throw new ArgumentException();
switch (input[0])
{
case 'C': case 'c':
Suit = SuitType.Clubs;
break;
case 'S': case 's':
Suit = SuitType.Spades;
break;
case 'H': case 'h':
Suit = SuitType.Hearts;
break;
case 'D': case 'd':
Suit = SuitType.Diamonds;
break;
default:
throw new ArgumentException();
}
int uncheckedValue = (int)input[1];
if (uncheckedValue > 14 || uncheckedValue < 1)
throw new ArgumentException();
Value = uncheckedValue;
}
public string encode()
{
string encodedCard = "";
switch (Suit)
{
case SuitType.Clubs:
encodedCard += 'c';
break;
case SuitType.Spades:
encodedCard += 's';
break;
case SuitType.Hearts:
encodedCard += 'h';
break;
case SuitType.Diamonds:
encodedCard += 'd';
break;
}
encodedCard += (char) Value;
return encodedCard;
}
public override string ToString()
{
string output = "";
if (Value > 10)
{
switch (Value)
{
case 11:
output += "Jack";
break;
case 12:
output += "Queen";
break;
case 13:
output += "King";
break;
case 14:
output += "Ace";
break;
}
}
else
{
output += Value;
}
output += " of " + System.Enum.GetName(typeof(SuitType), Suit);
return output;
}
}
Edit:
I added some string functionality.
I took structure of Card(string input) from Jon Hanna's answer.
There's an obvious numeric value for the pip-cards, and we can add J=11, Q=12, K=13.
It may be more convenient to have A=14 than A=1 depending on the game being modelled (so one can more simply compute different relative values of hands).
Enums gives no real advantage, especially since enums allow out-of-range values unless you explicitly check for them (e.g. there is nothing to stop someone assigning (CardValue)54 to the card-value enumeration value).
ToString can be aided with an array of the values {null,"1","2","3","4","5","6","7","8","9","10","J","Q","K"}. Likewise {'♥','♦','♠','♣'} could give a nicer output.
Parsing always trickier than outputting a string, even if you are very strict in what you accept, as you have to deal with the potential for invalid input. A simple approach would be:
private Card(string input)
{
if(input == null)
throw new ArgumentNullException();
if(input.length < 2 || input.length > 3)
throw new ArgumentException();
switch(input[input.Length - 1])
{
case 'H': case 'h': case '♥':
_suit = Suit.Hearts;
break;
case 'D': case 'd': case '♦':
_suit = Suit.Diamonds;
break;
case 'S': case 's': case '♠':
_suit = Suit.Spades;
break;
case 'C': case 'c': case '♣':
_suit = Suit.Clubs;
break;
default:
throw new ArgumentException();
}
switch(input[0])
{
case "J": case "j":
_cardValue = 11;
break;
case "Q": case "q":
_cardValue = 12;
break;
case "K": case "k":
_cardValue = 13;
break;
case "A": case "a":
_cardValue = 1;
break;
default:
if(!int.TryParse(input.substring(0, input.Length - 1), out _cardValue) || _cardValue < 2 || _cardVaue > 10)
throw new ArgumentException;
break;
}
}
public static Card Parse(string cardString)
{
return new Card(cardString);
}
You might want to add a static method that read a larger string, yield returning cards as it parsed, to allow for easier encoding of several cards.
When I first started on the card.dll, I was using enumerations for suits and card rankings but then I didn't want to have to deal with that same issue and writing extra code to compensate for the strings, there for I wrote a abstract class Info with only two variables
(Flag (byte)) and (Name(string)) to be implemented by the Rank class and Suit class which would be members of the Card class. I have found this to work a lot better for naming conventions and filtering purposes. I love using enums but having to work around variable naming can be a hassle so sometimes it is best not to if you have to get the variable name as string.
So when the Card constructor get called the card ID is entered and then it passes into the Rank and Suit which will then separate what the ID means in code (101 = 100 (suit flag) +
1 (rank flag)). The protected abstract SetName(int cardID) and SetFlag(int cardID) while handle the rest from there in the info's constructor via Rank and Suit. No more issues with the enumeration and it can still be filtered by number via the Flag.
This card naming system uses a 1 through 4 * 100 (telling the suit flag) + 1 through 13 (for card rank). 500 + 14 through 16 are Little Joker, Big Joker, and Wild.
public class Card
{
short id;
public Card(string zFile)
{
this.id = Convert.ToInt16(zFile.Split('.')[0].Trim());
this.Rank = new Rank(id);
this.Suit = new Suit(id);
}
public override string ToString()
{
if (Suit.Flag == 5)
return Suit.Name;
return string.Concat(Rank.Name, " of ", Suit.Name);
}
public override int GetHashCode()
{
return id;
}
public Rank Rank { get; private set; }
public Suit Suit { get; private set; }
public static Card GetGreaterRank(Card value1, Card value2)
{
return (value1.Rank >= value2.Rank) ? value1 : value2;
}
public static bool CompareRank(Card value1, Card value2)
{
return (value1.Rank.Flag == value2.Rank.Flag);
}
public static bool CompareSuit(Card value1, Card value2)
{
return (value1.Suit.Flag == value2.Suit.Flag);
}
};
public abstract class Info
{
protected Info(short cardID)
{
Flag = SetFlag(cardID);
}
protected string SetName(short cardID, params string[] names)
{
for (int i = 0; i < names.Length; i++)
{
if (Flag == (i + 1))
return names[i];
}
return "Unknown";
}
protected abstract byte SetFlag(short cardID);
public static implicit operator byte(Info info)
{
return info.Flag;
}
public byte Flag { get; protected set; }
public string Name { get; protected set; }
};
public class Rank : Info
{
internal Rank(short cardID) : base(cardID)
{
string name = SetName(cardID, "A","2","3","4","5","6","7",
"8","9","10","J","Q","K","Little Joker","Big Joker","Wild");
Name = (name == "Unknown") ? string.Concat(name, " Rank") : name;
}
protected override byte SetFlag(short cardID)
{
return Convert.ToByte(cardID.ToString().Remove(0, 1));
}
};
public class Suit : Info
{
internal Suit(short cardID) : base(cardID)
{
string name = SetName(cardID,"Clubs","Diamonds","Hearts","Spades");
Name = (name == "Unknown") ? string.Concat(name, " Suit") ? name;
}
protected override byte SetFlag(short cardID)
{
return Convert.ToByte(cardID.ToString().Remove(1));
}
};
So now if you have your card image file named 101.png and pass it into the Card ctor it will pass to the Rank and Suit getting the info for you. Really all you are doing in giving the image file a code(numeric) for a name.
I would probably start out with 2 enums, 1 representing the Suits and 1 representing the Faces. Then declare a public property "Suit" and a public property "Face" based off of these enums. You will also probably need an array with the different unique values that a card can have (i.e. 1 throught 13).
You can start enums with number (although it is preferred to start at zero)
public enum Card
{
Two = 2,
Three,
Four,
...
}
Scratch what I wrote before, this is better.
using System;
enum Suit
{
Clubs,
Hearts,
Diamonds,
Spades
}
class Card
{
Suit Suit
{
get;
private set;
}
int Value
{
get;
private set;
}
Card(Suit suit, int value)
{
Suit = suit;
Value = value;
}
private const string[] valsToString = new string[] { "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A" };
bool IsValid()
{
return Value >= 2 && Value <= 14;
}
override string ToString()
{
return string.Format("{0} of {1}", valsToString[Value - 2], Suit);
}
}

Categories

Resources