Wednesday 27 January 2021

Stack Overflow post led to this

Several months ago, I read a question on our beloved StackOverflow. OP wanted functionality to search 
for a keyword in all properties of an object.
Generally, when you create a search feature in an application, the logic is to search in one/few known
set of properties. Like, if you search for Steve, the application will search fields like Name or Surname 
being Steve.
However, this question was interesting as it plain and simple wants to search in all properties.
To design the below code (in C#), I used the following assumptions:

  1. Datatype of the search item will decide the properties that will be searched
  2. Match is Exact match

What I mean here is that if user searches for string "Steve", code looks for it in all string fields of the object.
If user searches for integer 2, we try to find it in all integer fields in the object.

Enough Talk, here comes the code:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

class Program
   {
      static void Main()
      {
         var cust = new Customer
         {
            Id = 111,
            Name = "Martin",
            Age = 22,
            Address = "Somewhere on Earth",
            Contracts = new List<Contract>(),
            Tags = new List<string>() { "Hello", "World", "Martin" },
            Test = new JustForTest { TestId = 420, TestName = "Nothing Martin" }
         };
         cust.Contracts.Add(new Contract
         {
            Id = 1,
            From = DateTime.Now.AddMonths(-1),
            To = DateTime.Now.AddMonths(1),
            Comment = "Signed by Martin",
            CustomerId = 111,
            Customer = cust
         });
         cust.Contracts.Add(new Contract
         {
            Id = 2,
            From = DateTime.Now.AddMonths(-2),
            To = DateTime.Now.AddMonths(3),
            Comment = "Thank the best answer on StackOverflow Martin",
            CustomerId = 111,
            Customer = cust
         });
         HashSet<object> coll = new HashSet<object>();
         var result = Search<string, Customer>("Martin", cust, coll);
      }
      private static List<string> Search<T, O>(T keyword, O obj, HashSet<object> processedObjects)
      {
         var retVal = new List<string>();
         if (processedObjects.Contains(obj))
            return retVal;
         // Search Direct Properties
         var properties = obj.GetType().GetProperties().Where(p => p.PropertyType == typeof(T));
         foreach (var prop in properties)
         {
            var propValue = prop.GetValue(obj);
            if (propValue != null && propValue.ToString().Contains(keyword.ToString()))
            {
               retVal.Add($"{prop.DeclaringType?.FullName}.{prop.Name} - contains - {keyword}");
            }
         }
         // Search Complex Properties
         var complexProperties = obj.GetType().GetProperties().Where(p => p.PropertyType.IsClass && typeof(IEnumerable).IsAssignableFrom(p.PropertyType) == false);
         foreach (var prop in complexProperties)
         {
            retVal.AddRange(Search(keyword, prop.GetValue(obj), processedObjects));
            processedObjects.Add(obj);
         }

         // Search Collections
         var collectionProperties = obj.GetType().GetProperties().Where(p => (p.PropertyType.IsClass || p.PropertyType.IsAnsiClass) && p.PropertyType != typeof(string) && typeof(IEnumerable).IsAssignableFrom(p.PropertyType));
         foreach (var collProperty in collectionProperties)
         {
            if (collProperty.PropertyType.IsGenericType)
            {
               var paramType = collProperty.PropertyType.GetGenericArguments().FirstOrDefault();
               if (paramType != null && paramType.IsClass && paramType != typeof(string))
               {
                  var enumColl = collProperty.GetValue(obj) as IEnumerable;
                  foreach (var item in enumColl)
                  {
                     retVal.AddRange(Search(keyword, item, processedObjects));
                     processedObjects.Add(item);
                  }
               }
               else
               {
                  var enumColl = collProperty.GetValue(obj) as IEnumerable;
                  foreach (var item in enumColl)
                  {
                     if (item != null && item.ToString().Contains(keyword.ToString()))
                     {
                        retVal.Add($"{collProperty.DeclaringType?.FullName}.{collProperty.Name} - contains - {keyword}");
                     }
                  }
               }
            }
         }
         return retVal;
      }
   }


   public class Customer
   {
      public int Id { get; set; }
      public string Name { get; set; }
      public int Age { get; set; }
      public string Address { get; set; }
      public JustForTest Test { get; set; }
      public List<string> Tags { get; set; }
      public ICollection<Contract> Contracts { get; set; }
   }

   public class Contract
   {
      public int Id { get; set; }
      public DateTime From { get; set; }
      public DateTime To { get; set; }
      public string Comment { get; set; }
      public int CustomerId { get; set; }
      public Customer Customer { get; set; }
   }

   public class JustForTest
   {
      public int TestId { get; set; }
      public string TestName { get; set; }
   }


This function can be used as Generic Search over class members.

Friday 28 June 2019

Cutting the cord... In Singapore


Hello Life

So I moved to Singapore in search of better life about an year ago.
It has been busy and I have seen my own little ups and downs here.

So this happened when I decided to not buy the shitty cable TV subscription and rather run on Netflix, Amazon Prime and YouTube.
Now the TV I had in the furnish apartment was a Samsung E series. Believe me it's a pathetic TV and shouldn't be called Smart TV at all. 
Yes it had all the 3 apps that I needed, but moving around is slow and at times they just crash.

So I decided to get a better alternative. I looked at ChromeCasts and Xiaomi Min boxes first. Somehow the Android experience on these weren't appealing either. At one instance the Xiaomi box crashed and rebooted too.
So I turned towards the ever famous "Roku".
The packaging and the box design were so inspiring bthat I didn't read much about it. Actually didn't find much about the issues I will face later also.
So, I ordered and got the pretty little box. Awesome remote coz of it's size and fit n finish. Voice control and private listen mode were the 2 features that had me vested in it.

I try to set it up and YouTube worked - wow.
Tried Netflix and it worked too. Another wow.
Searched for Amazon Prime Video. And No App found.
Re tried a few times. Restarted the box. Somehow I found the app but it won't install. Saying region related errors.

Disheartened, I tried the Voice control feature, again same Region related error.

Felt disgusted. Searched on the web for any help/clues. But found nothing. So vent  my anger in a Reddit post.

Also, spike to the seller that he didn't tell me that Rokus don't work outside USA and other handful supported regions. He said he had no clue. But offered me a months use of his VPN account so I can be happy.
I actually have no use for the VPN coz my router doesn't have VPN setup feature. So I can only use it from one mobile phone. And the speed was also Yucky.

I accepted it to be my sad fate. For a week.
However, somewhere deep inside I wanted to get this working. So I tried something that nobody has posted on any blog or websites. Or if they have, I didn't find it. 

So here I am posting the minor trick that worked for me.

First few learnings about Roku:
  • It's a capable hardware.
  • You need a Roku account to use this box. 
  • You can create the account from Roku website.
  • However, during sign-up they ask for Credit Card details, claiming that it won't be charged unless you purchase things.
  • Roku and many of it's features work only in USA, Canada and few Euro regions
Now with above learning here's how I approached and solved my problem:

  • Use VPN and take any USA server. 
  • Go-to Roku website and sign-up. Mostly it asks for name, email and basic stuff
  • Don't want to give credit card details - During sign up, first page asks basic details then on clicking button the next page asks for card details. You can stop here. Just close the browser tab. Coz Roku account is created already from first page. This second page thing is more like updating information about account.
  • So far you have created a USA Roku account and not used credit card.
Next you switch on and set up Roku box using this account. You don't need VPN or proxies now. Box assumes that you are in USA coz of account. Voice feature and private listening works. Even prime video app can be installed and used now.

For getting the mobile Roku remote you can download an unofficial app.

Hope this helps 


Saturday 13 September 2014

Car PC for Dummies

Back in January 2014, one of the most influential person in my life "Daya", my manager at Barclays and an amazing techie, casually discussed the idea of installing a computer in cars (mind you, he is very much into cars). The idea struck me so much, that here I am writing a post at an early hour in the morning to help out those who get inspired to do the same now. The drive to write this came from a successful power test last night.

Important: this has worked for me, so I see this as a lay-mans guide to setup a CarPC. Your needs may be much more complex then mine, so you may have to serach the web for clearer instructions

The Idea

As I am a windows oriented engineer, so my first & only thought was to have Windows 7 or above based system. At this time I am inclined towards writing my own interface on WPF, so the choice of a Win based system made more sense to me.

Features:
Although, vision was to have awe striking visuals and features in my car, that it beats the likes of Bond cars. However, hardware unavailability is a big reason to stick with basic stuff as of now. Note that this will be an ongoing upgrade
  1. Capacitive Multi touch display 10.1 inch
  2. All round view camera system
  3. GPS navigation
  4. Audio/Video Player
  5. Web Browsing
  6. Phone call (GSM) and messaging
  7. OBD diagnostics
  8. DVR with drivers pov

Hardware

Aliexpress came in handy for ordering all the cool stuff. Although shipment takes a lot of time (in weeks to months) to reach, big reason that the CarPC is not complete yet. Anyways, here is the actual hardware list with prices:

  • Feelworld 10.1" capacitive monitor (INR 13k + INR 3k Fedex Customs duty)
    • LCD based monitor - not so great display, L/R/U viewing angles are fine. Down angle is poor, but who cares.
    • Pros: HDMI input, plug n play - no drivers needed
    • Cons: proprietary SKS cable - availability is unknown
  • Gigabyte H97N-Wifi motherboard (INR 9.5k - got from local store)
    • H97 chipset, mini-ITX form factor, LGA1150 socket
    • Pros: 3 displays supported (2 HDMI + 1 DVI), Wifi + BT 4.0 micro PCI card on board, more USB 3.0 exposed than 2.0
    • Cons: Cost (I would have loved to get it for free)
  • Intel Core i3-4150 processor (INR 7.5k - local store)
    • 4th Gen, Dual Core - hyperthreaded
    • Pros: okayish TDP - 54Watts, HD Graphics 4400 (could not get 4600 in this price), 3.5 GHz, comes with heat sink
    • Cons: wish - intel had quad core cpu with HD 4600 graphics with lower price tag
  • Corsair Vengeance 4GB (INR 3.5k)
    • 1600 Mhz, with heat sink
    • Pros: using these for over an year, no complaints
    • Cons: make them cheap
  • Intel 520 series SSD 120GB (INR 7k)
    • This came as lack of option with seller, I wanted Adata or Crucial.
    • Pros: Anandtech review says fastest sandforce drive
    • Cons: scared about BSOD
  • M2-ATX PSU (INR 4k + INR 1k Fedex customs duty)
    • 160w capacity, about 95% efficiency
    • Pros: DC-DC supply, automotive grade, wide input range
    • Cons: chinese instruction manual is no help
  • ELM 327 OBD-II mini scanner (INR 350)
    • car diagnostic data reader
    • Pros: compact size, Bluetooth operation (wire-free)
    • Cons: have not tested it so much to find one
  • DC DC buck boost converter (INR 800)
    • Will be used to provide consistent volt/current to cameras
    • Pros: compact design, clear instructions, 7 segment display on IC for voltage
    • Cons: max 35 w (3A) output limits application

Future Add-ons

  • CCD sensor based camera with IR LED night vision (front + back)
  • Regular/4 IR led ccd camera for sides
  • HD WebCam (usb) for DVR/Skype from Car
  • 3.5mm jack Microphone - Voice controls/Calling
  • Huawei/Micromax GSM+3G usb adapter with Voice calling
  • USB GPS receiver - globalSat BU354SB
  • USB temperature sensors
  • Satellite TV receivers
  • bigger displays for back seats

Getting Started

Wiring M2-ATX
Believe me, this is the most undocumented part of the process. Though carmp3 forum has a pdf which helps to some extent. But it took me several hours of experiemental trials to get the mobo powered up. Please refer to the diagram:
Yes, dont be amused to see 2 battery positive (red wires) going to 1st & 2nd pins. The 2nd (middle) pin represents the car ignition. You will first have to connect, 1st Pin and 3rd Pin to +ve and -ve terminals respectively. Then connect the middle pin to the battery positive and it will simulate ignition, turning on the motherboard (off-course you will use both 20 Pin ATX and 4 pin ATX to connect the mobo).

I will post actual photos once I am done with my setup. Going on a hunt for a plexiglass/acrylic cabinet.

[Update]

Just finished wiring up the CarPC at home. Biggest challenge now is to decide a place for the cabinet and how to mount the Display. Wait, err did I say cabinet? Arrrgghh... I gave up my secret. Yeah finished making (destroying) one too. I actually wanted a transparent cabinet (search for acrylic cabinets and you will know what I mean). However, wasted a day last week and about INR 800 (US $15) on getting an acrylic one made from a local fabricator.

Lessons learnt:
1. Local fabricator sucked real bad
2. Plexiglass/Acrylic is not a good choice for cabinet (if made) due to sheet thickness (cant use screws)
3. Only if the fabricator has laser cutting precision then the chances are to get a neat finished product

I ended up having a cabinet which looked like a Shoe Box and was broken (sheet tears) at several places:

Iron L-bars were used for rigidity to the structure (ugly)
Sythetic/rubberised glue was used to paste the sides together (ugly white spunk)
Hole at the top face (for a 120mm fan) cause a crack in the sheet. (ugly)

Cabinet

Fortunately, found a perfectly sized plastic container box (kitchen/stationery) which is now my car PC case. I bought 2 boxes just because something in me knew I would damage the first draft.
And yes I did that. I had to heat cut (actually melt my way around) the box to make it usable.

I used wire tags to tie up the components. See image below.

Yeah, used the stickers that came with SSD and Processor to beautify the box. I know the slots are not neatly cut but anyways this is going to hide either under the seat or in the floor well.

This is how the system will look (if used as desktop). Remember, screen is a 10.1" one and so is the cabinet.

I used the buck boost converter to stabilize the voltage input to the monitor, tested it to work with the battery, BB circuit set to output 12V. Its an amazing piece of circuitory as the voltage levels vary between 11.9V - 12.1V only. Kind of steady.

I then took some time to connect the entire thing, powered by the battery.It worked.
Will upload the first boot video on youtube and link here.

Whats on mind?

1. Must find a way to wire up Front panel Audio (3.5mm Headphone jack and mic jack)
2. Need to find a talented mechanic to wire up this to my Car
3. Top of them all, need to finalize where to mount the display and where will the cabinet go. (Boot is not a good option as wire lengths will increase. Under seat is causing a little discomfort to rear seat passengers)
4. Minimal cut/screw/bolting the Dashboard and car for installing this

Reverse Parking Camera is still missing. Damn!! you manufacturers. Why you no make good camera's.

Sunday 14 October 2012

Building a Rig

Hey Fellas

Its been a while that I updated any stuff on my blog. Actually I was having troubles with my laptop that used to die but I could not accept. Every time it will go off, I would get a CPR (repair/refurbish) and it will run again for few months.
To start with my long story, I actually own(ed) a HP DV9734tx laptop. This was in February - 2008. 
Some quick specifications:
  1. 17" (16:10 Aspect Ratio) 1440 x 900 WXGA
  2. Intel Core 2 Duo T8100 (2.1 GHz, 3M Cache)
  3. Intel 965OM Chipset
  4. 3 GB DDR2 RAM 667Mhz
  5. 500 GB HDD (250X2 SATA) 5400 RPM
  6. Nvidia GeForce 8600M GS 512MB
  7. LightScribe DVD R/W
  8. Wireless, Ethernet, Bluetooth
  9. Remote Control, Finger Print
  10. Altec Lansing Audio
To be honest, I was a novice with specs at that time (Just went for a Large Screen, Processor and GPU).
Though the stuff I chose was the best in the market (and costly too). I was very happy with the machine I bought and I would spend hours and hours of my life on it (mostly playing online games with my buddies).
Then came October - 2010 when my laptop crashed for the first time.

Symptoms:
I just switched it on, the windows Vista (Home Prem.) login screen came up and I started seeing colored lines on display. These lines grew in count (the ones that you see if the RAM is faulty). So I rebooted the laptop, and it never came up.
Some lessons learnt:
  • Assume a laptops life is 3 Years.
  • Never use a laptop on Bed/Pillow where the Heat sink cannot exhaust hot air.
  • Read a lot of reviews before buying an electronic product.
To shorten the story, I got it repaired for the first time, the shopkeeper told me that the mobo has to be replaced, so I spent about 1/6 of the actual cost of laptop.
The laptop worked for 2-4 Months (shop covered the repair warranty for 1 Month).
Then when it dies 2nd time the other guy told me it was GPU issue and it cost me about 5K.

Now I started researching and found about faulty product line (HP DV9000 series) and pending law suit.
The problem was in the Mobo design. The GPU and CPU sit next to each other with practically no
heat isolation. And to top that, they share same heat sink. So my heated CPU was frying up the GPU.
Anyways, it was too late to understand this, so I got it repaired a 3rd time before losing faith in it,
This time I used a Belkin Heat Sink dock. But the GPU was destined to failed.

In 2012 when it failed for the last time, I gave up the affection I had. Having spent over 90K on this piece of ever-dying hardware killed me. Thats when I decided to get a desktop. Instead I decided to build myself one so that I can rest assured that all components that I chose aren't faulty.

I also learnt that its better to chose wisely, as I do not want to run into another sad investment.
Here is what my configuration looks like:
  • Intel 3rd Gen Core i5 3550
  • AsRock z77 Extreme4
  • Corsair Vengeance 1600 - 4GBX2
  • Seagate Barracuda ST2000DM001 - 2TB 7200.14 RPM 6Gb/s SATA 3.0
  • Cooler Master Thunder 500W
  • Cooler Master Elite370 Cabinet
  • ASUS E-Green DVD R/W
  • 2 X Cooler Master 1200mm Fans
  • Samsung S20B300 20" LED (16:9)
  • Altec Lansing 2.1
  • APC back UPS 1100V (650W)
  • 250 GB X 2 SATA 5400RPM (from my laptop)
All the stuff cost me around 55K (+ expenses of travelling).
I did not choose the K series processors as I know I am not going to Over Clock (and kill my PC).
I chose an AsRock mobo (over ASUS) just to have latest features in an affordable price.
I got a UPS back up in this era because the country I live in still has power outages.

Overall, it was a great period of about 1 Month over which I got all the components and built (with seagate HDD failing, and replacement taking about 2 Weeks) the machine. Feels great to work on a PC I have built down to each screw and bolt.

Friday 3 August 2012

Python - Last Minute Code

Two weeks back I attended a much awaited training to learn python. The syntax was (funny) new and needed attention to (whitespaces) details. After the training was over (phew...) we had to submit a script code!!!
This was the time to put my learning (graspings) to test.
Posting below the code that I wrote (Incomplete though) just to keep an archive of how python works:


class Stock:
def __init__(self, stockTicker, stockName, purSize, purPrice):
self.Ticker=stockTicker
self.Name=stockName
self.PurchaseSize=purSize
self.PurchasePrice=purPrice

def __str__(self):
return '%s,%s,%d,%f' % (self.Ticker,self.Name,self.PurchaseSize,self.PurchasePrice)


def main():
choice='x'
while choice.upper()!='Q':
createMenu();
choice = raw_input('\n\t:\>')


if choice.upper()=='A':
addStocks();
if choice.upper()=='L':
loadFile();
if choice.upper()=='D':
deleteStock();
#--------------- End of Main Method ---------------------#


def addStocks():
tckr = raw_input('Ticker: ')
name = raw_input('Name: ')
size = input('No. of Shares: ')
price = input('Purchase Price: ')
stk = Stock(tckr,name,size,price)
saveStockInFile(stk);
#--------------- End of addStocks Method ---------------------#


def deleteStock():
stkTicker = raw_input('Ticker: ')
filePtr = open('MyPortfolio.txt', 'r');
del LoadedStocks[:]
while True:
try:
LoadedStocks.append(pickle.load(filePtr));
except EOFError:
break;



for stk in LoadedStocks:
if stk.Ticker == stkTicker:
LoadedStocks.remove(stk);
break;

filePtr.close();
filePtr = open('MyPortfolio.txt', 'w');
filePtr.write('');
filePtr.close();


for stk in LoadedStocks:
saveStockInFile(stk)
#--------------- End of deleteStock Method ---------------------#


def loadFile():
filePtr = open('MyPortfolio.txt', 'r');
del LoadedStocks[:]
while True:
try:
LoadedStocks.append(pickle.load(filePtr));
except EOFError:
break;


filePtr.close();


for stk in LoadedStocks:
print str(stk)
#--------------- End of loadFile Method ---------------------#


def createMenu():
print '\n\t\tWelcome to Portfolio Manager (ver 1.0)'
print '\t\t--------------------------------------'
print '\n - What would you like to do Today?'
print '\n\t(A)dd Stocks\n\t(D)elete Stocks\n\t(L)oad File\n\t(U)pdate Prices\n\t(R)eport\n\t(Q)uit:'


def saveStockInFile(stk):
filePtr = open('MyPortfolio.txt', 'a');
#line = 'Ticker=%s\nName=%s\nShares=%d\nPurchase Price=%f'%(stk.Ticker, stk.Name, stk.PurchaseSize, stk.PurchasePrice)
#filePtr.write(line)
pickle.dump(stk, filePtr)
filePtr.flush();
filePtr.close();


import pickle;
LoadedStocks = []
main();

Monday 11 June 2012

Cracking the Code

Expression Evaluation Problem

I came across this problem (I am unwilling to share the source, however this might be helpful in a wrong way to some fellows). So, the crux is, design/suggest improvements over a given code for a given scenario.

C# Coding/Design Task
A junior member of your team has been asked to create a simple calculation module
(and related tests) that can process arithmetic commands with the input specification:
Grammar:
cmd::= expression* signed_decimal
expresion::= signed_decimal ' '* operator ' '* operator::= '+' | '-' | '*'
signed_decimal::= '-'? decimal_number 
decimal_number::= digits | digits ‘.’ 
digits digits::= '0' | non_zero_digit digits* 
non_zero_digit::= '1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'

He has produced the following Code. What changes will you suggest? Please consider the quality of the solution (including the design), and provide an updated version of the code, applying your suggestions.

Given Version of Code:


Calculator.cs
using System;

namespace Calculator
{
///<summary>
///Basic calc engine for handling +, -, * operations.
///</summary>
public class CalcEngine
{
///<summary>
///Process a calculation command string
///</summary>
///<param name="p">cmd</param>
///<returns></returns>
public string Process(string p)

{

String[] stuff = p.Split(' ');

double result = 0;

bool nextOpAdd = false;

bool nextOpSubtract = false;

bool nextOpMultiply = false;

foreach (string x in stuff)

{

try

{

double nextVal = Double.Parse(x);

if (nextOpAdd)

result += nextVal;

else if (nextOpSubtract)

result -= nextVal;

else if (nextOpMultiply)

result *= nextVal;

else

result = nextVal;
}
catch

{

nextOpAdd = false;

nextOpSubtract = false; nextOpMultiply = false;if (x == "+")
nextOpAdd = true;else if (x == "-")
nextOpSubtract = true;else if (x == "*")
nextOpMultiply = true;
}
}
return result.ToString();
}
}
}
//-------------------- This is the NUnit Test Class ------------------//
using Calculator;
using NUnit.Framework;
namespace TestCalculator
{
[TestFixture]
public class CalcEngineTest
{
[Test]
public void Test()
{
CalcEngine engine = new CalcEngine(); System.Console.WriteLine(engine.Process("1 + 2"));
}
}
}

Solution Design:
Given Grammar:
cmd::= expression* signed_decimal
expresion::= signed_decimal ' '* operator ' '* operator::= '+' | '-' | '*'
signed_decimal::= '-'? decimal_number decimal_number::= digits | digits ‘.’ digits digits::= '0' | non_zero_digit digits* non_zero_digit::= '1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'
Assumption: Signed Decimal and Operator may not be separated by spaces. i.e. 5+2-3 * 6+ 2 is a valid scenario and it should evaluate to 26.
Based on my understanding of the Given Predicate/Grammar & from the given code
I have the following observations:

  • Developer code is based on a strict rule that operands and operators will be separated by space
  • (that’s why use of .Split(‘ ‘) ). This may not be true and should be able to handle variety of inputs.
  • All the code lies within one method (Process()), which may grow unmaintainable.
  • One Boolean variable for each operation has been used, Design is not flexible/adaptable for addition of more operators (like divide, square root etc.)
  • Code has operator detection logic handled in Catch block. Actually, catch block shouldn’t be used for input verification purpose.
  • Program State is not being maintained, as all code is within single method.
  • Generation of test suites/cases for such code is little tricky.
  • No naming conventions were followed.
  • Use logical and contextual names for local variables
  • Use variable/object pooling as and when possible instead of creating local variables.
Suggestions:

  • Modularize code into logical units (classes) [as shown in code below]
  • Return proper/informative error codes/messages as and when needed.
  • Maintain state of the application using classes/objects
  • Though I too have used ForEach loop, but For performs better, so this program can be tuned more for performance
  • Modularized code is better maintainable, readable and extendable
  • Usage of Switch construct instead of if else if makes code flexible for future modifications
  • CCommand class manages the user input and has a property that will store computed Result.
  • CBetterCalcEngine class manages state of the Calculation Engine/Application
  • TryParse has been used instead of Parse.




namespace BetterCalculator
{
       class Program
       {
              static void Main(string[] args)
              {
                     CCommand cmdObj = new CCommand("1 + 2 + 3+4+5");
                     CBetterCalcEngine myEngine = new CBetterCalcEngine(cmdObj);
                     EErrorCodes eCode = myEngine.Compute();
              }
       }
       // Instead of a bool for each operator, using delegate
       public delegate double ExpressionOperators(double op1, double op2);
       public enum EErrorCodes
       {
              NoError,
              InvalidCommandString,
              InvalidOperands,
              InvalidOperation,
              DivideByZero,
       }
       public class CBetterCalcEngine
       {
              //Using Customized logic of Expression Evaluation
              //Traverse the user input expression from Left to Right
              //If you encounter Operand (Multi-Digit), push into Stack
              //If you encounter Operator, Enqueue into a Queue
              private CCommand InputCommand;
              private Stack<double> OperandStack;
              private Queue<char> OperatorQueue;
              public CBetterCalcEngine(CCommand userCommand)
              {
                     InputCommand = userCommand;
                     OperandStack = new Stack<double>();
                     OperatorQueue = new Queue<char>();
              }
              public EErrorCodes Compute()
              {
                     double nextOperand = 0, result = 0, op1, op2; OperandStack.Clear();
                     OperatorQueue.Clear();
                     string operand = string.Empty;
                     foreach (char opChar in InputCommand.Command)
                     {
                           if (opChar == ' ')
                           {
                                  continue;
                           }
                           if (InputCommand.IsValidOperator(opChar) == false)
                           {
                                  operand += opChar;
                           }
                           else
                           {
                                  if (Double.TryParse(operand, out nextOperand) == false)
                                  {
                                         return EErrorCodes.InvalidOperands;
                                  }
                                  OperandStack.Push(nextOperand);
                                  OperatorQueue.Enqueue(opChar); operand = string.Empty;
                                  if (OperandStack.Count == 2)
                                  {
                                         op1 = OperandStack.Pop(); op2 = OperandStack.Pop();
                                         ExpressionOperators operation = InputCommand.GetOperation(OperatorQueue.Dequeue());
                                         if (operation != null)
                                         {
                                                result = operation(op2, op1); OperandStack.Push(result);
                                         }
                                         else
                                         {
                                                return EErrorCodes.InvalidOperation;
                                         }
                                  }
                           }
                     }
                     if (OperandStack.Count == 1 && OperatorQueue.Count > 0)
                     {
                           if (Double.TryParse(operand, out nextOperand) == false)
                           {
                                  return EErrorCodes.InvalidOperands;
                           }
                           OperandStack.Push(nextOperand); operand = string.Empty;
                           if (OperandStack.Count == 2)
                           {
                                  op1 = OperandStack.Pop(); op2 = OperandStack.Pop();
                                  ExpressionOperators operation = InputCommand.GetOperation(OperatorQueue.Dequeue());
                                  if (operation != null)
                                  {
                                         result = operation(op2, op1); OperandStack.Push(result);
                                  }
                                  else
                                  {
                                         return EErrorCodes.InvalidOperation;
                                  }
                           }
                     }
                     else
                     {
                           return EErrorCodes.InvalidOperands;
                     }
                     InputCommand.Result = OperandStack.Pop().ToString(); return EErrorCodes.NoError;
              }
       }
       public class CCommand
       {
              //Stores user Input Command/Expression
              public string Command { get; set; }
              //Computed Result of the Command/Expression
              public string Result { get; set; }
              //For future extension. Add new operator to this Variable
              public string ValidOperators = "+-*";
              public CCommand(string commandString)
              {
                     Command = commandString; Result = string.Empty;
              }
              public bool IsValidOperator(char opChar)
              {
                     return ValidOperators.Contains(opChar);
              }
              public ExpressionOperators GetOperation(char currentOpertor)
              {
                     // For future extension. Add Case for the new operator here
                     switch (currentOpertor.ToString())
                     {
                           case "+":
                                  return AddOperation;
                           case "-":
                                  return SubstractOperation;
                           case "*":
                                  return MultiplyOperation;
                           default:
                                  return null;
                     }
              }
              private double AddOperation(double op1, double op2)
              {
                     return op1 + op2;
              }
              private double SubstractOperation(double op1, double op2)
              {
                     return op1 - op2;
              }
              private double MultiplyOperation(double op1, double op2)
              {
                     return op1 * op2;
              }
              // For future extension. Add Handler Method for the new operator here
       }
}
·This code can still be tuned/refined for performance
·Try Catch block exception handling can be employed.