Using enum item to call a method - c#

I have an enum with 30 items in it. Each item has a corresponding function with the same name. I would like to be able to call the function by referencing the enum at a certain position.
So if the value at enum[0] = Foo, I would like to be able to call Foo(string bar) by using something like enum(0)("foobar")
In the end the point is I am running each function as a task like so:
enum Test { AA, BB, CC, DD ....}
tasks[0] = Task.Run(() => { prices[0] = AA("a string"); });
tasks[1] = Task.Run(() => { prices[1] = BB("a string"); });
tasks[2] = Task.Run(() => { prices[2] = CC("a string"); });
//for 30 tasks
What I would like to do is something along the lines of:
enum Test { AA, BB, CC, DD ....}
for (int i = 0; i < 30; i++)
{
tasks[i] = Task.Run(() => { prices[i] = (Test)i("a string"); });
}
Task.WaitAll(tasks.ToArray());
Is this something that is even possible?
EDIT:
The enum relates to controls on a form so i have an array of textboxs, label and a array of prices that is populated with the results of the functions:
enum Dealers { Dealer1, Dealer2 ... Dealer29, Dealer30 };
static int noOfDealers = Enum.GetNames(typeof(Dealers)).Length;
decimal[] prices = new decimal[noOfDealers];
TextBox[] textBox = new TextBox[noOfDealers];
Label[] boxes = new Label[noOfDealers];
for (int i = 0; i < noOfDealers; i++)
{
textBox[i] = Controls.Find("txt" + (Dealers)i, true)[0] as TextBox;
boxes[i] = Controls.Find("box" + (Dealers)i, true)[0] as Label;
prices[i] = 0;
}
//RUN 30 TASKS TO POPULATE THE PRICES ARRAY
for (int i = 0; i < noOfDealers; i++)
{
textBox[i].Text = "£" + prices[i].ToString();
}
//LOOP THROUGH PRICES ARRAY AND FIND CHEAPEST PRICE, THEN COLOUR THE LABEL BACKGROUND GREEN FOR THE TEXT BOX WITH THE NAME AT ENUM VALUE WHATEVER I IS
I guess i am just trying to make my code as concise as possible, there is the potential for the amount of tasks to double and didn't want to end up with 60 lines to populate the tasks array

I would create dictionary and map enum to actions:
Dictionary<Test, Func<string,double>> actions = new Dictionary<Test, Func<string,double>>()
{
{Test.AA, (x) => { return 5;}},
{Test.BB, (x) => { return 15; }},
}; //x is your string
var res = actions[Test.AA]("hello");

I would strongly suggest using a built in construct - like an extension method and a simple switch:
public static int GetPriceWithString(this Test test, string str)
{
switch (test)
{
case Test.AA:
break;
case Test.BB:
break;
case Test.CC:
break;
case Test.DD:
break;
default:
throw new ArgumentOutOfRangeException(nameof(test), test, null);
}
}
then your loop looks almost the same:
for (int i = 0; i < 30; i++)
{
tasks[i] = Task.Run(() =>
{
prices[i] = ((Test)i).GetPriceWithString("a string");
});
}
What you want to do is possible with reflection, which can be a powerful tool - but ideally should only be used as a last resort, as it will hide what could be compile time errors, and cause less code readability.
Using a simple switch like this makes your code self-documented, so when you come back to this in a month you can quickly remember what the intention was.

How about using an array of delegates:
using System;
using System.Threading.Tasks;
namespace ConsoleApplication
{
class Program
{
private static int AA(string a) { return 0; }
private static int BB(string a) { return 1; }
private static int CC(string a) { return 2; }
private static Func<string, int>[] functions = new Func<string, int>[] { AA, BB, CC };
private static int[] prices = new int[functions.Length];
private static Task[] tasks = new Task[functions.Length];
static void Main(string[] args)
{
for (int i = 0; i < functions.Length; ++i)
tasks[i] = Task.Run(() => { prices[i] = functions[i]("a string"); });
Task.WaitAll(tasks);
}
}
}

An eg. speaks a lot more than words.
I used it in a winform so the this refers to win form.
I have assumed all your methods are public , have same signature & return the same type.
enum MyName { AA,BB,CC};
//Call this in one of your methods
string [] strVal= Enum.GetNames(typeof(MyName));
int x = CallFunction(strVal[0], "A");
int y = CallFunction(strVal[1], "h");
int z = CallFunction(strVal[1], "C");
//End Call this in one of your methods
int CallFunction(string strName,string strValue)
{
return Convert.ToInt32(this.GetType().InvokeMember(strName, BindingFlags.Public | BindingFlags.InvokeMethod|BindingFlags.Instance, null, this, new object[] { strValue }));
}
public int AA(string s)
{
return 1;
}
public int BB(string s)
{
return 2;
}
public int CC(string s)
{
return 3;
}

Another solution. I hope somebody will consider it as overkill :)
Create abstract class DealerBase.
public abstract class DealerBase
{
public string Name { get; }
public decimal Price { get; set; }
protected DealerBase(string name)
{
Name = name;
}
public abstract void UpdatePrice();
}
Then create classes for every dealers you have and implement own logic for UpdatePrice method.
public class Dealer1 : DealerBase
{
public Dealer1() : base("DealerOne") { }
public override void UpdatePrice()
{
//Calculate price
Price = DealerOneCalculationMethod();
}
}
public class Dealer2 : DealerBase
{
public Dealer2() : base("DealerTwo") { }
public override void UpdatePrice()
{
//Calculate price
Price = DealerTwoCalculationMethod();
}
}
And so on..
Then you just create collection of dealers which can be easily iterated
var dealers = new List<DealerBase>
{
new Dealer1(),
new Dealer2()
}
foreach(var dealer in dealers)
{
dealer.UpdatePrice();
}
You can loop dealers and generate textboxes, labels in the winforms.
But I suggest to use DataGridView where code will be tiny clearer.
First implement INotifyPropertyChanged interface in the base class DealerBase
public abstract class DealerBase : INotifyPropertyChanged
{
public string Name { get; }
protected decimal _Price;
public decimal Price
{
get { return _Price; }
set
{
if (Equals(_Price, value)) return;
_Price = value;
// next method will inform DataGridView about changes
// and update value there too
RaisePropertyChanged();
}
protected DealerBase(string name)
{
Name = name;
}
public abstract void UpdatePrice();
// Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
The in the Form you can create BindingList<DealerViewModelBase> and set it to DataGridView.DataSource
public class YourForm: Form
{
public YourForm()
{
InitializeComponent();
var dealers = new List<DealerBase>
{
new Dealer1(),
new Dealer2()
};
var bindSource = new BindingList<DealerBase>(dealers);
dataGridView.DataSource = bindSource;
}
// Add button which update prices for all dealers
private void ButtonUpdatePrices_Click(object sender, EventArgs e)
{
var dealers = (BindingList<DealerBase>)dataGridView.DataSource;
foreach (var dealer in dealers)
{
dealer.UpdatePrice();
// Because we call `RaisePropertyChanged` in
// setter of Price - prices will be automatically
// updated in DataGridView
}
}
}
Idea of this approach you put different logic of different dealers in the separated class which. Because all dealer classes will inherit from same abstract class you can add different dealers to the collection.
You already have hardcoded enums and correspondent method which you try to link together. This approach make using of dealers collection little bid easy

Related

How would I find the value of an unspecified variable in other objects of the same class? C#

I want this method to work with any variable - i.e., passing a "Price" value to the method then getting the total price of all items.
private int GetTotalValue(int stat){
int total = 0;
foreach(Item i in Vendor.items){
totalStat += i.stat;
}
return total;
}
However, it has no way of knowing the name of the variable that I passed as the parameter, and thus no way of accessing it in other objects.
How would I tell the method what variable I'm passing, instead of just the value of it?
If you always want the sum of some property value you could encapsulate that logic into a method, e.g. GetVendorItemSum:
internal class Program
{
private static void Main(string[] args)
{
var items = new[] {
new Item {Price = 1},
new Item {Price = 2}
};
var vendor = new Vendor {Items = items};
var vendorSum = GetVendorItemsSum(vendor, x => x.Price);
}
private static int GetVendorItemsSum(Vendor vendor, Func<Item, int> func)
{
return vendor.Items.Sum(func);
}
}
public class Vendor
{
public IEnumerable<Item> Items;
}
public class Item
{
public int Price { get; set; }
}

return results from a third method to another method

I have following problem:
In method 1 I get a string list and pass it to method
In method 2 I generate a 2d array based on the list and pass the list further to method 3
In method 3 I convert the 2d array to a data table
Now I want to return the data table back to method 1, but I don't know how to do that. The picture shows my problem:
My code:
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
Document doc = commandData.Application.ActiveUIDocument.Document;
View activeView = commandData.View;
if(activeView is ViewSchedule)
{
GetDimensions(activeView as ViewSchedule);
}
return Result.Succeeded;
}
public void GetDimensions(ViewSchedule schedule)
{
TableSectionData bodySelection = schedule.GetTableData().GetSectionData(SectionType.Body);
int numberOfRows = bodySelection.NumberOfRows;
int numberOfColumns = bodySelection.NumberOfColumns;
string[,] values = new string[numberOfRows,numberOfColumns];
FillArray(SectionType.Body, bodySelection, numberOfColumns, numberOfRows, values, schedule);
}
public void FillArray(SectionType secType, TableSectionData data,
int numberOfColumns, int numberOfRows, string[,] values, ViewSchedule schedule)
{
for (int r = data.FirstRowNumber; r < numberOfRows; r++)
{
for (int c = data.FirstColumnNumber; c < numberOfColumns; c++)
{
values[r, c] = schedule.GetCellText(secType, r, c);
}
}
}
Now I want to return the filled array to Execute.
It's impossible to pass it this way directly. There are three options:
make method 2 take the result and pass it back:
int Method2()
{
var method3Result = Method3();
return method3Result;
}
void Method1()
{
var result = Method2(); //indirectly gets from Method3
}
provide a shared state:
public class Method3Result { public int Value { get; set; } }
public class X
{
private Method3Result method3Result = new Method3Result();
public void Method1()
{
Method2();
//process result from method3Result
}
public void Method2()
{
Method3();
}
public void Method3()
{
method3Result.Value = 3;
}
}
provide a callback:
public void Method1()
{
int method3Result;
Method2(value => method3Result = value);
}
public void Method2(Action<int> callback)
{
Method3(callback);
}
public void Method3(Action<int> callback)
{
callback(3);
}
}
In your case option 1 seems straightforward:
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
Document doc = commandData.Application.ActiveUIDocument.Document;
View activeView = commandData.View;
string[,] values = null;
if (activeView is ViewSchedule)
{
values = GetDimensions(activeView as ViewSchedule);
}
return Result.Succeeded;
}
public string[,] GetDimensions(ViewSchedule schedule)
{
//...
return values;
}
Also, it seems that it would be clearer to change FillArray to something like CreateArray and make it also return the array:
public void FillArray(SectionType secType, TableSectionData data, int numberOfColumns, int numberOfRows, string[,] values, ViewSchedule schedule)
{
string[,] values = new string[numberOfRows,numberOfColumns];
for (int r = data.FirstRowNumber; r < numberOfRows; r++)
{
for (int c = data.FirstColumnNumber; c < numberOfColumns; c++)
{
values[r, c] = schedule.GetCellText(secType, r, c);
}
}
return values;
}
so it's easier to use.

How to iterate an array of objects, accumulate a property and update another property(a Total) of each item without using two for statement?

Having a model something like this (I cannot change this):
public class SomeObject
{
public int Amount { get; set; }
public int TotalAmount { get; set; }
}
I need to iterate an array of SomeObject to populate some values and accumulate (perform not simple calculations) another fields.
static void Main(string[] args)
{
List<SomeObject> myCollection = new List<SomeObject>()
{
new SomeObject() { Amount = 3 },
new SomeObject() { Amount = 6 },
new SomeObject() { Amount = 9 }
};
int totalAccumulated = 0;
for (int i = 0; i < myCollection.Count; i++)
{
PopulateAndCalculate(myCollection[i], ref totalAccumulated);
}
//I don't want to create here a second for to iterate again all myCollection to set his TotalAmount property.
//There is another way?
Console.WriteLine($"The total accumulated is: {totalAccumulated}");
}
private static void PopulateAndCalculate(SomeObject prmObject, ref int accumulatedTotal)
{
//Populate a lot of another fields
accumulatedTotal += prmObject.Amount;
prmObject.TotalAmount = accumulatedTotal; //This don't work, but I need something alike
}
I don't want a second for statement to update TotalAmount property of each item in myCollection.
The main requirement is iterate the whole array, few times, don't care about string interpolation this is a short demo, this code must run in .net 2.0.
Theres is a clean/better way?
The solution is actually simple, though it's not exactly a good coding practice.
What you really need is for TotalAmount to be a static property. Without that, there's this:
static void Main(string[] args)
{
List<SomeObject> myCollection = new List<SomeObject>()
{
new SomeObject() { Amount = 3 },
new SomeObject() { Amount = 6 },
new SomeObject() { Amount = 9 }
};
int totalAccumulated = 0;
for (int i = 0; i < myCollection.Count; i++)
{
PopulateAndCalculate(myCollection[i], ref totalAccumulated);
}
/*****This is the new part*******/
myCollection[0].TotalAmount = totalAccumulated;
myCollection[1].TotalAmount = totalAccumulated;
myCollection[2].TotalAmount = totalAccumulated;
Console.WriteLine($"The total accumulated is: {totalAccumulated}");
}
private static void PopulateAndCalculate(SomeObject prmObject, ref int accumulatedTotal)
{
//Populate a lot of another fields
accumulatedTotal += prmObject.Amount;
//no need to mess with the total here as far as the properties are concerned.
}
You can st fields inside linq expression.
Could you consider this please
myCollection.ForEach(c => c.TotalAmount = myCollection.Sum(a => a.Amount));
Console.WriteLine($"Total accumulated :{myCollection.First().TotalAmount}");
I found a solution using the Observer Pattern.
Firstly I created a global delegate to be used by an event:
public delegate void UpdateTotalAmountDelegate(int totalAmount);
Then a new class called: 'CalculatorSetter'
public class CalculatorSetter
{
public event UpdateTotalAmountDelegate UpdateTotalAmounthHandler;
public void UpdateTotalAmount(int prmTotalAmount)
{
UpdateTotalAmounthHandler(prmTotalAmount);
}
}
I refactor the data object 'SomeObject' adding a field of type CalculatorSetter.
public class SomeObject
{
private CalculatorSetter finalCalculator;
public void SetCalculator(CalculatorSetter prmCalculator)
{
this.finalCalculator = prmCalculator;
finalCalculator.UpdateTotalAmounthHandler += FinalCalculator_UpdateTotalAmounthHandler;
}
private void FinalCalculator_UpdateTotalAmounthHandler(int totalAmount)
{
this.TotalAmount = totalAmount;
}
//Some Other Fields
public int Amount { get; set; }
public int TotalAmount { get; set; }
}
And my original code and unique for:
static void Main(string[] args)
{
List<SomeObject> myCollection = new List<SomeObject>()
{
new SomeObject() { Amount = 3 },
new SomeObject() { Amount = 6 },
new SomeObject() { Amount = 9 }
};
CalculatorSetter commonCalculator = new CalculatorSetter();
int totalToAccumulate = 0;
for (int i = 0; i < myCollection.Count; i++)
{
PopulateAndCalculate(myCollection[i], commonCalculator, ref totalToAccumulate);
}
commonCalculator.UpdateTotalAmount(totalToAccumulate);
Console.WriteLine($"The total accumulated is: {totalToAccumulate}");
Console.WriteLine($"The first total accumulated is: {myCollection[0].TotalAmount}");
}
Many thanks.
Use a wrapper and keep it simple (if you want you can change a little for use static methods you can, or static class but I dont see the point)
the result is:
The Amount is 3, The total ammount is 18
The Amount is 6, The total ammount is 18
The Amount is 9, The total ammount is 18
namespace Prueba1
{
class Program
{
public class WrapperInt {
public int Value { get; set; }
}
public class SomeObject
{
public int Amount { get; set; }
public WrapperInt TotalAmount { get; set; }
}
public Program() {
WrapperInt TotalAmountAllArrays = new WrapperInt();
List<SomeObject> myCollection = new List<SomeObject>()
{
new SomeObject() { Amount = 3, TotalAmount =TotalAmountAllArrays },
new SomeObject() { Amount = 6 , TotalAmount =TotalAmountAllArrays },
new SomeObject() { Amount = 9 , TotalAmount =TotalAmountAllArrays }
};
for (int i = 0; i < myCollection.Count; i++)
{
myCollection[i].TotalAmount.Value += myCollection[i].Amount;
}
foreach (var c in myCollection)
{
Console.WriteLine($"The Amount is:" + c.Amount + " The total ammount is:" + c.TotalAmount.Value);
}
}
static void Main(string[] args)
{
new Program();
}
}
}
Hopefully this will work for you… One possible solution is to create a wrapper class called MyTotalList which contains a List named amounts and an int named total. MyTotalList class does not expose its list amounts as an editable list. If the class exposes this list as editable, then other methods could ultimately change an items value in that list and the MyTotalList class would not be aware of this and unfortunately contain an incorrect total. To avoid this situation and for the class to work as expected, methods must use the MyTotalList’s Add and Remove methods. To ensure this happens, the private List amounts in the MyTotalList class returns a read only list which ensures that changes to the list will not be made outside the MyTotalList class. Leaving the list exposed and editable will/could cause the class to contain an incorrect total.
My solution is to create a Class that wraps a List. MyTotalList class has a no argument constructor. Once a new instance of a MyTotalList object is created you can then use that instance to Add MyObject items to its list. Every time an item is added to the MyTotalList, list amounts the variable total gets updated with the added item’s amount. Example:
Create a new MyTotalList object:
MyTotalList listOfObjects = new MyTotalList();
Then add some MyObject instances to the listOfObjects
listOfObjects.Add(new MyObject(1,3));
listOfObjects.Add(new MyObject(2,6));
listOfObjects.Add(new MyObject(3,9));
After you add the items, you can then use the listOfObjects Total property to get the total sum of all MyObject items in the list with:
listOfObjects.Total
If you need to pass or use the List of MyTotalList items you can use:
listOfObjects.Items
Bear in mind as I discussed above, this List Items is a read-only list. Therefore you cannot add/remove items in this list as you would an editable list. So the code below will fail during implementation as these methods are not exposed for read only objects.
listOfObjects.Items.Remove(new MyObject(4, 10));
listOfObjects.Items.Add(new MyObject(4, 10));
The above lines will cause the compiler to complain: xxx… does not contain a definition for Add/Remove. This ensures methods will use the MyTotalList.Add and MyTotalsList.Remove methods and eliminate any possibility of the list changing outside the MyTotalList class.
MyObject Class
class MyObject : IComparable {
public int id { get; }
public int amount { get; }
public MyObject(int inID, int inAmount) {
id = inID;
amount = inAmount;
}
public override string ToString() {
return amount.ToString();
}
public override int GetHashCode() {
return id.GetHashCode();
}
public override bool Equals(object other) {
if (other != null)
return (this.id == ((MyObject)other).id);
return false;
}
public int CompareTo(object other) {
if (this.id > ((MyObject)other).id)
return 1;
if (this.id < ((MyObject)other).id)
return -1;
return 0;
}
}
MyTotalList Class
class MyTotalList {
private int total;
private List<MyObject> amounts;
public MyTotalList() {
total = 0;
amounts = new List<MyObject>();
}
public int ListCount {
get { return amounts.Count; }
}
public IReadOnlyCollection<MyObject> Items {
get { return amounts.AsReadOnly(); }
}
public int Total {
get { return total; }
}
public void Add(MyObject other) {
if (other != null) {
if (!(amounts.Contains(other))) {
total += other.amount;
amounts.Add(other);
}
else {
Console.WriteLine("Duplicate id's not allowed!");
}
}
}
public void Remove(MyObject other) {
if (amounts.Contains(other)) {
total -= amounts[amounts.IndexOf(other)].amount;
amounts.Remove(other);
}
else {
Console.WriteLine("Item to remove not found!");
}
}
}
Examples
MyTotalList listOfObjects = new MyTotalList();
listOfObjects.Add(new MyObject(1,3));
listOfObjects.Add(new MyObject(2,6));
listOfObjects.Add(new MyObject(3,9));
Console.WriteLine("----------------------------------------");
Console.WriteLine("Initial list with total");
Console.WriteLine("items in list:");
foreach (MyObject mo in listOfObjects.Items)
Console.Write(mo.ToString() + " ");
Console.WriteLine();
Console.WriteLine("Total from list of " + listOfObjects.ListCount +
" items is: " + listOfObjects.Total);
Console.WriteLine("----------------------------------------");
Console.WriteLine("Add three more items");
listOfObjects.Add(new MyObject(4, 10));
listOfObjects.Add(new MyObject(5, 11));
listOfObjects.Add(new MyObject(6, 12));
Console.WriteLine("items in list:");
foreach (MyObject mo in listOfObjects.Items)
Console.Write(mo.ToString() + " ");
Console.WriteLine();
Console.WriteLine("Total from list of " + listOfObjects.ListCount +
" items is: " + listOfObjects.Total);
Console.WriteLine("----------------------------------------");
Console.WriteLine("Remove id 4 (10) from the list");
listOfObjects.Remove(new MyObject(4, 10));
Console.WriteLine("items in list:");
foreach (MyObject mo in listOfObjects.Items)
Console.Write(mo.ToString() + " ");
Console.WriteLine();
Console.WriteLine("Total from list of " + listOfObjects.ListCount +
" items is: " + listOfObjects.Total);
A Side note to your original post…About the class you can not change
SomeObject {
public int Amount { get; set; }
public int TotalAmount { get; set; }
}
Regardless of how you get the total for theint varable: TotaAmount… for each instance of SomeObject class to contain the same variable with the same amount and you want to ensure this is true for all existing SomeObject instances… is well a poor design. This creates redundant data and simply waste space and it makes no sense for each variable to contain this value as it has absolutely nothing to do with that SomeObject instance. This class design is counter intuitive of a good design. As #Tim Schmelter’s comment points out "a single object should not know anything about the total amount of other objects." This “redundant data” situation is something a programmer should try to avoid, not promote.

How to maintain instance number in list of objects

I have a class baseClass, and a list of objects of the baseClass. What i want to achieve is that i have to dynamically assign the instance number to each object in the list. for that what am doing is that use a constructor to do this.
Following is the class definition:
public class baseClass
{
private int _InstanceNumber;
private int _MyIntVal;
private string _MyString;
public string MyString
{
get { return _MyString; }
set { _MyString = value; }
}
public int MyIntVal
{
get { return _MyIntVal; }
set { _MyIntVal = value; }
}
public int MyProperty
{
get { return _InstanceNumber; }
}
public baseClass(int instance)
{
_InstanceNumber = instance;
}
}
The creation of the List of objects is as follows:
int instanceNumber = 0;
List<baseClass> classList = new List<baseClass>();
classList.Add(new baseClass(instanceNumber++) { MyString = "sample1", MyIntVal = 10 });
classList.Add(new baseClass(instanceNumber++) { MyString = "sample2", MyIntVal = 11 });
I know it is not the actual way for creating this. it does not give the index number actually. how can i calculate the instance number?
Consider the following scenario, that am creating another list of objects then it hard to maintain the instance number. or if i create another object(this also be an instance) external to the list.
int instanceNumber = 0;
List<baseClass> anotherClassList = new List<baseClass>();
classList.Add(new baseClass(instanceNumber++) { MyString = "sample1", MyIntVal = 10 });
classList.Add(new baseClass(instanceNumber++) { MyString = "sample2", MyIntVal = 11 });
Updates:
This is my temporary solution for this. i need proper way/ method to maintain instance number
If you want to find the index of item in the list, you should ask it from the list, not the item like:
var index = list.IndexOf(item);
But it seems that you expect the item to be aware of its position in the list. In order to do this, you should pass the list to the item so it can use it to find its own place in it:
public class Item
{
private List<Item> _containerList;
public Item(List<Item> containerList)
{
_containerList = containerList;
}
public int InstanceNumber
{
get { return _containerList.IndexOf(this); }
}
}
and change your code to:
List<Item> classList = new List<Item>();
classList.Add(new Item(classList ) { ... });
classList.Add(new Item(classList ) { ... });

How to create an efficient data structure for two-dimensional LINQ operations?

Problem: I have 2 kinds of objects, lets call them Building and Improvement. There are roughly 30 Improvement instances, while there can be 1-1000 Buildings. For each combination of Building and Improvement, I have to perform some heavy calculation, and store the result in a Result object.
Both Buildings and Improvements can be represented by an integer ID.
I then need to be able to:
Access the Result for a given Building and Improvement efficiently (EDIT: see comment further down)
Perform aggregations on the Results for all Improvements for a given Building, like .Sum() and .Average()
Perform the same aggregations on the Results for all Buildings for a given Improvement
This will happen on a web-server back-end, so memory may be a concern, but speed is most important.
Thoughts so far:
Use a Dictionary<Tuple<int, int>, Result> with <BuildingID, ImprovementID> as key. This should give me speedy inserts and single lookups, but I am concerned about .Where() and .Sum() performance.
Use a two-dimensional array, with one dimension for BuildingIDs and one for ImprovementIDs, and the Result as value. In addition, build two Dictionary<int, int> that map BuildingIDs and ImprovementIDs to their respective array row/column indexes. This could potentially mean max 1000+ Dictionarys, will this be a problem?
Use a List<Tuple<int, int, Result>>. I think this may be the least efficient, with O(n) inserts, though I could be wrong.
Am I missing an obvious better option here?
EDIT: Turns out it is only the aggregated values (per Building and per Improvement) I am interested in; see my answer.
Generally, the Dictionary is most lookup efficent. The both lookup efficency and manipulation efficency is constant O(1), when accessed via key. This will help for access, the first point.
In the second and third you need to walk through all of the items O(n), so there is no way to speed it except you want to walk them through in specified order O(n*n) - then you can use SortedDictionray O(n), but you compromise the lookup and manipulation efficency (O(log n)).
So I would go with the 1st solution you post.
You could use a "dictionary of dictionaries" to hold the Result data, for example:
// Building ID ↓ ↓ Improvement ID
var data = new Dictionary<int, Dictionary<int, Result>>();
This would let you quickly find the improvements for a particular building.
However, finding the buildings that contain a particular improvement would require iterating over all the buildings. Here's some sample code:
using System;
using System.Linq;
using System.Collections.Generic;
namespace Demo
{
sealed class Result
{
public double Data;
}
sealed class Building
{
public int Id;
public int Value;
}
sealed class Improvement
{
public int Id;
public int Value;
}
class Program
{
void run()
{
// Building ID ↓ ↓ Improvement ID
var data = new Dictionary<int, Dictionary<int, Result>>();
for (int buildingKey = 1000; buildingKey < 2000; ++buildingKey)
{
var improvements = new Dictionary<int, Result>();
for (int improvementKey = 5000; improvementKey < 5030; ++improvementKey)
improvements.Add(improvementKey, new Result{ Data = buildingKey + improvementKey/1000.0 });
data.Add(buildingKey, improvements);
}
// Aggregate data for all improvements for building with ID == 1500:
int buildingId = 1500;
var sum = data[buildingId].Sum(result => result.Value.Data);
Console.WriteLine(sum);
// Aggregate data for all buildings with a given improvement.
int improvementId = 5010;
sum = data.Sum(improvements =>
{
Result result;
return improvements.Value.TryGetValue(improvementId, out result) ? result.Data : 0.0;
});
Console.WriteLine(sum);
}
static void Main()
{
new Program().run();
}
}
}
To speed up the second aggregation (for summing data for all improvements with a given ID) we can use a second dictionary:
// Improvment ID ↓ ↓ Building ID
var byImprovementId = new Dictionary<int, Dictionary<int, Result>>();
You would have an extra dictionary to maintain, but it's not too complicated. Having a few nested dictionaries like this might take too much memory though - but it's worth considering.
As noted in the comments below, it would be better to define types for the IDs and also for the dictionaries themselves. Putting that together gives:
using System;
using System.Linq;
using System.Collections.Generic;
namespace Demo
{
sealed class Result
{
public double Data;
}
sealed class BuildingId
{
public BuildingId(int id)
{
Id = id;
}
public readonly int Id;
public override int GetHashCode()
{
return Id.GetHashCode();
}
public override bool Equals(object obj)
{
var other = obj as BuildingId;
if (other == null)
return false;
return this.Id == other.Id;
}
}
sealed class ImprovementId
{
public ImprovementId(int id)
{
Id = id;
}
public readonly int Id;
public override int GetHashCode()
{
return Id.GetHashCode();
}
public override bool Equals(object obj)
{
var other = obj as ImprovementId;
if (other == null)
return false;
return this.Id == other.Id;
}
}
sealed class Building
{
public BuildingId Id;
public int Value;
}
sealed class Improvement
{
public ImprovementId Id;
public int Value;
}
sealed class BuildingResults : Dictionary<BuildingId, Result>{}
sealed class ImprovementResults: Dictionary<ImprovementId, Result>{}
sealed class BuildingsById: Dictionary<BuildingId, ImprovementResults>{}
sealed class ImprovementsById: Dictionary<ImprovementId, BuildingResults>{}
class Program
{
void run()
{
var byBuildingId = CreateTestBuildingsById(); // Create some test data.
var byImprovementId = CreateImprovementsById(byBuildingId); // Create the alternative lookup dictionaries.
// Aggregate data for all improvements for building with ID == 1500:
BuildingId buildingId = new BuildingId(1500);
var sum = byBuildingId[buildingId].Sum(result => result.Value.Data);
Console.WriteLine(sum);
// Aggregate data for all buildings with a given improvement.
ImprovementId improvementId = new ImprovementId(5010);
sum = byBuildingId.Sum(improvements =>
{
Result result;
return improvements.Value.TryGetValue(improvementId, out result) ? result.Data : 0.0;
});
Console.WriteLine(sum);
// Aggregate data for all buildings with a given improvement using byImprovementId.
// This will be much faster than the above Linq.
sum = byImprovementId[improvementId].Sum(result => result.Value.Data);
Console.WriteLine(sum);
}
static BuildingsById CreateTestBuildingsById()
{
var byBuildingId = new BuildingsById();
for (int buildingKey = 1000; buildingKey < 2000; ++buildingKey)
{
var improvements = new ImprovementResults();
for (int improvementKey = 5000; improvementKey < 5030; ++improvementKey)
{
improvements.Add
(
new ImprovementId(improvementKey),
new Result
{
Data = buildingKey + improvementKey/1000.0
}
);
}
byBuildingId.Add(new BuildingId(buildingKey), improvements);
}
return byBuildingId;
}
static ImprovementsById CreateImprovementsById(BuildingsById byBuildingId)
{
var byImprovementId = new ImprovementsById();
foreach (var improvements in byBuildingId)
{
foreach (var improvement in improvements.Value)
{
if (!byImprovementId.ContainsKey(improvement.Key))
byImprovementId[improvement.Key] = new BuildingResults();
byImprovementId[improvement.Key].Add(improvements.Key, improvement.Value);
}
}
return byImprovementId;
}
static void Main()
{
new Program().run();
}
}
}
Finally, here's a modified version which determines the time it takes to aggregate data for all instances of a building/improvement combination for a particular improvement and compares the results for dictionary of tuples with dictionary of dictionaries.
My results for a RELEASE build run outside any debugger:
Dictionary of dictionaries took 00:00:00.2967741
Dictionary of tuples took 00:00:07.8164672
It's significantly faster to use a dictionary of dictionaries, but this is only of importance if you intend to do many of these aggregations.
using System;
using System.Diagnostics;
using System.Linq;
using System.Collections.Generic;
namespace Demo
{
sealed class Result
{
public double Data;
}
sealed class BuildingId
{
public BuildingId(int id)
{
Id = id;
}
public readonly int Id;
public override int GetHashCode()
{
return Id.GetHashCode();
}
public override bool Equals(object obj)
{
var other = obj as BuildingId;
if (other == null)
return false;
return this.Id == other.Id;
}
}
sealed class ImprovementId
{
public ImprovementId(int id)
{
Id = id;
}
public readonly int Id;
public override int GetHashCode()
{
return Id.GetHashCode();
}
public override bool Equals(object obj)
{
var other = obj as ImprovementId;
if (other == null)
return false;
return this.Id == other.Id;
}
}
sealed class Building
{
public BuildingId Id;
public int Value;
}
sealed class Improvement
{
public ImprovementId Id;
public int Value;
}
sealed class BuildingResults : Dictionary<BuildingId, Result>{}
sealed class ImprovementResults: Dictionary<ImprovementId, Result>{}
sealed class BuildingsById: Dictionary<BuildingId, ImprovementResults>{}
sealed class ImprovementsById: Dictionary<ImprovementId, BuildingResults>{}
class Program
{
void run()
{
var byBuildingId = CreateTestBuildingsById(); // Create some test data.
var byImprovementId = CreateImprovementsById(byBuildingId); // Create the alternative lookup dictionaries.
var testTuples = CreateTestTuples();
ImprovementId improvementId = new ImprovementId(5010);
int count = 10000;
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < count; ++i)
byImprovementId[improvementId].Sum(result => result.Value.Data);
Console.WriteLine("Dictionary of dictionaries took " + sw.Elapsed);
sw.Restart();
for (int i = 0; i < count; ++i)
testTuples.Where(result => result.Key.Item2.Equals(improvementId)).Sum(item => item.Value.Data);
Console.WriteLine("Dictionary of tuples took " + sw.Elapsed);
}
static Dictionary<Tuple<BuildingId, ImprovementId>, Result> CreateTestTuples()
{
var result = new Dictionary<Tuple<BuildingId, ImprovementId>, Result>();
for (int buildingKey = 1000; buildingKey < 2000; ++buildingKey)
for (int improvementKey = 5000; improvementKey < 5030; ++improvementKey)
result.Add(
new Tuple<BuildingId, ImprovementId>(new BuildingId(buildingKey), new ImprovementId(improvementKey)),
new Result
{
Data = buildingKey + improvementKey/1000.0
});
return result;
}
static BuildingsById CreateTestBuildingsById()
{
var byBuildingId = new BuildingsById();
for (int buildingKey = 1000; buildingKey < 2000; ++buildingKey)
{
var improvements = new ImprovementResults();
for (int improvementKey = 5000; improvementKey < 5030; ++improvementKey)
{
improvements.Add
(
new ImprovementId(improvementKey),
new Result
{
Data = buildingKey + improvementKey/1000.0
}
);
}
byBuildingId.Add(new BuildingId(buildingKey), improvements);
}
return byBuildingId;
}
static ImprovementsById CreateImprovementsById(BuildingsById byBuildingId)
{
var byImprovementId = new ImprovementsById();
foreach (var improvements in byBuildingId)
{
foreach (var improvement in improvements.Value)
{
if (!byImprovementId.ContainsKey(improvement.Key))
byImprovementId[improvement.Key] = new BuildingResults();
byImprovementId[improvement.Key].Add(improvements.Key, improvement.Value);
}
}
return byImprovementId;
}
static void Main()
{
new Program().run();
}
}
}
Thanks for the answers, the test code was really informative :)
The solution for me turned out to be to forgo LINQ, and perform aggregation manually directly after the heavy calculation, as I had to iterate over each combination of Building and Improvement anyway.
Also, I had to use the objects themselves as keys, in order to perform calculations before the objects were persisted to Entity Framework (i.e. their IDs were all 0).
Code:
public class Building {
public int ID { get; set; }
...
}
public class Improvement {
public int ID { get; set; }
...
}
public class Result {
public decimal Foo { get; set; }
public long Bar { get; set; }
...
public void Add(Result result) {
Foo += result.Foo;
Bar += result.Bar;
...
}
}
public class Calculator {
public Dictionary<Building, Result> ResultsByBuilding;
public Dictionary<Improvement, Result> ResultsByImprovement;
public void CalculateAndAggregate(IEnumerable<Building> buildings, IEnumerable<Improvement> improvements) {
ResultsByBuilding = new Dictionary<Building, Result>();
ResultsByImprovement = new Dictionary<Improvement, Result>();
for (building in buildings) {
for (improvement in improvements) {
Result result = DoHeavyCalculation(building, improvement);
if (ResultsByBuilding.ContainsKey(building)) {
ResultsByBuilding[building].Add(result);
} else {
ResultsByBuilding[building] = result;
}
if (ResultsByImprovement.ContainsKey(improvement)) {
ResultsByImprovement[improvement].Add(result);
} else {
ResultsByImprovement[improvement] = result;
}
}
}
}
}
public static void Main() {
var calculator = new Calculator();
IList<Building> buildings = GetBuildingsFromRepository();
IList<Improvement> improvements = GetImprovementsFromRepository();
calculator.CalculateAndAggregate(buildings, improvements);
DoStuffWithResults(calculator);
}
I did it this way because I knew exactly which aggregations I wanted; if I required a more dynamic approach I would probably have gone with something like #MatthewWatson's Dictionary of Dictionaries.

Categories

Resources