Linq - Remove Object From Collection
-
Ok, so now I have this:
NodeModel folder = (from f in Folders
where f.Id == model.Id
select f).FirstOrDefault();
Folders.Remove(folder);The query is returning null. It's not finding the folder. It appears that the query is only looking at the first level and not drilling down into the collection.
Everything makes sense in someone's mind
-
Yes. It sure doesn't. I completely missed the inner list. So you want to remove from the ChildNode inner list or from the main/upper list or from both?
All the best, Dan
I want to find i anywhere in the list and remove it.
Everything makes sense in someone's mind
-
I want to find i anywhere in the list and remove it.
Everything makes sense in someone's mind
Well this is more complicated then it looks. Cause you don't know upfront how many nested levels you have. As each node has a list of childnodes and then each node in the nested childNodes list could have an even deeper nested level list of childNodes ... The only thing that comes to mind wright now is a brute force step by step actions: 1)ForEach item in the upper level list check to see if it has the id and remove it if so. 2) Else loop through the inner collection if Count>0 and remove from there else loop through it's inner collection if Count>0 and so on. Unless you do know that there are only 2 levels.
All the best, Dan
-
Well this is more complicated then it looks. Cause you don't know upfront how many nested levels you have. As each node has a list of childnodes and then each node in the nested childNodes list could have an even deeper nested level list of childNodes ... The only thing that comes to mind wright now is a brute force step by step actions: 1)ForEach item in the upper level list check to see if it has the id and remove it if so. 2) Else loop through the inner collection if Count>0 and remove from there else loop through it's inner collection if Count>0 and so on. Unless you do know that there are only 2 levels.
All the best, Dan
There can be any levels. I could write a recursive method, but I think that will cause problems modifying the collection.
Everything makes sense in someone's mind
-
There can be any levels. I could write a recursive method, but I think that will cause problems modifying the collection.
Everything makes sense in someone's mind
True, a faster approach would be to use a boolean filed like obsolete/deleted/visible. Then instead of deleting/altering the collection you would just set the value to true(no list and/or memory reallocation). When populating UI or whatever, based on the boolean field you'll show it or not.
All the best, Dan
-
There can be any levels. I could write a recursive method, but I think that will cause problems modifying the collection.
Everything makes sense in someone's mind
Only if you use foreach ... it's a list so you can use integer indices. Not thread-safe but self-modification-safe:
void Remove<T>(IList<T> list, T node) where T:INested<T> {
if(list == null) return; // in case you use ChildNodes=null to specify no children
for(int i = list.Count; i >= 0; i--){
if(list[i] == node) list.RemoveAt(i);
else Remove(list[i].ChildNodes, node);
}
}where
interface INested<T> { IList<T> ChildNodes; }
If this is the only place you need something like this, you don't have to generic it, but it is a general algorithm so you might want to put it in a utility library and have your class implement that interface.
-
I have a list of these models:
public class NodeModel : ViewModelBase
{
public Guid Id { get; set; }
public string Caption { get; set; }
public string Description { get; set; }
public bool IsActive { get; set; }
public int Sequence { get; set; }
public ImageSource Image { get; set; }
public bool IsExpanded { get; set; }
public string FileName { get; set; }
public NodeType Type { get; set; }
public List Children { get; set; }public NodeModel() { Children = new List(); FileName = string.Empty; }
}
The list is contained in a collection called Folders. I want to remove an object from the collection, so I'm using:
Folders.Remove(f => f.Id == model.Id);
But it's not removing it, What's wrong here?
Everything makes sense in someone's mind
If the search for the item to remove did not have to be recursive: this is simpler than the other example shown in the answers here:
Folders.Remove(Folders.Find(f => f.id == model.id));
To determine whether a recursive solution is actually required, I think we need to know: 1. is the collection of Models of fixed size, or read in at one time: if it is, then, imho, creating a flattened List is the way to go: then you can use model.id to get the instance of the Model in the flattened list, and use the simple form of Folders.Remove(modelInstance), and then remove the Model from the flattened list. 2. if the collection is nested only once (a two-level tree), a simpler solution is possible using Linq. See:[^]. 3. if the solution is nested to depth > 2, or has arbitrary levels of nesting, a more complex solution in Linq is possible. I suggest you look up the various solutions on StackOverFlow that use Eric Lippert's explanation ... he cites Wes Dyer[^] ... of using Linq to create a flattened hierarchy via the use of a 'Stack' .NET data structure for greatly increased efficiency. See:[^]. 4. if the collection of Models is being modified, new Models are being added, etc., frequently, then paying the "cost" of synchronizing (recreating ?) the flattened List may be prohibitive. In that case, well: you just gotta recurse. The interesting challenge in writing the recursive code in this case is how to immediately, once a matching node has been found, to delete it, and skip iterating its children, given that you can't modify the collection on the fly: which, really, is another reason to consider the use of a "parallel" flattened list, imho. Or you could extend the more common example of using Linq to essentially "temporarily copy" a List, or Dictionary, structure, and then apply the collection modifying operation to the "original" tree ? ... edit 1 ... Bob Janova posted an interesting way, on this thread, to use a for loop and its indexes to be able to delete from a nested List structure without violating the "no modify collection injunction" found using foreach:[^]. ... end edit 1 ... ... edit 2 ... also see Marc Gravell's recursive Flattening function:[^], which is in the form of an extension. ... end edit 2 ... Of course,
-
Only if you use foreach ... it's a list so you can use integer indices. Not thread-safe but self-modification-safe:
void Remove<T>(IList<T> list, T node) where T:INested<T> {
if(list == null) return; // in case you use ChildNodes=null to specify no children
for(int i = list.Count; i >= 0; i--){
if(list[i] == node) list.RemoveAt(i);
else Remove(list[i].ChildNodes, node);
}
}where
interface INested<T> { IList<T> ChildNodes; }
If this is the only place you need something like this, you don't have to generic it, but it is a general algorithm so you might want to put it in a utility library and have your class implement that interface.
Definition of the INested<T> interface will not compile since ChildNodes is a field. So, for testing your code, I'll be starting with this:
interface INested<T>
{
IList<T> ChildNodes { get; set; }
}In your code, where you compare:
if (list[i] == node) list.RemoveAt(i);
the compiler throws an error: " Operator '==' cannot be applied to operands of type 'T' and 'T' " best, Bill
"For no man lives in the external truth among salts and acids, but in the warm, phantasmagoric chamber of his brain, with the painted windows and the storied wall." Robert Louis Stevenson
-
Only if you use foreach ... it's a list so you can use integer indices. Not thread-safe but self-modification-safe:
void Remove<T>(IList<T> list, T node) where T:INested<T> {
if(list == null) return; // in case you use ChildNodes=null to specify no children
for(int i = list.Count; i >= 0; i--){
if(list[i] == node) list.RemoveAt(i);
else Remove(list[i].ChildNodes, node);
}
}where
interface INested<T> { IList<T> ChildNodes; }
If this is the only place you need something like this, you don't have to generic it, but it is a general algorithm so you might want to put it in a utility library and have your class implement that interface.
I'm going to be studying this code carefully today, since it appears to me a very valuable alternative to using foreach iteration, or using Linq to kind of create a copy of the recursive data structure from which you can modify the original structure without the "modification" error. My guess is that this (once compile-time errors are fixed) would make a great Tip/Trick for that section of CP. thanks, Bill
"For no man lives in the external truth among salts and acids, but in the warm, phantasmagoric chamber of his brain, with the painted windows and the storied wall." Robert Louis Stevenson
-
Only if you use foreach ... it's a list so you can use integer indices. Not thread-safe but self-modification-safe:
void Remove<T>(IList<T> list, T node) where T:INested<T> {
if(list == null) return; // in case you use ChildNodes=null to specify no children
for(int i = list.Count; i >= 0; i--){
if(list[i] == node) list.RemoveAt(i);
else Remove(list[i].ChildNodes, node);
}
}where
interface INested<T> { IList<T> ChildNodes; }
If this is the only place you need something like this, you don't have to generic it, but it is a general algorithm so you might want to put it in a utility library and have your class implement that interface.
I've further elaborated the idea above, using an extension method:
// Base interfaces for a node
public interface INode
where T : INode // A child is the same class as the parent
{
Guid Id { get; set; }
List Children { get; set; }
}// Static class containing Extension Methods
public static class NodeExtensions
{
// Recursively removes a node by its id
public static void Remove(this INode node, Guid id)
where T : INode
{
// List that will contains the nodes to be removed
// Can't remove nodes directly in the foreach loop
// (will throw "Collection was modified; enumeration operation may not execute.")
List toRemove = new List();if (node.Children == null) return; // No childrens, exit foreach (var child in node.Children) { if (child.Id == id) toRemove.Add(child); // The node has to be removed else child.Remove(id); // Recursion here } // Removes all found nodes foreach (var child in toRemove) node.Children.Remove(child); }
}