Reputation: 332
What I want to do is find groups of consecutive numbers(Excel row numbers) in a List
and for each chunk of consecutive numbers, delete the rows en masse, rather than one at at time since at times I will be iterating through up to 9K rows and growing. The issue that I am running into is the way I have tweaked the foreach
loop, I would need to reuse the last non-consecutive variable that was checked.
Ex: The list is rows {23,22,21,17,16,15} i would need to pull 23-21, then 17-15, out and delete the chunks (that's why they are in descending order, work from the bottom up). The loop enters the != if
statement on 17, and works, but then 17 is already used and 16 is the next iteration of the loop, so 17 is never captured as the start of the next consecutively numbered grouping.
My question: Is there a way to hold on to the 17, and any other start of a new consecutive group, in this manner or am I barking up the wrong tree?
Code:
public void FindMatchingBlocks(string stateId, string[] rangeNames)
{
Excel.Worksheet wksht = wkbk.Sheets["Sheet1"];
Excel.Range rng = wksht.Range["$A$15:$A$23"];
string val;
string val2;
List<int>rowNums = new List<int>();
string rngStart = rangeNames[0].ToString(); //gives me "$A$15"
string rngEnd = rangeNames[1].ToString();//gives me $A$23$
string[] tempArray = rngEnd.Split('$');
string end = tempArray[2].ToString();
List<int> rowsToDelete = new List<int>();
foreach (Excel.Range range in rng)
{
if (range.Row < Convert.ToInt32(end)+1)
{
//pulls out the first two characters of the cell value to
// match it to the stateID, if they match they are not to
// be added to the list and not be deleted.
val = range.Value.ToString();
val2 = val.Substring(0, 2);
if (Convert.ToInt32(val2) != Convert.ToInt32(stateId))
{
rowsToDelete.Add(range.Row); // ends up being
// {23,22,21,17,16,15}
}
}
}
int count = 0;
int firstItem = 0;
rowsToDelete.Reverse(); //delete from the bottom up
foreach (int x in rowsToDelete)
{
// First value in the ordered list: start of a sequence
if (count == 0)
{
firstItem = x;
count = 1;
}
// Skip duplicate values
else if (x == firstItem - count)
{
count++;
}
// New value contributes to sequence
else if (x != firstItem - count)
{
int endRow = firstItem;
int startRow = firstItem - count + 1;
Excel.Range delRange = wksht.Rows[startRow.ToString() + ":" + endRow.ToString()];
delRange.Delete(Excel.XlDeleteShiftDirection.xlShiftUp);
count = 0;
firstItem = ????; //can I do something to keep the first
//non-consecutive number each time it is
// encountered. In this list it skips 17
}
}
}
Hopefully this is clear, took me a bit to figure out how to concisely explain what I need. Thanks.
Upvotes: 2
Views: 1209
Reputation: 332
It took some time but I was able to come up with a compact way to take a list of numbers find the consecutive numbers and group them into a List. Hopefully if someone finds this and its useful:
private void groupConsecutiveNumbers()
{
/* this could easily be changed to look for ascending numbered groups by switching some of the "-1" to "+1"
* and swapping the firstNum/endNum variables. */
int[] numArray = new int[]{ 50, 23, 22, 21, 15, 16, 14, 9, 5, 4, 3, 1};
int firstNum = 0;
int endNum = 0;
string grouping;
for (int i = 0; i < numArray.Length; i++)
{
//If there is only 1 member of the list, that will be the first and last member of the group
if (numArray.Length == 1)
{
firstNum = numArray[0];
endNum = numArray[0];
grouping = firstNum.ToString() + "-" + endNum.ToString();
lstGroups.Items.Add(grouping);
}
//if the number is the first one in the list then it automatically is the first one in the first list
else if (i == 0)
{
firstNum = numArray[0];
}
/* if its not the first one in the list and it is equal to the previous list item minus one
* (contiguously descending), then enter this loop */
else if (numArray[i] == (numArray[i-1] - 1))
{
//if this is the last member of the list, it automatically is the last item in the range
if ((i + 1) == numArray.Length)
{
endNum = numArray[i];
grouping = firstNum.ToString() + "-" + endNum.ToString();
lstGroups.Items.Add(grouping);
}
//if this isn't the last member of the list, exit the loop and continue with the next item.
else
{
continue;
}
}
/* if the item if its not the first one in the list and does NOT equal the last item minus one
* (not contiguously descending) then the previous item was the last contiguously descending
* item and the current item is the first item in the next group */
else if (numArray[i] != (numArray[i-1]-1))
{
endNum = numArray[i - 1];
grouping = firstNum.ToString() + "-" + endNum.ToString();
lstGroups.Items.Add(grouping);
firstNum = numArray[i];
endNum = 0;
}
/* After all that testing,if the item is the last item in the list AND the first number in the group
* is also the last item in the list then the current item in the list is both the first and last member
* in the current group. */
if ((i + 1) == numArray.Length && firstNum == numArray[i])
{
endNum = numArray[i];
grouping = firstNum.ToString() + "-" + endNum.ToString();
lstGroups.Items.Add(grouping);
}
}
}
Upvotes: 0
Reputation: 660088
What do we have? A sequence of integers.
What do we want? A sequence of integer ranges.
Start by representing that in the type system. We have IEnumerable<int>
for a sequence of integers. Let's make a little type: (using C# 6 notation here)
struct MyRange
{
public int High { get; }
public int Low { get; }
public MyRange(int high, int low) : this()
{
High = high;
Low = low;
}
}
Easy. What is the signature of our method? We want integers in and ranges out, so:
static class MyExtensions
{
public static IEnumerable<MyRange> DescendingChunks(this IEnumerable<int> items)
Seems reasonable. Now what does this thing do? There are three cases. Either we've got no range at all because we're the first, or we're extending the current range, or we've got a new range. So one case for each:
{
bool first = true;
int high = 0;
int low = 0;
foreach(int item in items)
{
if (first)
{
high = item;
low = item;
first = false;
}
else if (item == low - 1)
{
low = item;
}
else
{
yield return new MyRange(high, low);
high = item;
low = item;
}
}
And we never yielded the last thing in the sequence...
yield return new MyRange(high, low);
}
Make sense? Now instead of your loop
foreach (int x in rowsToDelete)
we have
foreach(MyRange range in rowsToDelete.DescendingChunks())
and now you know what range to modify.
Super bonus question: there is another case I did not enumerate, and as a result there is a small bug in this method. What is it?
Upvotes: 8