Implementing spreadsheet functionality (auto updating variables) in compiled code - c#

I need a place in my code where variables auto-update when other variables change that they are dependent on.
The difference from spreadsheets is I don't want all the parsing stuff, I just want everything done in code as I don't need to update formulas/variables after the program has been compiled.
Basically there is input data, and a few "cells" from the spreadsheet part will listen in on those values, change, and the change will propagate through the "spreadsheet" part. Like a normal spreadsheet.
So I can then immediately after depend on all those values, instead of having to write all kinds of code to update all the variables correctly first, which would be very hard to do.
If anyone can help me with the thought process to implement this it would really help! I've been reading some spreadsheet source code but it will take a long time to understand that, and then understand how I would change those ideas to fit what I need to do.
Edit:
Right now I just have things in loops and structure everything correctly so it updates correctly like so:
A1 = 2;
B1 = A1 + 2;
Then this just loops again and again. But if I wanted things to update automatically, how would I store the calculations attached to the variables? So when updating B it would call A1 + 2 ?
By the way A1, and B1 are just random variables. I don't name things by cell names and there is no such structure.

One way of storing calculations is to use the elements of functional programming that have been built into C#.
A formula can be stored as a function, using one of the Func<T1,T2,...TResult> delegates.
var CalculationForX = new Func<double, double, double>(
(arg1, arg2) => arg1 * arg2
);
Here, CalculationForX specifies a function that takes two doubles, and returns a the product of the two doubles. Then using for example CalculationForX(5, 2) returns 10.
So, a straightforward way to keep track of all the dependencies would be to store an object for each dependent variable, containing its formula and inputs. These could be in the form of:
an array of strings that contain the property names of a given object
a PropertyInfo array
an Action<T1,T2> delegate represents the actual property setter
Whenever you receive a property changed event, retrieve the calculation and the inputs for all variables that depend on it, and update the target values.
(It's funny to me that you asked this question, as I asked a quite similar one yesterday on Programmers.)

Related

OutputBuffer not working for large c# list

I'm currently using SSIS to do an improvement on a project. need to insert single documents in a MongoDB collection of type Time Series. At some point I want to retrieve rows of data after going through a C# transformation script. I did this:
foreach (BsonDocument bson in listBson)
{
OutputBuffer.AddRow();
OutputBuffer.DatalineX = (string) bson.GetValue("data");
}
But this piece of code that works great with small file does not work with a 6 million line file. That is, there are no lines in the output. The other following tasks validate but react as if they had received nothing as input.
Where could the problem come from?
Your OuputBuffer has DatalineX defined as a string, either DT_STR or DT_WSTR and a specific length. When you exceed that value, things go bad. In normal strings, you'd have a maximum length of 8k or 4k respectively.
Neither of which are useful for your use case of at least 6M characters. To handle that, you'll need to change your data type to DT_TEXT/DT_NTEXT Those data types do not require a length as they are "max" types. There are lots of things to be aware of when using the LOB types.
Performance can suck depending on whether SSIS can keep the data in memory (good) or has to write intermediate values to disk (bad)
You can't readily manipulate them in a data flow
You'll use a different syntax in a Script Component to work with them
e.g.
// TODO: convert to bytes
Output0Buffer.DatalineX.AddBlobData(bytes);
Longer example of questionable accuracy with regard to encoding the bytes that you get to solve at https://stackoverflow.com/a/74902194/181965

Equivalent of VLOOKUP in C#

Ive been struggling to figure out what the correct way to do this is.
I have a table in Excel that relates Motor horsepower size to the amount of space it takes up.
I would like to implement a lookup function of this information in a C# Windows Form that I am writing.
I want to create a function that has a single argument passed to it (double HP). It would lookup if the motor HP falls in-between "Low" and "High" and then it would return the value in the "Space Factor" column. I feel pretty silly for not knowing the best way to do this. I want to avoid IF/Else statements as it doesnt seem like the right approach. Additionally, interfacing with Excel to do the lookup also seems a little silly.
How would you interface with a data table like this?
You should create an class to represent your rows, you can then add each object to a list. If you have few items you can just use Linq:
var hp = 20;
var objects = new List<MyClass>(){...};
var foundObj = objects.FirstOrDefault(o => hp > o.HpLow && hp < o.HpHigh);
if(foundObj != null){
// Found object!
}
If you have lots of rows you can instead sort the list by the HpHigh, and use BinarySearch to find the item without iterating over all items, but it requires a custom comparer and is a bit more complex.
If hp-ranges can overlap for you need some way to determine which of the rows is the most fitting one, so I'm just going to assume ranges do not overlap.
I would personally do this with c# arrays, however it might not be best or most practical solution for your solution, which im not quite sure how it works, however array is quick and easy to setup.

How to evaluate a string representation of a formula/parameter in POI Apache/NPOI directly

I want to convert some spreadsheets which contain references to a plugin-defined function which we are trying to remove. There is a method to convert this which works in VBA by using SUMIFS and generating a table
I am at the step where I have parsed the formulas and extracted the parameters used for the function (done by bracket counting). I have hit a snag. How do I get NPOI/POI Apache to evaluate the things. It seems to demand everything be done inside a cell. This isn't possible in this scenario (since these are parameters not cell formulas, so they don't have a cell in which to evaluate them).
There is the OperandResolver class which seems to be along the right lines, but it wants a ValueEval type as its input which I can't figure out at all. Currently I can only get the parameters as strings. Like "A1", "0.9", "SomeOtherFunction(...)" etc. Those are what I have.
Basically I need something like
pseudocode:
var result = Evaluate_Formula_String(string formula, var Contextual_Information_eg_current_Worksheet)
Where the result would contain either a string or something easily converted into a string.
The function you need seems to be simply Application.Evaluate. It takes a string and evaluates it.
Reference page: https://learn.microsoft.com/en-us/office/vba/api/excel.application.evaluate

What is a good substitute for a big switch-case?

I have objects called Country. At some point in the program, I want to set the field power of each object.
The power for each country is fixed and I have data for all 196 countries here on a piece of paper. My code should check, for instance, if the country's name is USA (and if so, set its power to 100) and so on.
I know I can do it with a switch-case, but what is the best, nicest, and most efficient way to do it?
You can store country-power pairs into a Dictionary<string, int> then just get the score of a particular country by using indexer:
var points = new Dictionary<string,int>();
// populate the dictionary...
var usa = points["USA"];
Edit: As suggested in comments you should store the information in external file, for example an xml would be a good choice. That way you don't have to modify the code to add or remove countries. You just need to store them into XML file, edit it whenever you need.Then parse it when your program starts, and load the values into Dictionary.You can use LINQ to XML for that.If you haven't use it before there are good examples in the documentation to get you started.
Whilst Selmans answer is right and good, it does not answer how to actually populate the Dictionary. Here is it:
var map = new Dictionary<string, int> {
{"USA", 100},
{"Germany", 110}
};
you may however also just add it as follows:
map.Add("USA", 100);
map.Add("Germany", 110);
Now you may access the value (as already mentioned by Semans):
map["USA"] = 50; // set new value for USA
int power = map["USA"]; // get new value
EDIT: As already mentioned within comments and other answers you may of course store the data within an external file or any other data-storage. Having said this you may just initialize an empty dictionary and then fill it with the Add-method previously mentioned for every record within that storage.
This is the right question to begin with, but there are a lot of things you need to learn. Many folk have given you answers to the question you asked. I'm going to be annoyingly Zen and tell you to unask the question because there is a larger problem to resolve.
Instead of hard coding this, store the related properties in an n-tuple also known as a database row and use a database engine to manage the relation between the two. And then since you are using C# it would probably be smart to learn to use LINQ. But before you do that, learn a bit of data modelling theory, because data-modelling is what you are doing.
Since you said you have "objects" called "Country", and you have tagged your question "C#", it would seem that you have two forces at work in your code. One is that having to refer to a map, however efficiently implemented, is not as cheap as referring to a member variable. On the other hand there might be some benefit to a setup where all the attributes of a country can be found in the same place as the attributes of other countries (the map-oriented solutions do address this concern). But these forces can be reconciled something like this:
class Country { // Apologies that this sketch is more C++ than C#
public:
Country(string name_, int power_);
private:
string name;
int power;
};
void MakeCountries()
{
countries.Add(new Country("USA", 50));
countries.Add(new Country("Germany", 60));
// ....
}
Do you need to update your data at runtime?
Yes? Load data from external storage into a dictionary.
No? Use a switch
Let the compiler generate dictionaries and hash-based lookups for you.
When you profiler starts screaming, explore alternative solutions.
For example, read that answer from "What is quicker, switch on string or elseif on type?".
What about making an array of Strings for storing country names in ascending order of their power. It will be more simple to implement.Then the index of each country can represent its power. This is possible, only if the power is continues counting numbers.
If its not , another siple way is to implement them as linked list. So that u will be able to change if u want. A list with 2 fields; 1for the country and other for the power

Getting my head around string manipulation

I've been working on an ExcelDNA/C# add in for a while, and I'm at the final hurdle.
I can get a selection address, and I first of all need to check if the rows in that selection are simply two, e.g. in Excel they would be 8+9, two rows next to each other or any two consecutive numbers.
I then need to check that there are more than two columns, etc C to J (more than two spaces in the alphabet).
This all needs to be done from a string like this: Sheet1!$C$8:$J$9
What I am trying to do, is split a selection like this, which returns the above string, into two strings, in the case of the example, the desired end result would be
Sheet1!$C$8:$J$8 + Sheet1!$C$9:$J$9 in two different strings, perhaps I need more coffee, but if anyone has a less trashy way of doing this than I plan, I would be forever in your debt!
Do you have a reference to the Range object? If so, range.Rows.Count == 2 will tell you if there are two rows, and range.Columns.Count > 2 will tell you if there are more than two columns.
Then, to get the addresses of the two rows independently you can do something like:-
var address1 = range.Rows[1].Address(external:true);
var address2 = range.Rows[2].Address(external:true);
I presume this is running from a macro called from a CommandBar or Ribbon event handler.
On that case you might as well use the COM Automation interface from the start, and never bother the the C API (XlCall and ExcelReference).
To get the current selection you'd just say
Application xlApp = (Application)ExcelDnaUtil.Application;
Range selectedRange = xlApp.Selection as Range;
if (selectedRange != null)
{ .... do you further checking here ...}
The C API stuff is a bit more important if you are making user-defined functions or you want high-performance data transfers. But for regular macros the COM API is easier.

Categories

Resources