how properly remove item from list [duplicate] - c#

This question already has answers here:
Closed 12 years ago.
Possible Duplicates:
Exception during iteration on collection and remove items from that collection
How to remove elements from a generic list while iterating around it?
Better way to remove matched items from a list
// tmpClientList is List<Client> type
if (txtboxClientName.Text != "")
foreach (Client cli in tmpClientList)
if (cli.Name != txtboxClientName.Text)
tmpClientList.Remove(cli);
Error: "Collection was modified; enumeration operation may not execute."
How can i remove items from the list, in some simple way, without saving indexes of these items in another list or array, and removing them in another place in the code. Tried also RemoveAt(index) but it's exactly the same situation, modifying when loop runs.

Move backwards through the list.. that way removing an item does not affect the next item.
for(var i=tmpClientList.Count-1;i>=0;i--)
{
if (tmpClientList[i].Name != txtboxClientName.Text)
tmpClientList.RemoveAt(i);
}

On a List<T>, there is a RemoveAll method that takes a delegate to indicate whether to remove the item. You can use it like this:
tmpCLientList.RemoveAll(cli => cli.Name != txtboxClientName.Text);

Either use a for/while loop, or tmpClientList.RemoveAll(a => a.Name == txtboxClientName.Text). As you didn't specify which c# version you are using, ymmw.

Don't use foreach. Use for and descend the list (i.e. start from the end), using RemoveAt.
So,
// tmpClientList is List<Client> type
if (txtboxClientName.Text != "")
foreach (int pos = tmpClientList.Length - 1; pos >= 0; pos--)
{
Client cli = tmpClientList[pos];
if (cli.Name != txtboxClientName.Text)
tmpClientList.RemoveAt(pos);
}

The problem is that you are trying the modify the list in a foreach iteration. Replace that with a for and you should be ok.
Also, since you seem to be using user input for the name, consider cleaning up the input a bit, at least with a Trim() to remove extra white spaces. If you don't, 'John ' and 'John' will be two different things.
Same for the initial != "" check.

You can create another list with the items you want to delete and iterate the new list to remove items from your "txtboxClientName" list.

Actually, foreach uses Enumerators to iterate through given Item-Collections. Going further the System.Collections.Generic.List<T> implements the IEnumarable-Interface to provide a Class, that knows how to iterate through the items of the list, i.e. the Enumerator. Now if you iterate through that list by using foreach the Enumerator keeps track of the current position, how to reach the next position and some other stuff. The internal logic could be something like storing the number of items in a variable n and then access all objects from 0 to n-1. As you may notice if any object is removed between the iteration steps we shall end in a NullReferenceException when the Enumerator tries to deliver the last object of the list. So to prevent any iteration failures, the list itself is not allowed to be modified during Enumeration.
Hope I was able to state that out at least a little bit comprehensively. :-)

Related

How to compare property of a first element with a second element in a List?

I have a single list with objects. Let's call it ItemList. Objects have properties such as Name, Code, ParentCode and so on. Some of these items have the same ParentCode and I need to compare the first element's ParentCode with the next one in some conditions. So I am using an if condition like this:
if (ItemList.First().ParentCode != ItemList.ElementAt(1).ParentCode)
However, sometimes this causes some issues because the ItemList can have single element inside it and it throws argument out of range or index out of range exception. To overcome this I changed the code to this:
if (ItemList.Count >= 2 && ItemList.First().ParentCode != ItemList.ElementAt(1).ParentCode)
Sometimes I need to run the same method when the ItemList have only one element or the first element does not have the equal ParentCode with the second element so I use this condition:
if (ItemList.Count == 1 || ItemList.Count >= 2 && ItemList.First().ParentCode != ItemList.ElementAt(1).ParentCode)
All of these seems counterintuitive thus I am open to suggestions on making to code more maintainable and readable. Thanks in advance!
A couple other Linq functions may help here, depending on the use-case. I'm not sure I fully understand why you'd only want to compare the first 2 elements though. What if it has 10 or 100 items? Only the first 2 matter? If it can only ever have 2 elements because of some other business logic, then consider creating a class that holds exactly 2 items, and put the "comparison/validation" logic inside that class. A constructor that accepts 2 parameters, first + second instance, should ensure validity of the wrapper class.
Either way... for a purely LINQ solution...
ItemList.GroupBy(x => x.ParentCode).Where(x => x.Count() > 1) ... will get you a list of "groups" that contain more than 1 duplicate ParentCode. Iterating that will provide you with a "Key" representing the ParentCode or whatever you group by.
ItemList.Skip(1).FirstOrDefault() ... will get you the second element, if it exists, otherwise it will be the default for whatever type is in the list
Is there any specific reason you have to use linq here? I feel like just referencing the objects directly is just as good of a solution.
if(ItemList.Count == 2)
{
if(ItemList[0].ParentCode != ItemList[1].ParentCode)
{
..do stuff..
}
}
Yes, the code isn't as flat, but this is extremely readable. If you need to compare the 0th value to more than just 1st value, checking that the list's length is greater than or equal to 2 and using a for loop will work just fine.
if(ItemList.Count >= 2)
{
for (var i = 1; i < ItemList.Count; i++)
{
if(ItemList[0].ParentCode != ItemList[i].ParentCode)
{
..do stuff..
}
}
}
Only suggesting a non-linq solution because you didn't mention that it had to be linq. This code is still extremely readable and has no difference in performance. Also, as you mentioned in your post, none of this code seems counterintuitive.

Deleting element of JArray in for-loop [duplicate]

This question already has answers here:
How can I remove a specific item from an array in JavaScript?
(142 answers)
Closed 6 years ago.
I am trying to remove invalid elements of an JArray while iterating over it. Doing this in a foreach loop obviously doesn't work because it doesn't expect the array to get shorter. So I decided to do use a for loop. I am doing it like this:
// data is my JArray, its elements are JObjects
for (int i = 0; i < data.Count; i++)
{
if (!data[i].HasValues) { // checking if element is empty
data[i].Remove();
i--;
}
}
When I tested this it worked correctly. But I am not sure if it is safe or if I am just lucky. For example, I am not sure if the order of the elements stays the same after removing one element. Or are there any other reasons why this could not work?
I guess the best alternative, if this was incorrect, would be to copy all valid elements to a new array. But that would cause a lot of overhead.
You can do this manually in a better way; when you find a "faulty" element you want to remove, you just shift all other elements to the left thus overriding the one you don't want. But I think there's no need for you to do that. (if you're adamant in this request, comment and I'll provide you with the code for doing so).
Remove() method does that for you. Most methods for element insertion or removal from collections do work this way: they insert/delete the element at a desired position and shift other elements to the right/left respectively.

Does list.count physically iterate through the list to count it, or does it keep a pointer

I am stepping through a large list of object to do some stuff regarding said objects in the list.
During my iteration, I will remove some objects from the list depending on certain criteria.
Once all is done, I need to update the UI regarding the number of objects in my list. (List of T).
QUESTION:
When I call list.count, does .net actually iterate through the list to
count it, or does it store the count as a property/variable?
If .net physically re-iterates through the list, I may just as well keep a counter on my own iteration through the list, and save the overhead?
Thanks
It simply keeps an internal int to track the number of items. So no iteration.
The documentation says retrieving Count is an O(1) operation:
http://msdn.microsoft.com/en-us/library/27b47ht3%28v=vs.110%29.aspx
You can see for yourself:
http://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs
List is implemented as an array list, and it keeps track of its own size, so invoking the .Count property doesn't require any iteration.
If you call the LINQ .Count() extension method, this will check whether the underlying IEnumerable<> implements ICollection (which a List<> does), and use the .Count property on that interface if possible. So this won't cause any iteration to occur either.
Incidentally, there are other problems you're going to encounter if you attempt to remove items from your list while iterating through it. It's not really clear how iteration should behave when you are removing elements out from under the iterator, so List<>s will avoid this issue entirely by throwing an exception if the list has been modified since its enumerator was created.
You can use a decompiler, such as the freely-available ILSpy, to answer these questions. If you're referring to the List<T> type, then the Count getter simply involves reading a field:
public int Count
{
get { return this._size; }
}
As stated here under the remarks tab
http://msdn.microsoft.com/en-us/library/27b47ht3(v=vs.110).aspx
Retrieving the value of this property is an O(1) operation.
Which means no iteration is occurring.
You tagged your question with both vb.net and c#, so in reply to "If .net physically re-iterates through the list, I may just as well keep a counter on my own iteration through the list, and save the overhead?"
If your iteration is with a For i = first To last then VB.NET will evaluate first and last when it enters the loop:
Dim first As Integer = 1
Dim last As Integer = 3
For i = first To last
Console.Write(i.ToString() & " ")
last = -99
Next
outputs: 1 2 3
If you do the equivalent in C#, first and last are evaluated on every iteration:
int first = 1;
int last = 1;
for (int i = first; i <= last; i++)
{
Console.Write(i.ToString() + " ");
last = -99;
}
outputs: 1
If your .Count() function/property is expensive to evaluate and/or you don't want it to be re-evaluated on each iteration (for some other reason), then in C# you could assign it to a temporary variable.

C# ~ A Few questions about List<>

I don't usually develop on Windows but recently I have had to do a bit of work with C# and I'm trying to get my head around a few things. I've been looking through the MSDN but cant quite find what I'm looking for.
Anyway as I understand it a List is indexed, much like an array. However if I deleted an item at position X using RemoveAt() would it then shift all the items so that a new item now filled position X? Or would position X just be empty?
Also using Remove() seems fairly straight forward if you have a List of strings or integers, but if you have a list of objects is it possible to use Remove() to delete an item where an object field has a specific value?
For example
Say i have List where each car object has a make, model & color.
Could i do something along the lines of
cars.Remove(cars.color="red");
I'm sure that is horribly wrong but I am coming from a PHP background so im pretty confused about alot of the syntax at the moment.
Thanks
List<T> will not get holes.
Removing an item will shift all subsequent items up by one.
You can remove all items that match a condition by calling RemoveAll() with a lambda expression:
list.RemoveAll(o => o.Color == "Red");
if I deleted an item at position X using RemoveAt() would it then
shift all the items so that a new item now filled position X?
Yes.
if you have a list of objects is it possible to use Remove() to delete
an item where an object field has a specific value?
Yes, use the RemoveAll method:
cars.RemoveAll(c => c.color == "red");
When items are removed the spot is taken up by the next one. As lists are collections you can use lambda with remove all
Cars.RemoveAll( x=> x.colour == "red");
Will remove all re cars.

Check if the same item already exist in List<string>? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Check if a List already contains an item or not?
for (int i = 0; i < webSites.Count(); i++)
{
string t = webSites[i];
webCrawler(t, levels - 1);
// csFiles.add
}
MessageBox.Show(webSites.Count().ToString());
return csFiles;
Lets say in webSites i have:
www.google.com
www.microsoft.com
Now in the second level lets say www.google.com exist again so this time i dont want to process it to do the recrusive if it will it will do it all over again the same thing. I need somehow to make or check that it will do each link once. How can i check it ?
I dont need to check just if the item already exist in the List i need to check if it was exist already so dont do it again since it will dig the same links again and repeat it self.
Don't use a list for this - use a Hashset<string> - this has expected O(1) lookup time instead of O(n) for a list, and really the "set" metaphor fits perfectly:
HashSet<string> visitedPages = new HashSet<string>();
for (int i = 0; i < webSites.Count(); i++)
{
string page = webSites[i];
if(visitedPages.Add(page)) //returns true if new page was added
{
webCrawler(page, levels - 1);
}
}
If you call this method recursively, of course the declaration of the visitedPages hash set must be outside of the method, e.g. make it a member variable so that you can maintain the history of visited pages.
List.Contains method is what you need I guess but
List.Contains is O(n) and I would recommend a Hashset instead which has a O(1) lookup..
if your list contains all the entry then you can also use the Distinct() function in Linq which will return you an enumerable with Distinct elements only..
webSites.Distinct()
Keep visited items in a HashSet<string>.
Use Add when visiting a page and Contains when checking if you already have visited the page.
Create a temporary List and call it, let's say, "temp". Every iteration of the for loop, see if the string in that position in webSites is already in temp. If it is, ignore it. If it isn't, add it to temp and then process it.
EDIT: Apparently this isn't the best approach.
Why not you just select distinct list of websites at first place?
foreach (var site in webSites.GroupBy(s => s))
{
webCrawler(t, levels - 1);
// csFiles.add
}
MessageBox.Show(webSites.Count().ToString());
return csFiles;

Categories