Simplest way to populate matrix with differing values in x and y? - c#

I have a 3x3 matrix that I want to populate (may grow to 3x4 or 3x5 but not larger). Very simple with a dual for loop except that each row has a unique formula and each column has a unique column within the formula.
I started trying to create for loops, case statements, but ended up just brute force updating each cell.
Then I thought maybe some master crafter has some ideas. Is there any way to make this simpler:
myWorksheet.Cells[1, 1].Formula = "=ROUND(AVERAGE(B$2:B$" + counter + "), 3)";
myWorksheet.Cells[1, 2].Formula = "=ROUND(AVERAGE(C$2:C$" + counter + "), 3)";
myWorksheet.Cells[1, 3].Formula = "=ROUND(AVERAGE(D$2:D$" + counter + "), 3)";
myWorksheet.Cells[2, 1].Formula = "=MAX(B$2:B$" + counter + ")";
myWorksheet.Cells[2, 2].Formula = "=MAX(C$2:C$" + counter + ")";
myWorksheet.Cells[2, 3].Formula = "=MAX(D$2:D$" + counter + ")";
myWorksheet.Cells[3, 1].Formula = "=STDEV(B$2:B$" + counter + ")";
myWorksheet.Cells[3, 2].Formula = "=STDEV(C$2:C$" + counter + ")";
myWorksheet.Cells[3, 3].Formula = "=STDEV(D$2:D$" + counter + ")";

Your formula has three portions, the function name, the argument, and the closing. You can make a method that will create the formula to insert based on the cell's row and column coordinates.
public static string RenderFormula(int row, int column, int counter)
{
var stringBuilder = new StringBuilder();
stringBuilder.Append("=");
// part 1 - the function name
var methodName = row switch
{
1 => "ROUND(AVERAGE(",
2 => "MAX(",
3 => "STDEV(",
_ => throw new ArgumentException(nameof(row))
};
stringBuilder.Append(methodName);
// part 2 - the argument
char rangeColumnLetter = (char)('A' + column);
var range = $"{rangeColumnLetter}$2:{rangeColumnLetter}${counter}";
stringBuilder.Append(range);
// part 3 - the closing
var methodEnding = row switch
{
1 => "), 3)",
2 or 3 => ")",
_ => throw new ArgumentException(nameof(row))
};
stringBuilder.Append(methodEnding);
return stringBuilder.ToString();
}
In part one, you know which excel function to use based on the row number.
In part two, you add the column number to a capital "A" to get the new range column letter. This means that (column 1 + "A" = "B"). Add the value of counter into it.
In part three, you need to close the function's parentheses. Row 1 formulas have two closing parentheses with another argument in the middle, so account for it.
This uses StringBuilder to avoid a lot of wasteful concatenations.
Then just call the method to find out what the formula to insert is.
Console.WriteLine(RenderFormula(1, 1, 50));
Console.WriteLine(RenderFormula(2, 2, 50));
Console.WriteLine(RenderFormula(3, 3, 50));
// =ROUND(AVERAGE(B$2:B$50), 3)
// =MAX(C$2:C$50)
// =STDEV(D$2:D$50)

Related

Minitab Automation and Skipping Columns

My main code is below:
Mtb.Application MtbApp = new Mtb.Application();
MtbApp.UserInterface.Visible = true;
MtbApp.UserInterface.DisplayAlerts = false;
Mtb.Project MtbProj = MtbApp.ActiveProject;
Mtb.Columns MtbColumns;
Mtb.Column MtbColumn1;
Double[] data1;
Hashtable htSingleColumn;
List<double> listSingleColumn;
int i = 1 ;
foreach (DictionaryEntry de in htDataTable)
{
htSingleColumn = (Hashtable)de.Value;
listSingleColumn = (List<double>)htSingleColumn["listSingleData"];
data1 = listSingleColumn.ToArray();
MtbColumns = MtbProj.ActiveWorksheet.Columns;
MtbColumn1 = MtbColumns.Add(null, null, i);
MtbColumn1.SetData(data1);
// strLowlim and strUpplim have no influence on this issue here
strCommand = "Capa C" + i+" 1;" + ((strLowlim == "NA") ? "" : (" Lspec " + strLowlim + ";")) +((strUpplim == "NA") ? "" : (" Uspec " + strUpplim + ";"))+ " Pooled; AMR; UnBiased; OBiased; Toler 6; Within; Percent; CStat.";
// The program is crashing here as a result of the columns not being created sequentially
MtbProj.ExecuteCommand(strCommand);
Mtb.Graph MtbGraph = MtbProj.Commands.Item(i).Outputs.Item(1).Graph;
MtbGraph.SaveAs("C:\\MyGraph" + DateTime.Now.ToString("yyyy-MM-dd HHmmss"), true, Mtb.MtbGraphFileTypes.GFPNGHighColor);
i++;
}
MtbApp.Quit();
When running this code (with the crashing section commented out), I get the following output:
It should look like this:
I am really puzzled about this result. The variable i is right, but what is affecting the column number?
I can't find much information on the Web about Minitab. I just read the start guide here.
This line is the problem.
MtbColumn1 = MtbColumns.Add(null, null, i);
The third parameter Quantity specifies the number of columns to add. On the first iteration of the loop, you add i = 1 column, but on the second iteration of the loop you add i = 2 columns. Each iteration of the loop will add an additional i columns, when what you really want is to add one column each time.
Change the line to:
MtbColumn1 = MtbColumns.Add();

Count the number of rows in each column

How can I count number of rows in specified column in a Excel sheet?
For example I have 2 columns in a spreadsheet:
A B
--- -----
abc hi
fff hello
ccc hi
hello
The result should look like:
count of A column is 3
count of B column is 4
How can I do this using Microsoft Interop?
The approach suggested by Doug Glancy is accurate and simple to be implemented. You can write the function and retrieve the value from a cell not seenable by the user (ZZ1000, for example). The code is straightforward:
Range notUsed = curSheet.get_Range("ZZ1000", "ZZ1000");
string targetCol = "A";
notUsed.Value2 = "=COUNTA(" + targetCol + ":" + targetCol + ")";
int totRows = Convert.ToInt32(notUsed.Value2);
notUsed.Value2 = "";
UPDATE ---
From your example I understood that you were looking for the total number of non-empty cells, what COUNTA delivers. But, apparently, this is not the case: you want the row number of the last non-empty cell; that is, by using a more descriptive example:
C
---
abc
fff
ccc
hello
You don't want to count the number of non-empty cells (4 in this case; what COUNTA delivers), but the position of "hello", that is, 5.
I don't like relying on Excel formulae too much, unless for clearly-defined problems (like yours, as I understood it initially). Excel formulae deliver still the best solution for what you really want (although its complexity is right "in the limit"). To account for the situation as described above, you can rely on MATCH. If your cells contain text (at least one letter per cell), the code can be changed into:
notUsed.Value2 = "=MATCH(REPT(\"z\",255)," + targetCol + ":" + targetCol + ")";
In case of having numeric values (not a single letter in the cell):
notUsed.Value2 = "=MATCH(LOOKUP(" + Int32.MaxValue.ToString() + "," + targetCol + ":" + targetCol + ")," + targetCol + ":" + targetCol + ")";
If you want to account for both options, you would have to combine these equations: you can create a new formula including both; or you might rely on C# code (e.g., get the values from both equations and consider only the bigger one).
Bear also in mind that you have to account for cases where no matches are found. Here you have a code accounting for both situations (letters and numbers via C# code) and for no matches:
notUsed.Value2 = "=MATCH(REPT(\"z\",255)," + targetCol + ":" + targetCol + ")";
int lastLetter = Convert.ToInt32(notUsed.Value2);
if (lastLetter == -2146826246)
{
lastLetter = 0;
}
totRows = lastLetter;
notUsed.Value2 = "=MATCH(LOOKUP(" + Int32.MaxValue.ToString() + "," + targetCol + ":" + targetCol + ")," + targetCol + ":" + targetCol + ")";
int lastNumber = Convert.ToInt32(notUsed.Value2);
if (lastNumber == -2146826246)
{
lastNumber = 0;
}
if (lastNumber > totRows)
{
totRows = lastNumber;
}
This should do it:
private static int GetRowsInColumnOnWorkSheetInWorkbook(string workbookName, int worksheetNumber, int workSheetColumn)
{
return new Excel.Application().Workbooks.Open(workbookName)
.Sheets[worksheetNumber]
.UsedRange
.Columns[workSheetColumn]
.Rows
.Count;
}
You could have the following override also:
private static int GetRowsInColumnOnWorkSheetInWorkbook(string workbookName, string worksheetName, int workSheetColumn)
{
return new Excel.Application().Workbooks.Open(workbookName)
.Sheets[worksheetName]
.UsedRange
.Columns[workSheetColumn]
.Rows
.Count;
}
It's slightly longer than the other answer, but I think this is more readable, and simpler.

Convert text data into multidimensional array in C#:

I have a following string, with line breaks in a textfile:
Card No. Seq Account 1 Account 2 Account 3 Account 4 Customer Name Expiry Status
0100000184998 1 2500855884500 - - /NIRMAL PRADHAN 1302 Cold
0100000186936 1 - - - /RITA SHRESTHA 1302 Cold
0100000238562 1 2500211214500 - - /HARRY SHARMA 1301 Cold
0100000270755 0 1820823730100 - - /EXPRESS ACCOUNT 9999 Cold
0100000272629 0 1820833290100 - - - /ROMA MAHARJAN 1208 Cold
0100000272637 0 2510171014500 - - /NITIN KUMAR SHRESTHA 1208 Cold
0100000272645 0 1800505550100 - - - /DR HARI BHATTA 1208 Cold
Here,
Card No., Seq has fixed digits.
Account 1, Account 2, Account 3, Account 4 can have fixed digit
number or - or null.
Customer Name can have First Name, Last Name, Middle Name etc.
My desired result is:
array[0][0] = "0100000184998"
array[0][1] = "1"
array[0][2] = "2500855884500"
array[0][3] = " "
array[0][4] = "-"
array[0][6] = "NIRMAL PRADHAN "
array[1][0] = "0100000186936"
array[1][1] = "1"
array[1][3] = " "
array[1][4] = "-"
Here, What I tried is:
var sourceFile = txtProcessingFile.Text;
string contents = System.IO.File.ReadAllText(sourceFile);
var newarr = contents.Split(new char[]{ '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Select (x =>
x.Split(new char[]{ ' ' }, StringSplitOptions.RemoveEmptyEntries).ToArray()
).ToArray();
DataTable dt = new DataTable("NewDataTable");
dt.Columns.Add("CardNo");
dt.Columns.Add("SNo");
dt.Columns.Add("Account1");
and so on...
for (int row = 0; row < newarr.Length; row++)
{
for (int col = 0; col < newarr[col].Length; col++)
{
dt.Rows.Add(newarr[row]);
row++;
}
}
This works fine if data field is not empty and Customer name is only the first name or delimited.
But, here what I am trying to get is:
First Name, Middle Name or Last Name must be stored in the same
array element.
Account Number in the array element must left blank if it is blank.
How is it possible to store it correctly on the datatable ?
I suggest that you learn to use the TextFieldParser class. Yes, it's in the Microsoft.VisualBasic namespace, but you can use it from C#. That class lets you easily parse text files that have fixed field widths. See the article How to: Read From Fixed-width Text Files in Visual Basic for an example. Again, the sample is in Visual Basic, but it should be very easy to convert to C#.
If you are willing to make the compromise of not making a difference between - and null values in the account values, you may try this:
var sourceFile = txtProcessingFile.Text;
string[] contents = System.IO.File.ReadAllLines(sourceFile);
DataTable dt = new DataTable("NewDataTable");
dt.Columns.Add("CardNo");
dt.Columns.Add("SNo");
dt.Columns.Add("Account1");
dt.Columns.Add("Account2");
dt.Columns.Add("Account3");
dt.Columns.Add("Account4");
dt.Columns.Add("CustomerName");
dt.Columns.Add("Expiry");
dt.Columns.Add("Status");
for (int row = 2; row < contents.Length; row++)
{
var newRow = dt.NewRow();
var regEx = new Regex(#"([\w]*)");
var matches = regEx.Matches(contents[row].ToString())
.Cast<Match>()
.Where(m => !String.IsNullOrEmpty(m.Value))
.ToList();
var numbers = matches.Where(m => Regex.IsMatch(m.Value, #"^\d+$")).ToList();
var names = matches.Where(m => !Regex.IsMatch(m.Value, #"^\d+$")).ToList();
for (int i = 0; i < numbers.Count() - 1; i++)
{
newRow[i] = numbers.Skip(i).First();
}
newRow[newRow.ItemArray.Length - 2] = numbers.Last();
newRow[newRow.ItemArray.Length - 1] = names.Last();
newRow[newRow.ItemArray.Length - 3] = names.Take(names.Count() - 1).Aggregate<Match, string>("", (a, b) => a += " " + b.Value);
dt.Rows.Add(newRow);
}
To get around the names with a single space in them, you could try splitting on a double-space instead of a single space:
x.Split(new string[]{ " " }
This still won't fix the issue with columns that have no value in them. It appears that your text file has everything in a specific position. Seq is in position 16, Account 1 is in position 20, etc.
Once your lines are stored in newarr, you may just want to use String.Substring() with .Trim() to get the value in each column.

Count of text in RichtextBox (Line and Column)

I'm working on a code editor and I just want to know how to do codes in counting Lines and Columns in richtextbox. Particularly something like this one in actual code editor:
Let's just say count will transfer in a ListBox.
Is there a fast way I can do it?
You can do this :
//This to get lines number.
int index = richTextBox.SelectionStart;
int li = richTextBox.GetLineFromCharIndex(index);
// This to get columns number.
int firstChar = richTextBox.GetFirstCharIndexFromLine(li);
int col = index - firstChar;
Good luck!
This will do it, you just have to call the code inside a timer:
int line = 1 + richTextBox1.GetLineFromCharIndex(richTextBox1.GetFirstCharIndexOfCurrentLine());
int column = 1 + richTextBox1.SelectionStart - richTextBox1.GetFirstCharIndexOfCurrentLine();
label1.Text = "line: " + line.ToString() + ", column: " + column.ToString();

How can I align text in columns using Console.WriteLine?

I have a sort of column display, but the end two column's seem to not be aligning correctly. This is the code I have at the moment:
Console.WriteLine("Customer name "
+ "sales "
+ "fee to be paid "
+ "70% value "
+ "30% value");
for (int DisplayPos = 0; DisplayPos < LineNum; DisplayPos = DisplayPos + 1)
{
seventy_percent_value = ((fee_payable[DisplayPos] / 10.0) * 7);
thirty_percent_value = ((fee_payable[DisplayPos] / 10.0) * 3);
Console.WriteLine(customer[DisplayPos] + " "
+ sales_figures[DisplayPos] + " "
+ fee_payable[DisplayPos] + " "
+ seventy_percent_value + " "
+ thirty_percent_value);
}
Try this
Console.WriteLine("{0,10}{1,10}{2,10}{3,10}{4,10}",
customer[DisplayPos],
sales_figures[DisplayPos],
fee_payable[DisplayPos],
seventy_percent_value,
thirty_percent_value);
where the first number inside the curly brackets is the index and the second is the alignment. The sign of the second number indicates if the string should be left or right aligned. Use negative numbers for left alignment.
Or look at
http://msdn.microsoft.com/en-us/library/aa331875(v=vs.71).aspx
Just to add to roya's answer. In c# 6.0 you can now use string interpolation:
Console.WriteLine($"{customer[DisplayPos],10}" +
$"{salesFigures[DisplayPos],10}" +
$"{feePayable[DisplayPos],10}" +
$"{seventyPercentValue,10}" +
$"{thirtyPercentValue,10}");
This can actually be one line without all the extra dollars, I just think it makes it a bit easier to read like this.
And you could also use a static import on System.Console, allowing you to do this:
using static System.Console;
WriteLine(/* write stuff */);
Instead of trying to manually align the text into columns with arbitrary strings of spaces, you should embed actual tabs (the \t escape sequence) into each output string:
Console.WriteLine("Customer name" + "\t"
+ "sales" + "\t"
+ "fee to be paid" + "\t"
+ "70% value" + "\t"
+ "30% value");
for (int DisplayPos = 0; DisplayPos < LineNum; DisplayPos++)
{
seventy_percent_value = ((fee_payable[DisplayPos] / 10.0) * 7);
thirty_percent_value = ((fee_payable[DisplayPos] / 10.0) * 3);
Console.WriteLine(customer[DisplayPos] + "\t"
+ sales_figures[DisplayPos] + "\t"
+ fee_payable + "\t\t"
+ seventy_percent_value + "\t\t"
+ thirty_percent_value);
}
I know, very old thread but the proposed solution was not fully automatic when there are longer strings around.
I therefore created a small helper method which does it fully automatic. Just pass in a list of string array where each array represents a line and each element in the array, well an element of the line.
The method can be used like this:
var lines = new List<string[]>();
lines.Add(new[] { "What", "Before", "After"});
lines.Add(new[] { "Name:", name1, name2});
lines.Add(new[] { "City:", city1, city2});
lines.Add(new[] { "Zip:", zip1, zip2});
lines.Add(new[] { "Street:", street1, street2});
var output = ConsoleUtility.PadElementsInLines(lines, 3);
The helper method is as follows:
public static class ConsoleUtility
{
/// <summary>
/// Converts a List of string arrays to a string where each element in each line is correctly padded.
/// Make sure that each array contains the same amount of elements!
/// - Example without:
/// Title Name Street
/// Mr. Roman Sesamstreet
/// Mrs. Claudia Abbey Road
/// - Example with:
/// Title Name Street
/// Mr. Roman Sesamstreet
/// Mrs. Claudia Abbey Road
/// <param name="lines">List lines, where each line is an array of elements for that line.</param>
/// <param name="padding">Additional padding between each element (default = 1)</param>
/// </summary>
public static string PadElementsInLines(List<string[]> lines, int padding = 1)
{
// Calculate maximum numbers for each element accross all lines
var numElements = lines[0].Length;
var maxValues = new int[numElements];
for (int i = 0; i < numElements; i++)
{
maxValues[i] = lines.Max(x => x[i].Length) + padding;
}
var sb = new StringBuilder();
// Build the output
bool isFirst = true;
foreach (var line in lines)
{
if (!isFirst)
{
sb.AppendLine();
}
isFirst = false;
for (int i = 0; i < line.Length; i++)
{
var value = line[i];
// Append the value with padding of the maximum length of any value for this element
sb.Append(value.PadRight(maxValues[i]));
}
}
return sb.ToString();
}
}
Hope this helps someone. The source is from a post in my blog here: http://dev.flauschig.ch/wordpress/?p=387
There're several NuGet packages which can help with formatting. In some cases the capabilities of string.Format are enough, but you may want to auto-size columns based on content, at least.
ConsoleTableExt
ConsoleTableExt is a simple library which allows formatting tables, including tables without grid lines. (A more popular package ConsoleTables doesn't seem to support borderless tables.) Here's an example of formatting a list of objects with columns sized based on their content:
ConsoleTableBuilder
.From(orders
.Select(o => new object[] {
o.CustomerName,
o.Sales,
o.Fee,
o.Value70,
o.Value30
})
.ToList())
.WithColumn(
"Customer",
"Sales",
"Fee",
"70% value",
"30% value")
.WithFormat(ConsoleTableBuilderFormat.Minimal)
.WithOptions(new ConsoleTableBuilderOption { DividerString = "" })
.ExportAndWriteLine();
CsConsoleFormat
If you need more features than that, any console formatting can be achieved with CsConsoleFormat.† For example, here's formatting of a list of objects as a grid with fixed column width of 10, like in the other answers using string.Format:
ConsoleRenderer.RenderDocument(
new Document { Color = ConsoleColor.Gray }
.AddChildren(
new Grid { Stroke = LineThickness.None }
.AddColumns(10, 10, 10, 10, 10)
.AddChildren(
new Div("Customer"),
new Div("Sales"),
new Div("Fee"),
new Div("70% value"),
new Div("30% value"),
orders.Select(o => new object[] {
new Div().AddChildren(o.CustomerName),
new Div().AddChildren(o.Sales),
new Div().AddChildren(o.Fee),
new Div().AddChildren(o.Value70),
new Div().AddChildren(o.Value30)
})
)
));
It may look more complicated than pure string.Format, but now it can be customized. For example:
If you want to auto-size columns based on content, replace AddColumns(10, 10, 10, 10, 10) with AddColumns(-1, -1, -1, -1, -1) (-1 is a shortcut to GridLength.Auto, you have more sizing options, including percentage of console window's width).
If you want to align number columns to the right, add { Align = Right } to a cell's initializer.
If you want to color a column, add { Color = Yellow } to a cell's initializer.
You can change border styles and more.
† CsConsoleFormat was developed by me.
You could use tabs instead of spaces between columns, and/or set maximum size for a column in format strings ...
Do some padding, i.e.
public static void prn(string fname, string fvalue)
{
string outstring = fname.PadRight(20) +"\t\t " + fvalue;
Console.WriteLine(outstring);
}
This worked well, at least for me.
The alignment can be combined with string interpolation by putting it in front of the ':' format character.
Console.WriteLine($"{name,40} {MaterialArea,10:N2}m² {MaterialWeightInLbs,10:N0}lbs {Cost,10:C2}");
I really like those libraries mentioned here but I had an idea that could be simpler than just padding or doing tons of string manipulations,
You could just manually set your cursor using the maximum string length of your data. Here's some code to get the idea (not tested):
var column1[] = {"test", "longer test", "etc"}
var column2[] = {"data", "more data", "etc"}
var offset = strings.OrderByDescending(s => s.Length).First().Length;
for (var i = 0; i < column.Length; i++) {
Console.Write(column[i]);
Console.CursorLeft = offset + 1;
Console.WriteLine(column2[i]);
}
you could easily extrapolate if you have more rows.

Categories

Resources