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.
Related
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)
I'm using MathNet Symbolics to handle the symbolic algebra portion of a program I'm working on. The general use is create a pair of symbolic formulas, and then divide those two formulas. This works quite well most of the time. However, sometimes, it does not want to do more complex simplification. For example:
(512*r*t*w + 2048*r*t^2*w)
-----------------------------------------------------------------------
(512*r*t*w + 512*r^2*t*w + 3072*r*t^2*w + 3072*r^2*t^2*w + 1024*r*t^3*w)
With some work, I've been able to have it eliminate w from the equation, as it is in all terms top and bottom:
(512*r*t + 2048*r*t^2)
--------------------------------------------------------------
(512*r*t + 512*r^2*t + 3072*r*t^2 + 3072*r^2*t^2 + 1024*r*t^3)
However, I cannot figure out how to make it find common terms:
(512*r*t)*(1 + 4*t)
--------------------------------------
(512*r*t)(1 + r + 6*t + 6*r*t + 2*t^2)
And eliminate these terms:
(1 + 4*t)
-----------------------------
(1 + r + 6*t + 6*r*t + 2*t^2)
I've been using Wolfram Alpha as my gold standard for checking my work. The code from LinqPad I've been working on most of the afternoon, that gets my the elimination of w:
var h1 = MathNet.Symbolics.Infix.ParseOrUndefined("(1/8)*r*t*w + (1/2)*r*t^2*w");
var h2 = MathNet.Symbolics.Infix.ParseOrUndefined("(1/8)*r*t*w + (1/8)*r^2*t*w + (3/4)*r*t^2*w + (3/4)*r^2*t^2*w + (1/4)*r*t^3*w");
Infix.Print(Rational.Expand(h1/h2)).Dump(); //Prints (512*r*t*w + 2048*r*t^2*w)/(512*r*t*w + 512*r^2*t*w + 3072*r*t^2*w + 3072*r^2*t^2*w + 1024*r*t^3*w)
var tot = Rational.Expand(h1 / h2);
var simplified = true;
do
{
simplified=false;
foreach (var v in Rational.Variables(tot))
{
var result = Polynomial.Divide(v, h1, h2);
if (!result.Item1.Equals(MathNet.Symbolics.Expression.Zero))
{
simplified = true;
tot = result.Item1;
break;
}
}
}while(simplified);
tot = Rational.Expand(tot);
Infix.Print(tot).Dump(); //Prints (512*r*t + 2048*r*t^2)/(512*r*t + 512*r^2*t + 3072*r*t^2 + 3072*r^2*t^2 + 1024*r*t^3)
Can someone give me pointers to how to proceed with MathNet? I've tried various combinations of functions from Rational and Polynomial, and have not been able to move past this point.
I've just published a new Math.NET Symbolics release v0.6.0 which includes a new Rational.Reduce routine that removes such common simple factors (also executed as part of Rational.Expand):
var h1 = Infix.ParseOrThrow("(1/8)*r*t*w + (1/2)*r*t^2*w");
var h2 = Infix.ParseOrThrow("(1/8)*r*t*w + (1/8)*r^2*t*w + (3/4)*r*t^2*w + (3/4)*r^2*t^2*w + (1/4)*r*t^3*w");
var q1 = h1/h2;
Infix.Print(q1);
// returns: ((1/8)*r*t*w + (1/2)*r*t^2*w)/((1/8)*r*t*w + (1/8)*r^2*t*w + (3/4)*r*t^2*w + (3/4)*r^2*t^2*w + (1/4)*r*t^3*w)
var q2 = Rational.Expand(q1);
Infix.Print(q2);
// returns: (1 + 4*t)/(1 + r + 6*t + 6*r*t + 2*t^2)
Unfortunately quite a few of the univariate polynomial and rational routines like the new square-free factorization do not have a multivariate counterpart yet. Univariate routines expect one symbol parameter, while multivariate ones expect a symbol set.
I'm working on a project where I need to develop a fractional calculator.
I want to add something extra to it, such as having a working out box that displays how the user got their answer.
For example, say I enter 1 1/2 + 1 1/2 into the calculate and press "Calculate", it would then display the answer and how it got the answer such as:
= 3/2 + 3/2
= ((3 × 2) + (3 × 2)) / (2 × 2)
= (6 + 6) / 4
= 12/4
= 3/1
= 3
Here is my basic, crappy looking, program: http://gyazo.com/1dc27b531873c48cdb198baa40b3af9a
I want it to display the working out in the 'Calculations' box below.
How am I able to do this?
Your question is missing some details, so here is a simple answer to begin with.
(I guess your GUI is winforms, and you have a trigger for the calculate OnClick)
To add text into your "Calculations" textbox the code you need is (for your example):
private void calculateButton_Click(object sender, EventArgs e)
{
int common_den = denumerator1*denumerator2;
int new_enumerator1 = enumerator1 * denumerator2;
int new_enumerator2 = enumerator2 * denumerator1;
string myCalculations = " = " + enumerator1.ToString() + "/" + denumerator1.ToString() + " + " + enumerator2.ToString() + "/" + denumerator2.ToString();
string myCalculations += " = " + new_enumerator1.ToString() + "/" + common_den.ToString() + " + " + new_enumerator2.ToString() + "/" + common_den.ToString();
string myCalculations += " = " + (new_enumerator1 + new_enumerator2).ToString() + "/" + common_den.ToString();
calculationsTextBox.Text = myCalculations;
}
If you are looking for the code that will build the calculation string - you need to ask for it explicitly
I search in a text for some strings and want to remove the first and last char in those strings.
Example :
...
...
OK 125 ab_D9 "can be "this" or; can not be "this" ";
...
OK 673 e_IO1_ "hello; is strong
or maybe not strong";
...
So I use the code to find all strings begin with OK and remove from the 4 groups "...":
tmp = fin.ReadToEnd();
var matches = Regex.Matches(tmp, "(OK) ([0-9]+) ([A-Za-z_0-9]+) (\"(?:(?!\";).)*\");", RegexOptions.Singleline);
for (int i = 0; i < matches.Count; i++)
{
matches[i].Groups[4].Value.Remove(0);
matches[i].Groups[4].Value.Remove(matches[i].Groups[4].Value.ToString().Length - 1);
Console.WriteLine(matches[i].Groups[1].Value + "\r\n" + "\r\n" + "\r\n" + matches[i].Groups[2].Value + "\r\n" + "\r\n" + matches[i].Groups[3].Value + "\r\n" + "\r\n" + "\r\n" + matches[i].Groups[4].Value);
Console.WriteLine(" ");
}
But it doesn't remove first and last char from Group 4. What did I do wrong?
My Result should be:
OK
125
ab_D9
can be "this" or; can not be "this"
OK
673
e_IO1
hello; is strong
or maybe not strong
There is no need to remove things. Just don't capture the quotes in the first place. So move the parentheses one character inward.
"(OK) ([0-9]+) ([A-Za-z_0-9]+) \"((?:(?!\";).)*)\";"
You should assign the result of Substring() and Remove() methods. they do not change the existing string but return the changed string which you need to assign to the same or some other string variable. Check the code:
tmp = fin.ReadToEnd();
var matches = Regex.Matches(tmp, "(OK) ([0-9]+) ([A-Za-z_0-9]+) (\"(?:(?!\";).)*\");", RegexOptions.Singleline);
for (int i = 0; i < matches.Count; i++)
{
string str = matches[i].Groups[4].Value.Substring(0);
str = str.Remove(str.Length - 1);
Console.WriteLine(matches[i].Groups[1].Value + "\r\n" + "\r\n" + "\r\n" + matches[i].Groups[2].Value + "\r\n" + "\r\n" + matches[i].Groups[3].Value + "\r\n" + "\r\n" + "\r\n" + str);
Console.WriteLine(" ");
}
P.S. You should use Environment.NewLine instead of "\r\n", it's the better approach.
I am cycling through the contents of a two-dimensional array containing the result of a Punnett Square calculation for gene crosses. I need to summarize the result so that the user can readily see the unique instances. I can accomplish this by putting the result into a text box, but when I try and use a ListBox to display the data, part of the information is getting lost, namely a translation of the AaBBCc type data to something that directly relates to the traits that the user initially selected.
This is the main block of code for the operation:
foreach (string strCombination in arrUniqueCombinations)
{
int intUniqueCount = 0;
decimal decPercentage;
foreach (string strCrossResult in arrPunnettSQ)
{
if (strCrossResult == strCombination)
{
intUniqueCount++;
}
}
decPercentage = Convert.ToDecimal((intUniqueCount*100)) / Convert.ToDecimal(intPossibleCombinations);
txtReport.AppendText(strCombination + " appears " + intUniqueCount.ToString() + " times or " + decPercentage.ToString() + "%."+ Environment.NewLine);
lstCrossResult.Items.Add(DecodeGenome(strCombination) + " appears " + intUniqueCount.ToString() + " times or " + decPercentage.ToString() + "%.");
}
For appending the data to the textbox I use this code and it works perfectly:
txtReport.AppendText(DecodeGenome(strCombination) + " appears " + intUniqueCount.ToString() + " times or " + decPercentage.ToString() + "%."+ Environment.NewLine);
Giving the result:
Trait 1 Het.,Trait 3 appears 16 times or 25%.
For adding the result to a list box, this works:
lstCrossResult.Items.Add(strCombination + " appears " + intUniqueCount.ToString() + " times or " + decPercentage.ToString() + "%.");
Giving the result:
AaBBCc appears 16 times or 25%.
But the contents of strCombination is AaBBCc and I need it translated to "Trait 1 Het.,Trait 3", which I accomplish with this bit of code:
private string DecodeGenome(string strGenome)
{
string strTranslation = "";
int intLength = strGenome.Length;
int intCounter = intLength / 2;
string[] arrPairs = new string[intLength / 2];
//Break out trait pairs and load into array
for (int i = 1; i <= intLength; i++)
{
arrPairs[i / 2] = strGenome.Substring((i-1),2);
i++;
}
foreach (string strPair in arrPairs)
{
char chFirstLetter = strPair[0];
char chSecondLetter = strPair[1];
intCounter = intCounter - 1;
if (Char.IsUpper(chFirstLetter))
{
if (!Char.IsUpper(chSecondLetter))
{
if (intCounter > 0)
{
txtReport.AppendText(GetDescription(strPair.Substring(0, 1)) + " Het.,");
}
else
{
txtReport.AppendText(GetDescription(strPair.Substring(0, 1)));
}
}
}
else
{
if (!Char.IsUpper(chSecondLetter))
{
if (intCounter > 0)
{
txtReport.AppendText(GetDescription(strPair.Substring(0, 1)) + ",");
}
else
{
txtReport.AppendText(GetDescription(strPair.Substring(0, 1)));
}
}
}
}
return strTranslation;
}
That has no problem displaying in a text box, but when I try and put it as an item into a list box it turns it into null. Instead of:
"Trait 1 Het.,Trait 3 appears 16 times or 25%."
I get:
" appears 16 times or 25%."
I have tried adding the results to an ArrayList, then populating the listbox after everything is processed, but the result is the same.
Any clues as to why the list box is not accepting the translated AaBBCc information would be greatly appreciated.
strTranslation is never set. Everything is pushed to txtReport.AppendText