I have bound the data to ListView from multiple sources. And there is duplicate data, I want to remove all the duplicates from that ListView. I used the following code but it is not helping.
listview.Sorting = System.Windows.Forms.SortOrder.Ascending;
for (int i = 0; i < listview.Items.Count - 1; i++)
{
if (listview.Items[i].Tag == listview.Items[i + 1].Tag)
{
listview.Items[i + 1].Remove();
}
}
Without a good, minimal, complete code example, it's not possible to know for sure what the problem is. However, most likely your Tag values are reference types and not actually identical object instances.
Assuming the objects override the Equals() method, you can fix it by using that method instead:
listview.Sorting = System.Windows.Forms.SortOrder.Ascending;
for (int i = 0; i < listview.Items.Count - 1; i++)
{
if (listview.Items[i].Tag.Equals(listview.Items[i + 1].Tag))
{
listview.Items[i + 1].Remove();
i--;
}
}
Note that you also had a bug in which you would skip checking elements if there were three or more duplicates of a given value. You can fix this by decrementing i when you remove an element (see above).
Related
I've been working on a project where I need on a button press that this line gets executed.
if (listView1.SelectedItems[0].SubItems[3].Text == "0") //Checks to see Value
{
listView1.SelectedItems[0].SubItems[3].Text = "1";// If Value is Greater, Increase and Change ListView
questionNumberLabel.Text = listView1.SelectedItems[0].SubItems[3].Text;// Increase and Change Label
}
Now I have this repeated about 10 times with each value increasing by one. But I know that this is ugly, and dysfunctional. As well as conflates the file size. I've tried a few things. Primarily this method.
if (listView1.SelectedItems[0].SubItems[3].Text == "0")
{
for (var i = 1; i < 100;)
{
if (!Int32.TryParse(listView1.SelectedItems[0].SubItems[3].Text, out i))
{
i = 0;
}
i++;
listView1.SelectedItems[0].SubItems[3].Text = i.ToString();
Console.WriteLine(i);
}
}
But instead of just adding one, it does the 100 instances and ends. The reason this is becoming a pain in the *** is because the
listView1.SelectedItems[0].SubItems[3].Text
is just that - it's a string, not an int. That's why I parsed it and tried to run it like that. But it still isn't having the out come I want.
I've also tried this
string listViewItemToChange = listView1.SelectedItems[0].SubItems[3].Text;
Then parsing the string, to make it prettier. It worked like it did before, but still hasn't given me the outcome I want. Which to reiterate is, I'm wanting the String taken from the list view to be changed into an int, used in the for loop, add 1, then restring it and output it on my listView.
Please help :(
You say you want the text from a listview subitem converted to an int which is then used in a loop
so - first your creating your loop variable, i, then in your loop you're assigning to it potentially 3 different values 2 of which are negated by the, i++. None of it makes sense and you shouldn't be manipulating your loop variable like that (unless understand what you're doing).
if you move statements around a little..
int itemsToCheck = 10; // "Now I have this repeated about 10 times "
for (var item = 0; item < itemsToCheck; item++)
{
int i;
if (!Int32.TryParse(listView1.SelectedItems[item].SubItems[3].Text, out i))
{
i = 0;
}
i++;
listView1.SelectedItems[item].SubItems[3].Text = i.ToString();
Console.WriteLine(i);
}
Something along those lines is what you're looking for. I haven't changed what your code does with i, just added a loop count itemsToCheck and used a different loop variable so your loop variable and parsed value are not one in the same which will likely be buggy.
Maybe this give you an idea. You can start using this syntax from C# 7.0
var s = listView1.SelectedItems[0].SubItems[3].Text;
var isNumeric = int.TryParse(s, out int n);
if(isNumeric is true && n > 0){
questionNumberLabel.Text = s;
}
to shortcut more
var s = listView1.SelectedItems[0].SubItems[3].Text;
if(int.TryParse(s, out int n) && n > 0){
questionNumberLabel.Text = s;
}
I want to check if a DateTime in CustomFormat is greater than another DateTime, but the checking is okay after that the code removes a lot of item and none of that correct. Here is my code. The job is to remove any ListViewItem which is older than 3 days.
for (int i = 0; i < lvValid.Items.Count; i++)
{
if (DateTime.Now.AddDays(-3) > DateTime.Parse(lvValid.Items[i].SubItems[1].Text))
{
lvValid.Items[i].Remove();
}
}
I've already tried foreach but it didn't help. And its removing every second element, but its not correct
One big problem with a forward loop is that if you remove one item, then the indices of the remaining will shift leftwards. So at i == 1 the next item will be at 1 after the removal. Then i will be incremented and skip the former next item.
I would suggest to use a backward loop:
for (int i = lvValid.Items.Count - 1; i >= 0 ; i--)
{
if (DateTime.Now.AddDays(-3) > DateTime.Parse(lvValid.Items[i].SubItems[1].Text))
{
lvValid.Items[i].Remove();
}
}
personally I would advice to work with the original data. First filter the data according to your condition and then repopulate the ListView.
Maybe you should Reverse for loop & not use .AddDays(-3)
for (int i = lvValid.Items.Count - 1; i >= 0; i--)
{
if (DateTime.Now <= DateTime.Parse(lvValid.Items[i].SubItems[1].Text).AddDays(3);
{
lvValid.Items[i].Remove();
}
}
If you want to use forward loop (if order of removing matters) you should increment i++ when you don't remove an item:
// we don't want redrawing on each removing (i.e. lvValid blicking)
lvValid.BeginUpdate();
try
{
for (int i = 0; i < lvValid.Items.Count; ) // don't icrement i here...
if (DateTime.Now.AddDays(-3) > DateTime.Parse(lvValid.Items[i].SubItems[1].Text))
lvValid.Items[i].Remove();
else
++i; // ... but there
}
finally
{
// when finisihing removing, redraw lvValid if required
lvValid.EndUpdate();
}
EDIT: so it looks like this is normal behavior, so can anyone just recommend a faster way to do these numerous intersections?
so my problem is this. I have 8000 lists (strings in each list). For each list (ranging from size 50 to 400), I'm comparing it to every other list and performing a calculation based on the intersection number. So I'll do
list1(intersect)list1= number
list1(intersect)list2= number
list1(intersect)list888= number
And I do this for every list. Previously, I had HashList and my code was essentially this: (well, I was actually searching through properties of an object, so I
had to modify the code a bit, but it's basically this:
I have my two versions below, but if anyone knows anything faster, please let me know!
Loop through AllLists, getting each list, starting with list1, and then do this:
foreach (List list in AllLists)
{
if (list1_length < list_length) //just a check to so I'm looping through the
//smaller list
{
foreach (string word in list1)
{
if (block.generator_list.Contains(word))
{
//simple integer count
}
}
}
// a little more code, but the same, but looping through the other list if it's smaller/bigger
Then I make the lists into regular lists, and applied Sort(), which changed my code to
foreach (List list in AllLists)
{
if (list1_length < list_length) //just a check to so I'm looping through the
//smaller list
{
for (int i = 0; i < list1_length; i++)
{
var test = list.BinarySearch(list1[i]);
if (test > -1)
{
//simple integer count
}
}
}
The first version takes about 6 seconds, the other one takes more than 20 (I just stop there cuz otherwise it would take more than a minute!!!) (and this is for a smallish subset of the data)
I'm sure there's a drastic mistake somewhere, but I can't find it.
Well I have tried three distinct methods for achieving this (assuming I understood the problem correctly). Please note I have used HashSet<int> in order to more easily generate random input.
setting up:
List<HashSet<int>> allSets = new List<HashSet<int>>();
Random rand = new Random();
for(int i = 0; i < 8000; ++i) {
HashSet<int> ints = new HashSet<int>();
for(int j = 0; j < rand.Next(50, 400); ++j) {
ints.Add(rand.Next(0, 1000));
}
allSets.Add(ints);
}
the three methods I checked (code is what runs in the inner loop):
the loop:
note that you are getting duplicated results in your code (intersecting set A with set B and later intersecting set B with set A).
It won't affect your performance thanks to the list length check you are doing. But iterating this way is clearer.
for(int i = 0; i < allSets.Count; ++i) {
for(int j = i + 1; j < allSets.Count; ++j) {
}
}
first method:
used IEnumerable.Intersect() to get the intersection with the other list and checked IEnumerable.Count() to get the size of the intersection.
var intersect = allSets[i].Intersect(allSets[j]);
count = intersect.Count();
this was the slowest one averaging 177s
second method:
cloned the smaller set of the two sets I was intersecting, then used ISet.IntersectWith() and checked the resulting sets Count.
HashSet<int> intersect;
HashSet<int> intersectWith;
if(allSets[i].Count < allSets[j].Count) {
intersect = new HashSet<int>(allSets[i]);
intersectWith = allSets[j];
} else {
intersect = new HashSet<int>(allSets[j]);
intersectWith = allSets[i];
}
intersect.IntersectWith(intersectWith);
count = intersect.Count;
}
}
this one was slightly faster, averaging 154s
third method:
did something very similar to what you did iterated over the shorter set and checked ISet.Contains on the longer set.
for(int i = 0; i < allSets.Count; ++i) {
for(int j = i + 1; j < allSets.Count; ++j) {
count = 0;
if(allSets[i].Count < allSets[j].Count) {
loopingSet = allSets[i];
containsSet = allSets[j];
} else {
loopingSet = allSets[j];
containsSet = allSets[i];
}
foreach(int k in loopingSet) {
if(containsSet.Contains(k)) {
++count;
}
}
}
}
this method was by far the fastest (as expected), averaging 66s
conclusion
the method you're using is the fastest of these three. I certainly can't think of a faster single threaded way to do this. Perhaps there is a better concurrent solution.
I've found that one of the most important considerations in iterating/searching any kind of collection is to choose the collection type very carefully. To iterate through a normal collection for your purposes will not be the most optimal. Try using something like:
System.Collections.Generic.HashSet<T>
Using the Contains() method while iterating over the shorter list of two (as you mentioned you're already doing) should give close to O(1) performance, the same as key lookups in the generic Dictionary type.
Hi I have a problem with a for loop.
It looks like this
for (int i = 0; i < ObjectManager.Instance.Objects.Count; i++)
{
if (ObjectManager.Instance.Objects[i] is Asteroid)
{
ObjectManager.Instance.Objects.Remove(ObjectManager.Instance.Objects[i]);
}
}
But the count gets shorter while I remove objects, which causes the loop to end prematurely. Is there a way to do this without a bunch of extra loops.
Why don't you loop backward?
// Just change the order from Count - 1 down to 0
for (int i = ObjectManager.Instance.Objects.Count - 1; i >= 0; --i)
{
if (ObjectManager.Instance.Objects[i] is Asteroid)
{
ObjectManager.Instance.Objects.Remove(ObjectManager.Instance.Objects[i]);
}
}
In case you have to loop forward (e.g. if Instances should be deleted in the order they are created because they are depend on each other) you can modify for loop in this way:
for (int i = 0; i < ObjectManager.Instance.Objects.Count;) // <- No increment here
if (ObjectManager.Instance.Objects[i] is Asteroid)
ObjectManager.Instance.Objects.Remove(ObjectManager.Instance.Objects[i]);
else
i += 1; // <- Increment should be here!
Yet another possibility is Linq:
ObjectManager.Instance.Objects.RemoveAll(item => item is Asteroid);
Three options:
If ObjectManager.Instance.Objects is a List<T>, use List<T>.RemoveAll with a predicate, making your code much simpler:
// This replaces your whole loop...
ObjectManager.Instance.Objects.RemoveAll(x => x is Asteroid);
Count from the end of the collection rather than from the start, so that you don't need to adjust the index afterwards:
for (int i = ObjectManager.Instance.Objects.Count - 1; i >= 0; i--)
Just decrement i after calling Remove, so that you'll look at the right index on the next iteration.
Note that in the second and third options your code will be a lot simpler to read if you extract the expression ObjectManager.Instance.Objects into a local variable before you use it 4 times. Also consider using RemoveAt(i) rather than Remove(instances[i]), assuming RemoveAt is available for the type you're using.
My current code:
Remove()
{
for (int i = 0; i < ConGridView.RowCount; i++)
{
if (ConGridView.Rows[i].Cells[0].Value.ToString() == Address)
{
ConGridView.Rows.RemoveAt(i);
break;
}
}
}
So what I am trying to call the remove function every time a client disconnect. the function will remove the connection address from the datagridview. It works well when clients are disconnection one by one. However, if 100 connections gets dropped and it tries to remove 100 connections in less than a second, than it errors out saying "Row Index provided is out of range". How should I check for that ?
So far I've tried:
Try, catch.
if (ConGridView.Rows[i] != null), if (i < ConGridView.RowCount)
None of it seem to work so far. I've also got results using (i < ConGridView.RowCount) where i is 26 while RowCount is 24, but the remove at function still activates..
Any idea on this ?
You can't do this. Your code loops through all the rows in ConGridView, but it deletes them as you do. Therefore, at some point you will try to access an item you have deleted, which will cause the error you described.
Probably the best approach it to iterate through the rows in reverse order. This way, deleting a row at the end won't affect when you access rows at the start.
The problem is you initialise your for loop with the current count of rows and then start removing those same rows from the datagridview. At some point your for loop will try to remove a row at an index that is greater than the number of rows left.
Try this instead:
for (int i = ConGridView.RowCount - 1; i >= 0; i--)
{
if (ConGridView.Rows[i].Cells[0].Value.ToString() == Address)
{
ConGridView.Rows.RemoveAt(i);
break;
}
}
why dont you get the total count to a separate variable and then iterate
Remove()
{
int totalConnections = ConGridView.RowCount;
for (int i = 0; i < totalConnections ; i++)
{
if (ConGridView.Rows[i].Cells[0].Value.ToString() == Address)
{
ConGridView.Rows.RemoveAt(i);
break;
}
}
}
This issue is becuase you are modifying the collection your are iterating over. It will be better if you use a temporary array and two loops to remove your entries.
Remove()
// You can use an array/list or whatever you want below.
Collection<DataGridViewRow> rowsToDelete = new Collection<DataGridViewRow>();
for (int i = 0; i < ConGridView.RowCount; i++)
{
if (ConGridView.Rows[i].Cells[0].Value.ToString() == Address)
{
rowsToDelete.Add(ConGridView.Rows[i]);
break;
}
}
// now remove the marked entries.
foreach(DataGridViewRow deletedRow in rowsToDelete)
{
ConGridView.Rows.Remove(deletedRow);
}
When you remove an item from an array, it is reconstructed; shifting the remaining elements up by one to remove the gap of the index you have removed.
1. guybrush threepwood
2. murray
3. elaine
4. Jimmy Gibbs Jr.
If you remove 2. item in here; it becomes this:
1. guybrush threepwood
2. elaine
3. Jimmy Gibbs Jr.
When you are iterating, imagine:
for (int i = 0; i < myArray.Count; i++)
{
if (i == 2) myArray.RemoveAt(i);
}
While running this, when i = 3, the element at 3 has changed, you expect it to be 'elaine' but it is 'Jimmy Gibbs Jr.'. One way to fix this is decrease i by one if we delete it, making sure that i refers to correct value.
for (int i = 0; i < myArray.Count; i++)
{
if (i == 2)
{
myArray.RemoveAt(i);
i--;
}
}
I would go for LINQ in this case, though, everything is easier with that.
myArray.RemoveAll(x => x == "murray");
I've tried all the suggestions posted by everyone here, however, the error was still there.
I've solved the problem using a different way... I've switched to TreeNodeView since that's what I was going to use ultimately. Now I can remove as many connection as i want with:
For each(TreeNode TN in ConTreeView)
{
ConTreeView.Nodes.Remove(TN);
}