Excel Interop Efficiency get row number with value in a column - c#

Currently I am using the below segement of code to get a row with a value int in the 3rd column.
private int getRowByRDS(int id)
{
int bestfit = -1;
Boolean foundOne = false;
for (int i = 2; i < oSheet.Rows.Count; i++)
{
string val = getValueOfCell(i, 3);
if (val == null)
continue;
int rds = int.Parse(val);
if (rds == id)
{
bestfit = i;
foundOne = true;
}
else
if (foundOne)
return bestfit;
}
return bestfit;
}
The issues is that this is pathetically show at large amount of rows.
Can someone suggest a better way of searching col 3 for a int and returning the last row # that it is in.
For Joe:
public void inputRowData(string[] data, int rds)
{
int bestRow = getRowByRDS_a(rds);
string[] formatedData = formatOutput(bestRow, data);
string val = getValueOfCell(bestRow, 6);
if (val != null)
{
shiftRows(bestRow, data.Length);
bestRow++;
}
else
shiftRows(bestRow, data.Length - 1);
// transform formated data into string[,]
string[][] splitedData = formatedData.Select(s => s.Split('\t')).ToArray();
var colCount = splitedData.Max(r => r.Length);
var excelData = new string[splitedData.Length, colCount];
for (int i = 0; i < splitedData.Length; i++)
{
for (int j = 0; j < splitedData[i].Length; j++)
{
excelData[i, j] = splitedData[i][j];
}
}
oSheet.get_Range("A" + bestRow.ToString()).Resize[splitedData.Length, colCount].Value = excelData;
MainWindow.mainWindowDispacter.BeginInvoke(new System.Action(() => MainWindow.mainWindow.debugTextBox.AppendText("Done with " + rds + " input!" + Environment.NewLine)));
}
private void shiftRows(int from, int numberof)
{
from++;
Range r = oXL.get_Range("A" + from.ToString(), "A" + from.ToString()).EntireRow;
for (int i = 0; i < numberof; i++)
r.Insert(Microsoft.Office.Interop.Excel.XlInsertShiftDirection.xlShiftDown);
}

Here's how I'd do it:
Get a Range corresponding to the column you're interested in
Get the UsedRange of the sheet you're interested in
Get a Range that is the intersection of the above two ranges
Get the value of this Range, which will be an array of values from the column you're interested in
You can then iterate through this array to find the value you want, then use its index to derive the row number.
The number of calls to the Excel is O(1) with the above method, as opposed to O(n) in your version.

You could also use the Find method of the Excel.Range object. This method returns a Excel.Range object if a match is found or null. Follow the first three steps that Joe described to create the search range. If the Find method returns a valid Range, you can then use it's Row Property.
P.S.: Sorry Joe I wanted to upvote your answer, but I am not allowed to do so yet.

Related

I Want to verify that Sum of 3 Row values is equal to the first row in on my WebTable using Selenium C#. The Rows are Row3, Row6 & Row8

IList<IWebElement> rows = _driver.FindElements(By.XPath("//div[#id='data_3']/div/div/div[3]/table/tbody/tr[#class]"));
for (int i = 1; i <= rows.Count; i++)
{
IList<IWebElement> columns = _driver.FindElements(By.XPath("//div[#id='data_3']/div/div/div[3]/table/tbody/tr[#class][" + i + "]/td[#class]"));
for (int j = 1; j <= columns.Count; j++)
{
}
}
From what I understand, you need to sum values from three rows and check if it is equal to the value from the first one. I will use the path you have provided.
public int GetRowValue(int rowNumber)
{
var row = _driver.FindElements(By.XPath("//div[#id='data_3']/div/div/div[3]/table/tbody/tr[#class]"))[rowNumber - 1] // decreased by 1, because if you would like to get first row, it would have 0th index in the array
var columns = row.FindElements(By.XPath("./td"); // find all tds of the given row.
int value = 0;
foreach(var column in columns)
{
if (String.IsNullOrEmpty(column.Text)) continue;
result += Int32.Parse(column.Text);
}
return value;
}
With that, you can easily check what you want.
var row1Value = GetRowValue(1);
var sum = GetRowValue(3);
sum += GetRowValue(6);
sum += GetRowValue(8)
Assert.Equals(row1Value, sum);

How to convert nested for loops with if condition to LINQ

I have a horrible method that extracts data from a DataTable and converts it to a desirable formatted DataTable. I'm sure there is a much nicer way to do this in LinQ but I'm not really experienced with it. I would appreciate if somebody could show me a nicer solution.
Heres the code
private static void ExtractImportLayoutFromExcelDt(DataTable importDt, DataTable dtExtracted, int languages)
{
// The number of Locale colums included in the excel file.
for (int x = 0; x < languages; x++)
{
// The total number of friendlynames-keys / language included in the excel.
for (int j = 0; j < dtExtracted.Rows.Count; j++)
{
var row = dtExtracted.Rows[j];
DataRow tempRow = importDt.NewRow();
// Filling in the 3 columns. (FriendlyName - LocaleID - Text)
for (int i = 0; i <= 2; i++)
{
if (i == 0)
{
tempRow[i] = row[i]; // Friendly names: This is always going to be column 1 [0].
}
else if (i == 1)
{
tempRow[i] = Regex.Match(dtExtracted.Columns[x + 1].ToString(), #"\d+").Value; // LocaleIDs: Getting rid of non numeric characters from this column.
}
else
{
tempRow[i] = row[x + 1];
}
}
importDt.Rows.Add(tempRow);
}
}
}
i would rewrite inner for loops
for (int x = 0; x < languages; x++)
foreach (DataRow row in dtExtracted.Rows)
importDt.Rows.Add
(
row[0],
Regex.Match(dtExtracted.Columns[x + 1].ToString(), #"\d+").Value; // LocaleIDs: Getting rid of non numeric characters from this column.
row[x + 1]
);
DataTable.Rows collection has overload of Add method, which accept an array of objects: Add
I am not quite sure if LINQ could be of any help here, but apart from rewriting the entire mapping logic I would at least split this method into two:
private static void ExtractImportLayoutFromExcelDt(DataTable importDt, DataTable dtExtracted, int languages)
{
// The number of Locale colums included in the excel file.
for (int x = 0; x < languages; x++)
{
// The total number of friendlynames-keys / language included in the excel.
for (int j = 0; j < dtExtracted.Rows.Count; j++)
{
AddRow(importDt, dtExtracted, dtExtracted.Rows[j], x+1);
}
}
}
private static void AddRow(DataTable table, DataTable dtExtracted, DataRow originalRow, int language)
{
var row = table.NewRow();
row[0] = originalRow[0];
row[1] = Regex.Match(dtExtracted.Columns[language].ToString(), #"\d+").Value;
row[2] = originalRow[language];
table.Rows.Add(row);
}
You can write something like this but in this case is more like abuse of Linq but if you do it for educational purposes ..The main power of LINQ is when you want to enumerate or filter for example collections not in cases like this.
public static void ExtractImportLayoutFromExcelDt(DataTable importDt, DataTable dtExtracted, int languages)
{
Enumerable.Range(0, languages)
.ToList().ForEach(x =>
{
Enumerable.Range(0, dtExtracted.Rows.Count)
.ToList().ForEach(j =>
{
var row = dtExtracted.Rows[j];
DataRow tempRow = importDt.NewRow();
AddRow(importDt, dtExtracted, x, row, tempRow);
});
});
}
private static void AddRow(DataTable importDt, DataTable dtExtracted, int x, DataRow row, DataRow tempRow)
{
for (int i = 0; i <= 2; i++)
{
if (i == 0)
{
tempRow[i] = row[i]; // Friendly names: This is always going to be column 1 [0].
}
else if (i == 1)
{
tempRow[i] = Regex.Match(dtExtracted.Columns[x + 1].ToString(), #"\d+").Value; // LocaleIDs: Getting rid of non numeric characters from this column.
}
else
{
tempRow[i] = row[x + 1];
}
}
importDt.Rows.Add(tempRow);
}

Excel data entry from List<[]> is slow, is there a better algorithm design for this?

I have an algorithm that takes a list of arrays and enters them into an Excel file, however it is very slow. Is there a better design for this algorithm?
public void WriteToExcel(List<string[]> parsedData, string path, string fileName)
{
// Get the Excel application object.
Excel.Application xlApp = new Excel.Application();
// Make Excel visible.
xlApp.Visible = true;
Excel.Workbook workbook = xlApp.Workbooks.Add(Excel.XlWBATemplate.xlWBATWorksheet);
Excel.Worksheet sheet = (Excel.Worksheet)xlApp.Worksheets[1];
sheet.Select(Type.Missing);
//Loop through arrays in parsedData list.
for (var lstElement=0;lstElement<parsedData.Count;lstElement++)
{
//Loop through array.
for(var arryElement = 0; arryElement<parsedData[lstElement].Count(); arryElement++)
{
sheet.Cells[lstElement + 1, arryElement + 1] = parsedData[lstElement][arryElement];
}
}
// Save the changes and close the workbook.
workbook.Close(true, fileName, Type.Missing);
// Close the Excel server.
xlApp.Quit();
}
When working with Office interop, the slowest part are the inter process calls which happen anytime you access some property or method of the automation class/interface.
So the optimization goal should be to minimize the roundtrips (inter process calls).
In your particular use case, instead of setting values cell by cell (i.e. doing a lot of calls), there is fortunately a way to set values of a whole Excel range with one call by passing array of values. Depending of how many columns contains your data, the following modification should give you a significant speedup.
The significant part:
//Loop through arrays in parsedData list.
int row = 1, column = 1;
object[] values = null; // buffer - see below. Avoids unnecessary allocations.
for (var lstElement = 0; lstElement < parsedData.Count; lstElement++)
{
var data = parsedData[lstElement];
if (data == null || data.Length == 0) continue;
if (data.Length == 1)
{
// Single cell
sheet.Cells[row, column] = data[0];
}
else
{
// Cell range
var range = sheet.Range[CellName(row, column), CellName(row, column + data.Length - 1)];
// We can pass the data array directly, but since it's a string[], Excel will treat them as text.
// The trick is to to pass them via object[].
if (values == null || values.Length != data.Length)
values = new object[data.Length];
for (int i = 0; i < data.Length; i++)
values[i] = data[i];
// Set all values in a single roundtrip
range.Value2 = values;
}
row++;
}
Helpers used:
static string CellName(int row, int column)
{
return ColumnName(column) + row;
}
static string ColumnName(int column)
{
const int StartLetter = 'A', EndLetter = 'Z', LetterCount = EndLetter - StartLetter + 1;
int index = column - 1;
var letter = (char)(StartLetter + (index % LetterCount));
if (index < LetterCount) return letter.ToString();
var firstLetter = (char)(StartLetter + index / LetterCount - 1);
return new string(new [] { firstLetter, letter });
}
Once you get the idea, you can get even better performance by extending the above to handle multi row ranges like this (the most important thing in this case is to use 2d array for values):
const int MaxCells = 1 * 1024 * 1024; // Arbitrary
var maxColumns = parsedData.Max(data => data.Length);
var maxRows = Math.Min(parsedData.Count, MaxCells / maxColumns);
object[,] values = null;
int row = 1, column = 1;
for (int lstElement = 0; lstElement < parsedData.Count; )
{
int rowCount = Math.Min(maxRows, parsedData.Count - lstElement);
if (values == null || values.GetLength(0) != rowCount)
values = new object[rowCount, maxColumns];
for (int r = 0; r < rowCount; r++)
{
var data = parsedData[lstElement++];
for (int c = 0; c < data.Length; c++)
values[r, c] = data[c];
}
var range = sheet.Range[CellName(row, column), CellName(row + rowCount - 1, column + maxColumns - 1)];
range.Value2 = values;
row += rowCount;
}

Excel Interop - insert & add data by row

Hey guys I need to insert empty rows below a certain row in an excel and then add data into those empty rows I inserted...
So far I am able to create empty rows but I am having a hell of a time trying to figure out how to set Range.Value to an array of type String
Method for inserting Rows:
private void shiftRows(int from, int numberof)
{
from++;
Range r = oXL.get_Range("A" + from.ToString(), "A" + from.ToString()).EntireRow;
for (int i = 0; i < numberof; i++)
r.Insert(Microsoft.Office.Interop.Excel.XlInsertShiftDirection.xlShiftDown);
}
// so this would shift the below rows by numberof times.
This method is currently what I am stuck on... which is inserting an array into the new rows one row at a time
public void inputRowData(string[] data, int rds)
{
int bestRow = getRowByRDS(rds);
string val = getValueOfCell(bestRow, 6);
if (val == null || val.Equals(""))
{
shiftRows(bestRow, data.Length);
string[] formatedData = formatOutput(bestRow, data);
for (int i = 0; i < formatedData.Length; i++)
{
Range r = oSheet.get_Range((bestRow + i).ToString() + ":" + (bestRow + i).ToString());
r.set_Value(formatedData[i].Split('\t'));
// have tried r.Value = formatedData[i].Split('\t')
// formatedData is an array of string which contains data for each cell seperated by a tab
}
}
else
{
Console.WriteLine("Line has some information already, skipping 1 more");
shiftRows(bestRow, data.Length + 1);
}
}
I strongly advise you:
NOT to insert rows but just write empty row instead (safety and performance)
to set a big array object and do only ONE write in excel (performance)
example (i kept the shiftrows but you should really get rid of it):
public void inputRowData(string[] data, int rds)
{
int bestRow = getRowByRDS(rds);
string val = getValueOfCell(bestRow, 6);
if (val == null || val.Equals(""))
{
shiftRows(bestRow, data.Length);
string[] formatedData = formatOutput(bestRow, data);
// transform formated data into string[,]
var string[][] splitedData = formatedData.Select(s => s.Split('\t')).ToArray();
var colCount = splitedData.Max(r => r.Lenght);
var excelData = new string[splitedData.Length, colCount]
for (int i = 0; i < splitedData.Length; i++)
{
for (int j = 0; j < splitedData[i].Length; j++)
{
excelData[i,j] = splitedData[i][j];
}
}
oSheet.get_Range("A" + bestRow.ToString()).Resize(splitedData.Length, colCount).Value = excelData;
}
else
{
Console.WriteLine("Line has some information already, skipping 1 more");
shiftRows(bestRow, data.Length + 1);
}
}

Method that Searches 2-D array for a specific number and will return a Boolean value of true if found

I am writing a program that creats a two-dimensional array fills it with random numbers and then prompts the user to enter a number and searches the 2-d array for that number.
I have the entire program completed beside the last method which I am lost on.
I am supposed to have this method return a bool to indicate if the sought out number was found or not. I am supposed to initialize the row and column parameters to -1 and have this method to use first parameter and the 2-d array parameter to search the array for the number. If the number is found I am to assign the row and column parameters to the row and column index where it is found and stop searching the array right away.
Any advice on the searchArray() method is greatly appreciated. Thank you!
Here is the code filled with errors in the last method that I have so far:
static void Main(string[] args)
{
int [,] randomNumArray = new int[3, 5];
FillArray(randomNumArray);
PrintArray(randomNumArray);
SumRows(randomNumArray);
SumCols(randomNumArray);
SumArray(randomNumArray);
GetNumber();
}
public static void FillArray(int[,] randomNumbersArray)
{
Random num = new Random();
for (int r = 0; r < randomNumbersArray.GetLength(0); r++)
{
for (int c = 0; c < randomNumbersArray.GetLength(1); c++)
{
randomNumbersArray[r, c] = num.Next(15, 97);
}
}
}
public static void PrintArray(int[,] randomPrintArray)
{
for (int r = 0; r < randomPrintArray.GetLength(0); r++)
{
for (int c = 0; c < randomPrintArray.GetLength(1); c++)
{
Console.Write("{0,3:F0}", randomPrintArray[r, c]);
}
Console.WriteLine("");
}
Console.WriteLine("");
}
public static void SumRows(int[,] sumOfRowsArray)
{
int rowSum;
for (int r = 0; r < sumOfRowsArray.GetLength(0); r++)
{
rowSum = 0;
for (int c = 0; c < sumOfRowsArray.GetLength(1); c++)
{
rowSum += sumOfRowsArray[r, c];
}
Console.WriteLine("The total sum for row "+ (r + 1) + " is: " + rowSum + ".");
}
Console.WriteLine("");
}
public static void SumCols(int[,] sumOfColsArray)
{
int colsSum;
for (int c = 0; c < sumOfColsArray.GetLength(1); c++)
{
colsSum = 0;
for (int r = 0; r < sumOfColsArray.GetLength(0); r++)
{
colsSum += sumOfColsArray[r, c];
}
Console.WriteLine("The total sum for column " + (c + 1) + " is: " + colsSum + ".");
}
Console.WriteLine("");
}
public static void SumArray(int[,] sumOfAllArray)
{
int sumOfAll = 0;
for (int r = 0; r < sumOfAllArray.GetLength(0); r++)
{
for (int c = 0; c < sumOfAllArray.GetLength(1); c++)
{
sumOfAll += sumOfAllArray[r, c];
}
}
Console.WriteLine("Total for sum of the Array is: " + sumOfAll + "\n");
}
public static int GetNumber()
{
Console.Write("Please enter a number between 15 and 96: ");
int chosenNumber = int.Parse(Console.ReadLine());
while (chosenNumber > 96 || chosenNumber < 15)
{
Console.Write("Number not between 15 and 96. Try again: ");
chosenNumber = int.Parse(Console.ReadLine());
}
return chosenNumber;
}
public static bool SearchArray(int soughtOutNum, int [,] searchableArray, out int rowIndex, out int colsIndex)
{
bool itsTrue == false;
for (int c = 0; c < searchableArray.GetLength(0); c++)
{
for (int r = 0; r < searchableArray.GetLength(1); r++)
{
if (searchableArray[r, c] == soughtOutNum)
{
return itsTrue == true;
break;
}
}
}
Console.WriteLine("");
}
}
}
Your code has four problems that I can see.
First, two of your parameters are marked as out parameters, but you haven't filled in a value for them. Think of out parameters like multiple return values: instead of the caller passing your function something, you're passing it back out. Since the person calling this function is probably going to rely on the values in those, you have to explicitly give them a value. This is probably giving you compiler errors.
I would start by assigning -1 to each of the two out parameters at the beginning of your function, and when you find the number you're searching for (that inner if statement), overwrite those -1's with the real answer (hint: how do you know what column and row you're on?)
Second, your use if itsTrue is a bit odd. In C# (and many other languages), we use a single = for assignment, and a double == for comparison. If you fix that, it should work, and give you the right answer. However, from a code clarity standpoint, why do you need the variable at all? It's never used except as a return value... why don't you just return true directly when you find the number you're looking for.
Third, your method isn't guaranteed to return a value: what happens if it never finds a number? Eventually, you're going to fall outside of both for loops, and you'll reach that Console.WriteLine(""); you have at the bottom... but what do you return in that case?
Finally, when you call the method, you need to let C# know where to stick the values of those out parameters. Right now, when you try to call SearchNumber(10), it can't find a method with just ONE parameter. There's only one with 3. You should instead declare variables where you can store the row and column, and then pass those in, like so:
int row, col;
SearchNumber(10, out row, out col);
I think you are not so far away from the solution if I understood you correctly. Are you looking for something like this?
public static bool SearchArray(int soughtOutNum, int[,] searchableArray, out int rowIndex, out int colsIndex)
{
rowIndex = -1;
colsIndex = -1;
// Assuming your c is column and r is row
for (int c = 0; c < searchableArray.GetLength(0); c++)
{
for (int r = 0; r < searchableArray.GetLength(1); r++)
{
if (searchableArray[r, c] == soughtOutNum)
{
rowIndex = r;
colsIndex = c;
//Console.WriteLine("found number");
return true;
}
}
}
//Console.WriteLine("Number not found");
return false;
}
Update to answer to the comment:
I coded it without a Visual Studio at hand so look out for typos.
static void Main(string[] args)
{
int [,] randomNumArray = new int[3, 5];
FillArray(randomNumArray);
PrintArray(randomNumArray);
SumRows(randomNumArray);
SumCols(randomNumArray);
SumArray(randomNumArray);
int row;
int column;
int search = GetNumber();
if (SearchArray(search, randomNumArray, out row, out column))
{
Console.WriteLine("found " + search + " at row " + row + " col " + column);
}
else
{
Console.WriteLine("Number not found");
}
}
Update 2:
There is also an error in the last method.
You have constructed your array like this: randomNumbersArray[row, column]
In all methods r is from GetLength(0) and c is GetLength(1). But in the last method you switch and your r is GetLength(1) and c is GetLength(0). So you switch the numbers when accessing the array and essentially call randomNumbersArray[column, row]. This will give you an error when c_max != r_max.

Categories

Resources