Help with converting foreach loop to while loop in c# - c#

I had learnt by reading your great answers here, that it is not good practice deleting items from within a foreach loop, as it is (and I quote) "Sawing off the branch you're sitting on".
My code currently removes the text from the dropdownlist, but the actual item remains (just without text displayed).
In other words, it isn't deleting, and probably can't because you can't delete from within a foreach loop.
After hours of trying I am unable to get my head around a way of doing it.
//For each checked box, run the delete code
for (int i = 0; i < this.organizeFav.CheckedItems.Count; i++)
{
//this is the foreach loop
foreach (ToolStripItem mItem in favoritesToolStripMenuItem.DropDownItems)
{
//This rules out seperators
if (mItem is ToolStripMenuItem)
{
ToolStripMenuItem menuItem = mItem as ToolStripMenuItem;
//This matches the dropdownitems text to the CheckedItems String
if (((ToolStripMenuItem)mItem).Text.ToString() == organizeFav.CheckedItems[i].ToString())
{
//And deletes the item
menuItem.DropDownItems.Remove(mItem);
}
}
}
}
But it isn't deleting because it is within a foreach loop!
I would greatly appreciate your help, and be truly amazed if anyone can get their head around this code :)
Kind Regards

Fun with LINQ!
// Loop through the checked items, same as you did.
foreach (var checkedItem in this.organizeFav.CheckedItems)
{
// Cast from IEnumerable to IEnumerable<T> so we can abuse LINQ
var matches = favoritesToolStripMenuItem.DropDownItems.Cast<ToolStripItem>()
// Only items that the Text match
.Where(item => item.Text == checkedItem.Text)
// Don't match separators
.Where(item => item is ToolStripMenuItem)
// Select the keys for the later .Remove call
.Select(item => item.Name);
// Loop through all matches
foreach (var key in matches)
{
// Remove them with the Remove(string key) overload.
favoritesToolStripMenuItem.Remove(key);
}
}

You don't need a foreach loop - just use a regular loop but go in reverse, start at the end and go to the beginning.
//For each checked box, run the delete code
for (int i = 0; i < this.organizeFav.CheckedItems.Count; i++)
{
//this *replaces* the foreach loop
for(int j = favoritesToolStripMenuItem.DropDownItems.Count - 1; j >= 0; j--)
{
ToolStripMenuItem menuItem = favoritesToolStripMenuItem.DropDownItems[j] as ToolStripMenuItem;
//This rules out seperators
if (menuItem != null)
{
//This matches the dropdownitems text to the CheckedItems String
if (menuItem.Text.ToString() == organizeFav.CheckedItems[i].ToString())
{
favoritesToolStripMenuItem.DropDownItems.Remove(menuItem);
}
}
}
}
this was #Kurresmack's code rearranged, i just coded it directly here in the page so excuse any small syntax error or anything obvious i overlooked (disclaimer: it is a sample!!)
You can still treat favoritesToolStripMenuItem.DropDownItems as a collection like you were, but you don't have to enumerate over it using a foreach. This cuts down on a few lines of code, and it works because you are iterating it in reverse order, you will not get an index out of bounds exception.

Try something like this:
//For each checked box, run the delete code
for (int i = 0; i < this.organizeFav.CheckedItems.Count; i++)
{
List<ToolStripItem> toRemove = new List<ToolStripItem>();
//this is the foreach loop
foreach (ToolStripItem mItem in favoritesToolStripMenuItem.DropDownItems)
{
//This rules out seperators
if (mItem is ToolStripMenuItem)
{
ToolStripMenuItem menuItem = mItem as ToolStripMenuItem;
//This matches the dropdownitems text to the CheckedItems String
if (((ToolStripMenuItem)mItem).Text.ToString() == organizeFav.CheckedItems[i].ToString())
{
toRemove.Add(mItem);
}
}
}
foreach(var item in toRemove)
{
favoritesToolStripMenuItem.DropDownItems.Remove(item);
}
}

To my mind, the way to make the code work is:
1. Create an instance of the type the favoritesToolStripMenuItem.DropDownItems collection is.
2. In the foreach loop, add all items, you do not want to be removed, to that collection.
3. Make favoritesToolStripMenuItem.DropDownItems to point to the new collection. Or clear favoritesToolStripMenuItem.DropDownItems and load the items from the new collection to it.
Hope this helps

Instead of a foreach use a reverse for-Loop:
for(int reverseIndex = myList.Count - 1; reverseIndex >= 0; reverseIndex--)
{
var currentItem = myList[reverseIndex];
if(MatchMyCondition(currentItem))
{
myList.Remove(currentItem);
}
}

Related

Way to delete row from list if criteria met

i am running two lists through a fuzzy matching program and want to be able to remove that row from the list if a match occurs but am having problems doing so. here is my code
foreach (var name in list)
{
foreach (var stepone in Step1)
{
if (FuzzyMatching(name.Full(), stepone.Full()) >= 90)
{
csvcontent.AppendLine(name.Full());
//find a way to delete list record out of list
//list.Remove(name);
}
}
}
you can mark in another list all the items to delete.
When the loop is finished you can delete all the items in the new created list from that list.
With LINQ is simply:
list1 = list1.Where(x => !itemToDeleteList.Contains(x)).ToList();
You can't remove an item from a list using a ForEach loop. In this case you should use a for loop.
var list = new List<string>();
list.Add("a");
list.Add("b");
list.Add("c");
list.Add("d");
for(int i = 0; i < list.Count(); i++)
{
if(list[i].Contains("a"))
{
list.RemoveAt(i);
}
}
Your results list will be:
b
c
d
Leaving the above for reference, but to #LarsTech comment below, list.RemoveAt breaks the loop so it will only remove 1 item. If you are wanting to remove multiple items, Alex's other answer with the Linq would be the cleanest way to remove it.
If you don't want to create a second list to house the items to be deleted, you can also do the below:
for (int i = 0; i < list.Count(); i++)
{
if (list[i].Contains("a"))
{
list = list.Where(x => !x.Contains("a")).ToList();
}
}
When you loop over a list with foreach and try to remove an item, you will get a runtime InvalidOperationException:
Collection was modified, enumeration operation may not execute
So you can use a for loop, which can handle modifications to the list. However, when you delete an item, all higher indexes will shift down. This will mean you skip an item.
The solution is to iterate in reverse order:
var list = new List<string>();
list.Add("a");
list.Add("b");
list.Add("b");
list.Add("c");
list.Add("d");
for (int i = list.Count()-1; i>=0; i--)
{
if (list[i].Contains("b"))
{
list.RemoveAt(i);
}
}
// result: a, c, d
you have to start at "count - 1", because that is the highest actual index
the last index to use is 0 (the start of the list)
and decrease the index after every iteration

C# for loop skipping last item

I have the below for loop
int listCount = _itemCollection.Count;
//_itemCollection is of type SPListItemCollection
for (int i=0;i<listCount;i++)
{
var item = _itemCollection[i]; // just to prevent changes in all places inside the for loop
if(item['expirydate']>today){
item.delete();
listCount--; //as I am removing 1 item, I am decrementing count
}
}
In this for loop, I am iterating through the items in itemcollection and deleting some of them. i.e item will be removed from itemcollection array and so itemcollection.count will be reduced by 1
This is not deleting the 3rd item every time, when I have 3 items to delete
I am not sure what condition should be used for getting it right
You should go in the reverse order as below and use for instead of foreach as below.
int listCount = _itemCollection.Count;
for (int i = listCount - 1; i >= 0; i--)
{
var item = _itemCollection[i]; // just to prevent changes in all places inside the for loop
if(item['expirydate'] > today){
item.delete();
}
}
You can do something like this:
_itemCollection.RemoveAll(item => item['expirydate'] > today);
This removes all the items that matches the given condition.
To remove item from SPListItemCollection check this documentation
Try this:
int listCount = _itemCollection.Count;
for (int i = 0; i < listCount; i++)
{
var item = _itemCollection[i];
if(item [expirydate] > today)
{
_itemCollection.Remove(item);
listCount--;
}
}
This may fulfill your want. Here you can directly use _itemCollection[i] instead of item.
I hope this may help you. Enjoy coding.

Switch statement inside a foreach loop - not getting expected results

So I am trying to loop though items that are in a listbox in my application. The list box will allow you to select multiple items to which I have a method tied to each item in the listbox. I have a counter variable incremented each time the loop works.When I use the foreach loop with the switch statement below, it does the first item correct, but then loops through the same item again. I know I am missing something as it is supposed to go to the next item in the listbox and not the same item.
string reportname = lstbxReports.SelectedValue.ToString();
int i = 0;
foreach (var report in reportname)
{
switch (reportname)
{
case "Overview":
{
if (i < 1)
{
PrintOverview(filename);
}
else if (i >= 1)
{
PrintOverviewAppend(filename);
}
break;
}
case "Sources":
{
if (i < 1)
{
PrintSource(filename);
}
else if (i >= 1)
{
PrintSourceAppend(filename);
}
break;
}
}
i++
Any thoughts or suggestions on how I can get the foreach loop to go to the next item in the selected listbox?
Also, this is just a snippet as I have about 11 case items to loop through.
You probably want to switch on report, not reportname.
foreach(string item in listBox.Items)
{
}
?
Depends on how you setup the data source for the listbox though (I'm assuming this is WinForm?). If you created it by adding .Items or using the designer then this will work. However if you've used .DataSource then it wont work.
I'd personally have a
List<string> list = SomeMethodWhereIMakeTheList();
and set that to:
listbox.DataSource = list;
then I wouldn't even have to touch the ListBox to mess with the contents:
list.ForEach(...)
Don't do the print logic in a foreach. Split out the data then print such this (note I changed the name of reportname to reportnames to signify a list of items)
string reportnames = lstbxReports.SelectedValue.ToString();
var firstReport = reportnames.First(); // No error checking here, would use FirstOrDefault with null checks.
if (firstReport == "OverView")
PrintOverview(filename);
else
PrintSource(filename);
// Now print out the rest
reportnames.Skip(1)
.ToList()
.ForEach(rp =>
{
if (rp == "OverView")
PrintOverviewAppend(filename);
else
PrintSourceAppend(filename);
});

RemoveAt() not working c#

Even after the RemoveAt() method, my list keeps being the same and I don't even get an error:
foreach (var row in queryCandidates.ToList())
{
try
{
xString = queryCandidates.ToList().ElementAt(i).District;
int.TryParse(xString, out xNumber);
temp = xNumber.Equals(districtNumber);
System.Diagnostics.Debug.Write(temp+ " ");
System.Diagnostics.Debug.Write(i+" ");
if (temp == false)
{
System.Diagnostics.Debug.WriteLine(" i is:"+i);
//not working even when it should
queryCandidates.ToList().RemoveAt(i);
}
}
catch { }
i++;
if (last == i)
{
System.Diagnostics.Debug.WriteLine("before ending loop: ");
return View(queryCandidates.ToList());
}
}
System.Diagnostics.Debug.WriteLine("after ending the loop: ");
return View(queryCandidates.ToList());
ToList() creates a new instance. From this instance you are removing the element. You are not removing the element from the original enumerable.
You should be doing something like this instead:
var candidates = queryCandidates.ToList();
var elementsToRemove = new List<int>();
foreach (var row in candidates)
{
// ...
xString = candidates[i].District;
// ...
if (temp == false)
{
// ...
elementsToRemove.Add(i);
}
}
for(int i = elementsToRemove.Count - 1; i >= 0; --i)
candidates.RemoveAt(elementsToRemove[i]);
return View(candidates);
Please note the use of elementsToRemove. You can't remove the items directly in the loop. This will throw an exception.
Additionally, please note that ToList copies all data. Every single time you call it. It should be obvious that this is not a good idea to do in a loop.
queryCandidates.ToList().RemoveAt(i);
ToList() creates a brand new list, which you then remove an element from, but that list is long gone.
Try:
var newList = queryCandidates.ToList();
for (int i=newList.Count-1; i>=0; i--){
///snip
newList.RemoveAt(i);
Note that I changed your foreach to for (in reverse) because you cannot modify a list while you are iterating over it with foreach.
The ToList() function creates a new List every time you call it. The object is removed from that list, not from the original list. So you should call ToList once before the foreach.
Once you've done that the removeAt() call will work and cause new issues because then you are trying to modify the list from within the foreach loop. So you'll need to rewrite your code in a way which takes the remove out of the loop as well.
Well I'm not exactly sure what Type queryCandidates is, but the reason you are not seeing an update is because you are removing element 'i' from the wrong object. Your ToList() function creates a new object of List type. If you want to keep the change you need to cache that list and use it where you use your original queryCandidates object.
queryCandidates isn't a list.
You're converting it to a list which creates a new instance from which you're removing the item but doesn't affect queryCandidates itself.
You can do:
var queryCandidates myCollection.ToList();
and then
queryCandidates.RemoveAt(i);
What works for me is to remove from the bottom up:
for (int i = list.Count - 1; i > 0; i--)
{
if (list[i][0] == " " || list[i][3] == "0")
list.RemoveAt(i);
}
It makes sense that some items are missed after decreasing the item count.

How to delete duplicate items in listview?

Well, I need to check if there exists duplicate items inside listview on my app, but... I don't know how.
The way to detect this is by checking the field "Tag", if they are the same, then delete the item.
A good way to find duplicates is to use a temporary hashset. This gives you an O(n) O(n log n) algorithm (see Rick Sladkeys comments) to detect duplicates. Example:
var tags = new HashSet<string>();
var duplicates = new List<Item>();
foreach(Item item in listView.Items)
{
// HashSet.Add() returns false if it already contains the key.
if(!tags.Add(item.Tag)
duplicates.Add(item);
}
[Remove duplicates here]
Used nested for loops to go through and check each item against each other item.
//tag duplicates for removal
List<Item> toRemove = new List<Item>();
foreach(Item item1 in listView.Items)
{
foreach(Item item2 in listView.Items)
{
//compare the two items
if(item1.Tag == item2.Tag)
toRemove.Add(item2);
}
}
//remove duplicates
foreach(Item item in toRemove)
{
listView.Items.Remove(item);
}
You'll have to tweak the syntax for your code, but that's the basic idea behind it. Also, there are optimizations that could probably be made to it, but don't worry about those yet.
None of the above helped me, so I thought I'd post what I came up with in case someone else is having this issue.
myListView is a ListView, sorted alphabetically (so duplicates in this case are adjacent). You could sort it programatically beforehand if you wanted.
myListView.Sorting = SortOrder.Ascending;
Anyway this is fairly simplistic but I hope it helps someone!
for (int i = 0; i < myListView.Items.Count - 1; i++)
{
if (myListView.Items[i].Tag == myListVIew.Items[i + 1].Tag)
{
myListView.Items[i + 1].Remove();
}
}
If you have a lot of items then you can use a HashSet to keep the performance acceptable.
Loop over the items starting with the HashSet empty. For each item check if the tag is in the HashSet. If so this is a duplicate. If not add the tag to the HashSet.
This approach avoids an N^2 algorithm which is what you get with a nested loop. The HashSet makes the algorithm linear complexity providing the removal phase is carefully implemented. Of course this may not matter to you depending on how many items you have. If the list is small use nested loops and keep it simple.
If you are comparing duplicated Items based on specific columns you should simply use:
if(listview.Items.Find(SearchQuery).Count > 0)
{
//remove duplicates from list...
}
but you should name every item in the list when you fill up the listview..
for example if I want to search for duplicate items in column 1+2+5 I'd name the whole row like:
myItemName.Name = column1.Text + "_" + column2.Text + "_" + column5.Text;
and then perform the above "if statement"..
I would like to offer my solution using a Dictionary for future reference:
(you will edit for your own "Colums" (subitems), Simply read the code to understand.
I used the below to remove "Dups" in a listview on a button click, i am searching subitem you can edit code for your own use...
uses dictionary and a little easy "update" class i wrote.
private void removeDupBtn_Click(object sender, EventArgs e)
{
Dictionary<string, string> dict = new Dictionary<string, string>();
int num = 0;
while (num <= listView1.Items.Count)
{
if (num == listView1.Items.Count)
{
break;
}
if (dict.ContainsKey(listView1.Items[num].SubItems[1].Text).Equals(false))
{
dict.Add(listView1.Items[num].SubItems[1].Text, ListView1.Items[num].SubItems[0].Text);
}
num++;
}
updateList(dict, listView1);
}
and using a little updateList() class...
private void updateList(Dictionary<string, string> dict, ListView list)
{
#region Sort
list.Items.Clear();
string[] arrays = dict.Keys.ToArray();
int num = 0;
while (num <= dict.Count)
{
if (num == dict.Count)
{
break;
}
ListViewItem lvi;
ListViewItem.ListViewSubItem lvsi;
lvi = new ListViewItem();
lvi.Text = dict[arrays[num]].ToString();
lvi.ImageIndex = 0;
lvi.Tag = dict[arrays[num]].ToString();
lvsi = new ListViewItem.ListViewSubItem();
lvsi.Text = arrays[num];
lvi.SubItems.Add(lvsi);
list.Items.Add(lvi);
list.EndUpdate();
num++;
}
#endregion
}
Good luck!
int J = 0;
In this simple code we will remove duplicates from a loop which will take the first item and compare it with the items bellow, so if it exists you can remove the above one or the second one
foreach (ListViewItem item1 in listView1.Items)
{
//J+1 to do not repeat the list from first and remove itself just take a look on next items
for (int i=J+1;i<listView1.Items.Count-1;i++)
{
//i compare two subitems that must be unique in my list
if (listView1.Items[J].SubItems[1].ToString() == listView1.Items[i].SubItems[1].ToString())
// listView1.Items.RemoveAt(i); // remove the second one
listView1.Items.RemoveAt(J); // remove the first one and keep the second
}
J++;
}

Categories